~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-02-25 08:21:38 UTC
  • Revision ID: grantw@unimelb.edu.au-20090225082138-9xjes8motif205xw
Move the old tutorial views into the 'subjects' tab, so they get the right
icon and highlight the right tab.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2009 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
# Author: Matt Giuca, Will Grant
 
19
 
 
20
"""
 
21
Database Classes and Utilities for Storm ORM
 
22
 
 
23
This module provides all of the classes which map to database tables.
 
24
It also provides miscellaneous utility functions for database interaction.
 
25
"""
 
26
 
 
27
import md5
 
28
import datetime
 
29
 
 
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
 
31
                         Reference, ReferenceSet, Bool, Storm, Desc
 
32
 
 
33
import ivle.conf
 
34
 
 
35
__all__ = ['get_store',
 
36
            'User',
 
37
            'Subject', 'Semester', 'Offering', 'Enrolment',
 
38
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
 
39
            'Exercise', 'Worksheet', 'WorksheetExercise',
 
40
            'ExerciseSave', 'ExerciseAttempt',
 
41
            'TestCase', 'TestSuite', 'TestSuiteVar'
 
42
        ]
 
43
 
 
44
def _kwarg_init(self, **kwargs):
 
45
    for k,v in kwargs.items():
 
46
        if k.startswith('_') or not hasattr(self.__class__, k):
 
47
            raise TypeError("%s got an unexpected keyword argument '%s'"
 
48
                % (self.__class__.__name__, k))
 
49
        setattr(self, k, v)
 
50
 
 
51
def get_conn_string():
 
52
    """
 
53
    Returns the Storm connection string, generated from the conf file.
 
54
    """
 
55
 
 
56
    clusterstr = ''
 
57
    if ivle.conf.db_user:
 
58
        clusterstr += ivle.conf.db_user
 
59
        if ivle.conf.db_password:
 
60
            clusterstr += ':' + ivle.conf.db_password
 
61
        clusterstr += '@'
 
62
 
 
63
    host = ivle.conf.db_host or 'localhost'
 
64
    port = ivle.conf.db_port or 5432
 
65
 
 
66
    clusterstr += '%s:%d' % (host, port)
 
67
 
 
68
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
 
69
 
 
70
def get_store():
 
71
    """
 
72
    Open a database connection and transaction. Return a storm.store.Store
 
73
    instance connected to the configured IVLE database.
 
74
    """
 
75
    return Store(create_database(get_conn_string()))
 
76
 
 
77
# USERS #
 
78
 
 
79
class User(Storm):
 
80
    """
 
81
    Represents an IVLE user.
 
82
    """
 
83
    __storm_table__ = "login"
 
84
 
 
85
    id = Int(primary=True, name="loginid")
 
86
    login = Unicode()
 
87
    passhash = Unicode()
 
88
    state = Unicode()
 
89
    admin = Bool()
 
90
    unixid = Int()
 
91
    nick = Unicode()
 
92
    pass_exp = DateTime()
 
93
    acct_exp = DateTime()
 
94
    last_login = DateTime()
 
95
    svn_pass = Unicode()
 
96
    email = Unicode()
 
97
    fullname = Unicode()
 
98
    studentid = Unicode()
 
99
    settings = Unicode()
 
100
 
 
101
    __init__ = _kwarg_init
 
102
 
 
103
    def __repr__(self):
 
104
        return "<%s '%s'>" % (type(self).__name__, self.login)
 
105
 
 
106
    def authenticate(self, password):
 
107
        """Validate a given password against this user.
 
108
 
 
109
        Returns True if the given password matches the password hash for this
 
110
        User, False if it doesn't match, and None if there is no hash for the
 
111
        user.
 
112
        """
 
113
        if self.passhash is None:
 
114
            return None
 
115
        return self.hash_password(password) == self.passhash
 
116
 
 
117
    @property
 
118
    def password_expired(self):
 
119
        fieldval = self.pass_exp
 
120
        return fieldval is not None and datetime.datetime.now() > fieldval
 
121
 
 
122
    @property
 
123
    def account_expired(self):
 
124
        fieldval = self.acct_exp
 
125
        return fieldval is not None and datetime.datetime.now() > fieldval
 
126
 
 
127
    @property
 
128
    def valid(self):
 
129
        return self.state == 'enabled' and not self.account_expired
 
130
 
 
131
    def _get_enrolments(self, justactive):
 
132
        return Store.of(self).find(Enrolment,
 
133
            Enrolment.user_id == self.id,
 
134
            (Enrolment.active == True) if justactive else True,
 
135
            Enrolment.offering_id == Offering.id,
 
136
            Offering.semester_id == Semester.id,
 
137
            Offering.subject_id == Subject.id).order_by(
 
138
                Desc(Semester.year),
 
139
                Desc(Semester.semester),
 
140
                Desc(Subject.code)
 
141
            )
 
142
 
 
143
    def _set_password(self, password):
 
144
        if password is None:
 
145
            self.passhash = None
 
146
        else:
 
147
            self.passhash = unicode(User.hash_password(password))
 
148
    password = property(fset=_set_password)
 
149
 
 
150
    @property
 
151
    def subjects(self):
 
152
        return Store.of(self).find(Subject,
 
153
            Enrolment.user_id == self.id,
 
154
            Enrolment.active == True,
 
155
            Offering.id == Enrolment.offering_id,
 
156
            Subject.id == Offering.subject_id).config(distinct=True)
 
157
 
 
158
    # TODO: Invitations should be listed too?
 
159
    def get_groups(self, offering=None):
 
160
        preds = [
 
161
            ProjectGroupMembership.user_id == self.id,
 
162
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
 
163
        ]
 
164
        if offering:
 
165
            preds.extend([
 
166
                ProjectSet.offering_id == offering.id,
 
167
                ProjectGroup.project_set_id == ProjectSet.id,
 
168
            ])
 
169
        return Store.of(self).find(ProjectGroup, *preds)
 
170
 
 
171
    @property
 
172
    def groups(self):
 
173
        return self.get_groups()
 
174
 
 
175
    @property
 
176
    def active_enrolments(self):
 
177
        '''A sanely ordered list of the user's active enrolments.'''
 
178
        return self._get_enrolments(True)
 
179
 
 
180
    @property
 
181
    def enrolments(self):
 
182
        '''A sanely ordered list of all of the user's enrolments.'''
 
183
        return self._get_enrolments(False) 
 
184
 
 
185
    @staticmethod
 
186
    def hash_password(password):
 
187
        return md5.md5(password).hexdigest()
 
188
 
 
189
    @classmethod
 
190
    def get_by_login(cls, store, login):
 
191
        """
 
192
        Get the User from the db associated with a given store and
 
193
        login.
 
194
        """
 
195
        return store.find(cls, cls.login == unicode(login)).one()
 
196
 
 
197
    def get_permissions(self, user):
 
198
        if user and user.admin or user is self:
 
199
            return set(['view', 'edit'])
 
200
        else:
 
201
            return set()
 
202
 
 
203
# SUBJECTS AND ENROLMENTS #
 
204
 
 
205
class Subject(Storm):
 
206
    __storm_table__ = "subject"
 
207
 
 
208
    id = Int(primary=True, name="subjectid")
 
209
    code = Unicode(name="subj_code")
 
210
    name = Unicode(name="subj_name")
 
211
    short_name = Unicode(name="subj_short_name")
 
212
    url = Unicode()
 
213
 
 
214
    offerings = ReferenceSet(id, 'Offering.subject_id')
 
215
 
 
216
    __init__ = _kwarg_init
 
217
 
 
218
    def __repr__(self):
 
219
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
 
220
 
 
221
    def get_permissions(self, user):
 
222
        perms = set()
 
223
        if user is not None:
 
224
            perms.add('view')
 
225
            if user.admin:
 
226
                perms.add('edit')
 
227
        return perms
 
228
 
 
229
class Semester(Storm):
 
230
    __storm_table__ = "semester"
 
231
 
 
232
    id = Int(primary=True, name="semesterid")
 
233
    year = Unicode()
 
234
    semester = Unicode()
 
235
    state = Unicode()
 
236
 
 
237
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
238
 
 
239
    __init__ = _kwarg_init
 
240
 
 
241
    def __repr__(self):
 
242
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
 
243
 
 
244
class Offering(Storm):
 
245
    __storm_table__ = "offering"
 
246
 
 
247
    id = Int(primary=True, name="offeringid")
 
248
    subject_id = Int(name="subject")
 
249
    subject = Reference(subject_id, Subject.id)
 
250
    semester_id = Int(name="semesterid")
 
251
    semester = Reference(semester_id, Semester.id)
 
252
    groups_student_permissions = Unicode()
 
253
 
 
254
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
 
255
    members = ReferenceSet(id,
 
256
                           'Enrolment.offering_id',
 
257
                           'Enrolment.user_id',
 
258
                           'User.id')
 
259
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
260
 
 
261
    worksheets = ReferenceSet(id, 
 
262
        'Worksheet.offering_id', 
 
263
        order_by="Worksheet.seq_no"
 
264
    )
 
265
 
 
266
    __init__ = _kwarg_init
 
267
 
 
268
    def __repr__(self):
 
269
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
 
270
                                  self.semester)
 
271
 
 
272
    def enrol(self, user, role=u'student'):
 
273
        '''Enrol a user in this offering.'''
 
274
        enrolment = Store.of(self).find(Enrolment,
 
275
                               Enrolment.user_id == user.id,
 
276
                               Enrolment.offering_id == self.id).one()
 
277
 
 
278
        if enrolment is None:
 
279
            enrolment = Enrolment(user=user, offering=self)
 
280
            self.enrolments.add(enrolment)
 
281
 
 
282
        enrolment.active = True
 
283
        enrolment.role = role
 
284
 
 
285
    def get_permissions(self, user):
 
286
        perms = set()
 
287
        if user is not None:
 
288
            perms.add('view')
 
289
            if user.admin:
 
290
                perms.add('edit')
 
291
        return perms
 
292
 
 
293
class Enrolment(Storm):
 
294
    __storm_table__ = "enrolment"
 
295
    __storm_primary__ = "user_id", "offering_id"
 
296
 
 
297
    user_id = Int(name="loginid")
 
298
    user = Reference(user_id, User.id)
 
299
    offering_id = Int(name="offeringid")
 
300
    offering = Reference(offering_id, Offering.id)
 
301
    role = Unicode()
 
302
    notes = Unicode()
 
303
    active = Bool()
 
304
 
 
305
    @property
 
306
    def groups(self):
 
307
        return Store.of(self).find(ProjectGroup,
 
308
                ProjectSet.offering_id == self.offering.id,
 
309
                ProjectGroup.project_set_id == ProjectSet.id,
 
310
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
311
                ProjectGroupMembership.user_id == self.user.id)
 
312
 
 
313
    __init__ = _kwarg_init
 
314
 
 
315
    def __repr__(self):
 
316
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
317
                                  self.offering)
 
318
 
 
319
# PROJECTS #
 
320
 
 
321
class ProjectSet(Storm):
 
322
    __storm_table__ = "project_set"
 
323
 
 
324
    id = Int(name="projectsetid", primary=True)
 
325
    offering_id = Int(name="offeringid")
 
326
    offering = Reference(offering_id, Offering.id)
 
327
    max_students_per_group = Int()
 
328
 
 
329
    projects = ReferenceSet(id, 'Project.project_set_id')
 
330
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
 
331
 
 
332
    __init__ = _kwarg_init
 
333
 
 
334
    def __repr__(self):
 
335
        return "<%s %d in %r>" % (type(self).__name__, self.id,
 
336
                                  self.offering)
 
337
 
 
338
class Project(Storm):
 
339
    __storm_table__ = "project"
 
340
 
 
341
    id = Int(name="projectid", primary=True)
 
342
    synopsis = Unicode()
 
343
    url = Unicode()
 
344
    project_set_id = Int(name="projectsetid")
 
345
    project_set = Reference(project_set_id, ProjectSet.id)
 
346
    deadline = DateTime()
 
347
 
 
348
    __init__ = _kwarg_init
 
349
 
 
350
    def __repr__(self):
 
351
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
 
352
                                  self.project_set.offering)
 
353
 
 
354
class ProjectGroup(Storm):
 
355
    __storm_table__ = "project_group"
 
356
 
 
357
    id = Int(name="groupid", primary=True)
 
358
    name = Unicode(name="groupnm")
 
359
    project_set_id = Int(name="projectsetid")
 
360
    project_set = Reference(project_set_id, ProjectSet.id)
 
361
    nick = Unicode()
 
362
    created_by_id = Int(name="createdby")
 
363
    created_by = Reference(created_by_id, User.id)
 
364
    epoch = DateTime()
 
365
 
 
366
    members = ReferenceSet(id,
 
367
                           "ProjectGroupMembership.project_group_id",
 
368
                           "ProjectGroupMembership.user_id",
 
369
                           "User.id")
 
370
 
 
371
    __init__ = _kwarg_init
 
372
 
 
373
    def __repr__(self):
 
374
        return "<%s %s in %r>" % (type(self).__name__, self.name,
 
375
                                  self.project_set.offering)
 
376
 
 
377
class ProjectGroupMembership(Storm):
 
378
    __storm_table__ = "group_member"
 
379
    __storm_primary__ = "user_id", "project_group_id"
 
380
 
 
381
    user_id = Int(name="loginid")
 
382
    user = Reference(user_id, User.id)
 
383
    project_group_id = Int(name="groupid")
 
384
    project_group = Reference(project_group_id, ProjectGroup.id)
 
385
 
 
386
    __init__ = _kwarg_init
 
387
 
 
388
    def __repr__(self):
 
389
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
390
                                  self.project_group)
 
391
 
 
392
# WORKSHEETS AND EXERCISES #
 
393
 
 
394
class Exercise(Storm):
 
395
    __storm_table__ = "exercise"
 
396
    id = Unicode(primary=True, name="identifier")
 
397
    name = Unicode()
 
398
    description = Unicode()
 
399
    partial = Unicode()
 
400
    solution = Unicode()
 
401
    include = Unicode()
 
402
    num_rows = Int()
 
403
 
 
404
    worksheets = ReferenceSet(id,
 
405
        'WorksheetExercise.exercise_id',
 
406
        'WorksheetExercise.worksheet_id',
 
407
        'Worksheet.id'
 
408
    )
 
409
    
 
410
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
411
 
 
412
    __init__ = _kwarg_init
 
413
 
 
414
    def __repr__(self):
 
415
        return "<%s %s>" % (type(self).__name__, self.name)
 
416
 
 
417
    def get_permissions(self, user):
 
418
        perms = set()
 
419
        if user is not None:
 
420
            if user.admin:
 
421
                perms.add('edit')
 
422
                perms.add('view')
 
423
        return perms
 
424
 
 
425
class Worksheet(Storm):
 
426
    __storm_table__ = "worksheet"
 
427
 
 
428
    id = Int(primary=True, name="worksheetid")
 
429
    offering_id = Int(name="offeringid")
 
430
    identifier = Unicode()
 
431
    name = Unicode()
 
432
    assessable = Bool()
 
433
    data = Unicode()
 
434
    seq_no = Int()
 
435
    format = Unicode()
 
436
 
 
437
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
438
    offering = Reference(offering_id, 'Offering.id')
 
439
 
 
440
    all_worksheet_exercises = ReferenceSet(id,
 
441
        'WorksheetExercise.worksheet_id')
 
442
 
 
443
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
444
    # objects binding worksheets to exercises. This is required to access the
 
445
    # "optional" field.
 
446
    @property
 
447
    def worksheet_exercises(self):
 
448
        return self.all_worksheet_exercises.find(active=True)
 
449
 
 
450
    __init__ = _kwarg_init
 
451
 
 
452
    def __repr__(self):
 
453
        return "<%s %s>" % (type(self).__name__, self.name)
 
454
 
 
455
    # XXX Refactor this - make it an instance method of Subject rather than a
 
456
    # class method of Worksheet. Can't do that now because Subject isn't
 
457
    # linked referentially to the Worksheet.
 
458
    @classmethod
 
459
    def get_by_name(cls, store, subjectname, worksheetname):
 
460
        """
 
461
        Get the Worksheet from the db associated with a given store, subject
 
462
        name and worksheet name.
 
463
        """
 
464
        return store.find(cls, cls.subject == unicode(subjectname),
 
465
            cls.name == unicode(worksheetname)).one()
 
466
 
 
467
    def remove_all_exercises(self, store):
 
468
        """
 
469
        Remove all exercises from this worksheet.
 
470
        This does not delete the exercises themselves. It just removes them
 
471
        from the worksheet.
 
472
        """
 
473
        store.find(WorksheetExercise,
 
474
            WorksheetExercise.worksheet == self).remove()
 
475
            
 
476
    def get_permissions(self, user):
 
477
        return self.offering.get_permissions(user)
 
478
 
 
479
class WorksheetExercise(Storm):
 
480
    __storm_table__ = "worksheet_exercise"
 
481
    
 
482
    id = Int(primary=True, name="ws_ex_id")
 
483
 
 
484
    worksheet_id = Int(name="worksheetid")
 
485
    worksheet = Reference(worksheet_id, Worksheet.id)
 
486
    exercise_id = Unicode(name="exerciseid")
 
487
    exercise = Reference(exercise_id, Exercise.id)
 
488
    optional = Bool()
 
489
    active = Bool()
 
490
    seq_no = Int()
 
491
    
 
492
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
493
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
 
494
 
 
495
    __init__ = _kwarg_init
 
496
 
 
497
    def __repr__(self):
 
498
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
 
499
                                  self.worksheet.identifier)
 
500
 
 
501
class ExerciseSave(Storm):
 
502
    """
 
503
    Represents a potential solution to an exercise that a user has submitted
 
504
    to the server for storage.
 
505
    A basic ExerciseSave is just the current saved text for this exercise for
 
506
    this user (doesn't count towards their attempts).
 
507
    ExerciseSave may be extended with additional semantics (such as
 
508
    ExerciseAttempt).
 
509
    """
 
510
    __storm_table__ = "exercise_save"
 
511
    __storm_primary__ = "ws_ex_id", "user_id"
 
512
 
 
513
    ws_ex_id = Int(name="ws_ex_id")
 
514
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
515
 
 
516
    user_id = Int(name="loginid")
 
517
    user = Reference(user_id, User.id)
 
518
    date = DateTime()
 
519
    text = Unicode()
 
520
 
 
521
    __init__ = _kwarg_init
 
522
 
 
523
    def __repr__(self):
 
524
        return "<%s %s by %s at %s>" % (type(self).__name__,
 
525
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
526
 
 
527
class ExerciseAttempt(ExerciseSave):
 
528
    """
 
529
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
 
530
    ExerciseSave, it constitutes exercise solution data that the user has
 
531
    submitted to the server for storage.
 
532
    In addition, it contains additional information about the submission.
 
533
    complete - True if this submission was successful, rendering this exercise
 
534
        complete for this user.
 
535
    active - True if this submission is "active" (usually true). Submissions
 
536
        may be de-activated by privileged users for special reasons, and then
 
537
        they won't count (either as a penalty or success), but will still be
 
538
        stored.
 
539
    """
 
540
    __storm_table__ = "exercise_attempt"
 
541
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
542
 
 
543
    # The "text" field is the same but has a different name in the DB table
 
544
    # for some reason.
 
545
    text = Unicode(name="attempt")
 
546
    complete = Bool()
 
547
    active = Bool()
 
548
    
 
549
    def get_permissions(self, user):
 
550
        return set(['view']) if user is self.user else set()
 
551
  
 
552
class TestSuite(Storm):
 
553
    """A Testsuite acts as a container for the test cases of an exercise."""
 
554
    __storm_table__ = "test_suite"
 
555
    __storm_primary__ = "exercise_id", "suiteid"
 
556
    
 
557
    suiteid = Int()
 
558
    exercise_id = Unicode(name="exerciseid")
 
559
    description = Unicode()
 
560
    seq_no = Int()
 
561
    function = Unicode()
 
562
    stdin = Unicode()
 
563
    exercise = Reference(exercise_id, Exercise.id)
 
564
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
 
565
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
566
 
 
567
class TestCase(Storm):
 
568
    """A TestCase is a member of a TestSuite.
 
569
    
 
570
    It contains the data necessary to check if an exercise is correct"""
 
571
    __storm_table__ = "test_case"
 
572
    __storm_primary__ = "testid", "suiteid"
 
573
    
 
574
    testid = Int()
 
575
    suiteid = Int()
 
576
    suite = Reference(suiteid, "TestSuite.suiteid")
 
577
    passmsg = Unicode()
 
578
    failmsg = Unicode()
 
579
    test_default = Unicode()
 
580
    seq_no = Int()
 
581
    
 
582
    parts = ReferenceSet(testid, "TestCasePart.testid")
 
583
    
 
584
    __init__ = _kwarg_init
 
585
 
 
586
class TestSuiteVar(Storm):
 
587
    """A container for the arguments of a Test Suite"""
 
588
    __storm_table__ = "suite_variable"
 
589
    __storm_primary__ = "varid"
 
590
    
 
591
    varid = Int()
 
592
    suiteid = Int()
 
593
    var_name = Unicode()
 
594
    var_value = Unicode()
 
595
    var_type = Unicode()
 
596
    arg_no = Int()
 
597
    
 
598
    suite = Reference(suiteid, "TestSuite.suiteid")
 
599
    
 
600
    __init__ = _kwarg_init
 
601
    
 
602
class TestCasePart(Storm):
 
603
    """A container for the test elements of a Test Case"""
 
604
    __storm_table__ = "test_case_part"
 
605
    __storm_primary__ = "partid"
 
606
    
 
607
    partid = Int()
 
608
    testid = Int()
 
609
    
 
610
    part_type = Unicode()
 
611
    test_type = Unicode()
 
612
    data = Unicode()
 
613
    filename = Unicode()
 
614
    
 
615
    test = Reference(testid, "TestCase.testid")
 
616
    
 
617
    __init__ = _kwarg_init