schemas.py 18 KB
Newer Older
Peter Stanko's avatar
Peter Stanko committed
1
2
3
"""
Schemas used to serialize/deserialize and validate of the models in the DB
"""
Peter Stanko's avatar
Peter Stanko committed
4
import functools
5
from typing import List
Peter Stanko's avatar
Peter Stanko committed
6

Peter Stanko's avatar
Peter Stanko committed
7
from marshmallow import Schema, ValidationError, fields, validates_schema
8
9
from marshmallow_enum import EnumField

10
11
12
13
from portal.database.models import Client, Course, Group, Project, \
    ReviewItem, Role, Secret, Submission, User, Worker, Review
from portal.database import ProjectState, SubmissionState
from portal.database.enums import ClientType, CourseState, WorkerState
Barbora Kompišová's avatar
Barbora Kompišová committed
14

15

16
17
18
19
def _in(prefix, params) -> List[str]:
    return [f'{prefix}.{p}' for p in params]


20
class NestedCollection:
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    USERS_NESTED = _in('user', ['username', 'codename', 'name', 'uco', 'id'])
    COURSE_NESTED = _in('course', Course.listable_params())
    NESTED_REPR = dict(
        Role=Role.listable_params() + COURSE_NESTED,
        Group=Group.listable_params() + COURSE_NESTED,
        Project=Project.listable_params() + COURSE_NESTED,
        Course=Course.listable_params(),
        User=User.listable_params(),
        Worker=Worker.listable_params(),
        Client=Client.listable_params(),
        Submission=[*Submission.listable_params(), *_in('project', Project.listable_params()),
                    *COURSE_NESTED, *USERS_NESTED],
        ReviewItem=ReviewItem.listable_params() + USERS_NESTED + _in('review', 'id'),
        Review=Review.base_params(),
        Secret=Secret.listable_params()
    )
Peter Stanko's avatar
Peter Stanko committed
37

38
    def __init__(self, mod_name: str):
39
40
        self.mod_name = mod_name
        self.collection = {}
Peter Stanko's avatar
Peter Stanko committed
41
        self.__build_nested()
42
43
44
45
46
47
48
49

    def class_path(self, schema_name: str):
        return f'{self.mod_name}.{schema_name}Schema'

    def __call__(self, *args, **kwargs):
        return self.nested(*args, **kwargs)

    def nested(self, schema_name, *args, only=None, **kwargs):
Peter Stanko's avatar
Peter Stanko committed
50
51
52
53
54
55
56
57
58
        params = []
        if only:
            params.extend(only)
        if args:
            params.extend(args)
        other = {**kwargs}
        if params:
            other['only'] = params
        return fields.Nested(self.class_path(schema_name), **other)
59
60
61
62

    def _set_nested(self, schema_name: str, params, many=False):
        lower = schema_name.lower()
        if many:
Peter Stanko's avatar
Peter Stanko committed
63
            lower = lower + 's'
Peter Stanko's avatar
Peter Stanko committed
64
        self.collection[lower] = self.nested(schema_name, *params, many=many)
65
66
67

    def _set_nested_and_list(self, schema_name, params):
        self._set_nested(schema_name=schema_name, params=params)
Peter Stanko's avatar
Peter Stanko committed
68
        # List version
69
70
71
        self._set_nested(schema_name=schema_name, params=params, many=True)

    def __build_nested(self):
Peter Stanko's avatar
Peter Stanko committed
72
        for (name, params) in self.entities.items():
73
74
75
76
            self._set_nested_and_list(name, params=params)

    @property
    def entities(self):
77
        return self.__class__.NESTED_REPR
78
79
80
81
82
83
84
85
86
87
88

    def get(self, name):
        return self.collection[name]

    def __getitem__(self, item):
        return self.get(item)


NESTED = NestedCollection(__name__)


Peter Stanko's avatar
Peter Stanko committed
89
# pylint: disable=too-few-public-methods
90
91
class BaseSchema(object):
    id = fields.Str(dump_only=True, required=True)
92
93
    created_at = fields.LocalDateTime(dump_only=True, allow_none=True)
    updated_at = fields.LocalDateTime(dump_only=True, allow_none=True)
94
95


Peter Stanko's avatar
Peter Stanko committed
96
97
98
99
class NamedSchema(object):
    name = fields.Str(required=True)
    codename = fields.Str(required=True)
    description = fields.Str(required=False, allow_none=True)
100
    namespace = fields.Str(required=False, allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
101
102


103
class UserSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
104
105
    """User schema
    """
106
107
    uco = fields.Int(required=True)
    email = fields.Email(required=True)
108
    username = fields.Str(required=True)
109
    gitlab_username = fields.Str()
110
    name = fields.Str()
111
    codename = fields.Str()
Peter Stanko's avatar
Peter Stanko committed
112
    type = fields.Str()
113
    is_admin = fields.Bool(default=False, missing=False)
114
    managed = fields.Bool(default=False, missing=False)
Peter Stanko's avatar
Peter Stanko committed
115
116
117
118
    submissions = NESTED['submissions']
    review_items = NESTED['reviewitems']
    roles = NESTED['roles']
    groups = NESTED['groups']
119
120
121


class PasswordChangeSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
122
123
    """Password Change Schema
    """
124
125
    old_password = fields.Str()
    new_password = fields.Str(required=True)
126
127


Peter Stanko's avatar
Peter Stanko committed
128
class CourseSchema(BaseSchema, NamedSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
129
130
    """Course Schema
    """
131
    state = EnumField(CourseState, by_value=True)
132
    faculty_id = fields.Int(required=False, allow_none=True)
133
134
135
    roles = NESTED['roles']
    groups = NESTED['groups']
    projects = NESTED['projects']
136
137
138


class SubmissionFileSourceSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
139
140
    """Submissions File Source Schema
    """
Peter Stanko's avatar
Peter Stanko committed
141
    type = fields.Str(required=True)
Peter Stanko's avatar
Peter Stanko committed
142
143
    url = fields.Str(allow_none=True)  # git@gitlab.fi.muni.cz:foo/project.git
    branch = fields.Str(allow_none=True)
144
145
146
147
148
149
150
151
152
153
154
    checkout = fields.Str()

    @validates_schema
    def validate_present_fields(self, data):
        if data.get('type') is None:
            raise ValidationError(
                f"Submission source type is required. Supported values: 'git', 'zip'.")
        if data['type'] not in ('git', 'zip'):
            raise ValidationError(
                f"Submission source unsupported: {data['type']}. Supported sources: git, zip.")
        if data['type'] == 'git':
Peter Stanko's avatar
Peter Stanko committed
155
156
            if any(key not in data.keys()
                   for key in ['url', 'branch', 'checkout']):
157
                raise ValidationError(
Peter Stanko's avatar
Peter Stanko committed
158
                    f"File source 'git' require precisely 'url', "
159
                    f"'branch' and 'checkout' attributes."
160
                )
161
        if data['type'] == 'zip':
Peter Stanko's avatar
Peter Stanko committed
162
163
164
165
            if any(key in data.keys()
                   for key in ['url', 'branch', 'checkout']):
                raise ValidationError(
                    f"File source 'zip' does not require any other attributes.")
166
167


Peter Stanko's avatar
Peter Stanko committed
168
169
170
171
class SubmissionFileParamsSchema(Schema):
    """Submission File Params Schema
    """
    source = NESTED('SubmissionFileSource', required=True)
Peter Stanko's avatar
Peter Stanko committed
172
    from_dir = fields.Str(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
173
174


175
class SubmissionCreateSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
176
177
    """Submission Create Schema
    """
Peter Stanko's avatar
Peter Stanko committed
178
    file_params = NESTED('SubmissionFileParams', required=True)
Peter Stanko's avatar
Peter Stanko committed
179
    project_params = fields.Dict(required=False, allow_none=True)
180
181


Peter Stanko's avatar
Peter Stanko committed
182
class ProjectSchema(BaseSchema, NamedSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
183
184
    """Project Schema
    """
185
    assignment_url = fields.Str(required=False, allow_none=True)
186
    submit_instructions = fields.Str(required=False, allow_none=True)
187
    submit_configurable = fields.Bool(required=False, allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
188
189
190
    config = NESTED('ProjectConfig',
                    exclude=('id', '_submissions_allowed_from',
                             '_submissions_allowed_to', '_archive_from'))
191
    state = EnumField(ProjectState)
192
193
194
    course = NESTED['course']
    submissions = NESTED['submissions']
    groups = NESTED['groups']
195

196

197
class ProjectConfigSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
198
199
    """Project config Schema
    """
200
    project = NESTED['project']
201
202
    submissions_cancellation_period = fields.Number(allow_none=True)
    test_files_source = fields.Str(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
203
    test_files_subdir = fields.Str(allow_none=True)
204
205
206
207
208
209
210
211
    file_whitelist = fields.Str(allow_none=True)
    pre_submit_script = fields.Str(allow_none=True)
    post_submit_script = fields.Str(allow_none=True)
    submission_parameters = fields.Str(allow_none=True)
    submission_scheduler_config = fields.Str(allow_none=True)
    submissions_allowed_from = fields.LocalDateTime(allow_none=True)
    submissions_allowed_to = fields.LocalDateTime(allow_none=True)
    archive_from = fields.LocalDateTime(allow_none=True)
212
213


Peter Stanko's avatar
Peter Stanko committed
214
class RoleSchema(BaseSchema, NamedSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
215
216
    """Role Schema
    """
Barbora Kompisova's avatar
Barbora Kompisova committed
217
    clients = NESTED['clients']
218
    course = NESTED['course']
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    view_course_limited = fields.Bool()
    view_course_full = fields.Bool()
    update_course = fields.Bool()
    handle_notes_access_token = fields.Bool()

    write_roles = fields.Bool()
    write_groups = fields.Bool()
    write_projects = fields.Bool()
    archive_projects = fields.Bool()

    create_submissions = fields.Bool()
    create_submissions_other = fields.Bool()
    resubmit_submissions = fields.Bool()
    evaluate_submissions = fields.Bool()

    read_submissions_all = fields.Bool()
    read_submissions_groups = fields.Bool()
    read_submissions_own = fields.Bool()
    read_all_submission_files = fields.Bool()

    read_reviews_all = fields.Bool()
    read_reviews_groups = fields.Bool()
    read_reviews_own = fields.Bool()

    write_reviews_all = fields.Bool()
    write_reviews_group = fields.Bool()
    write_reviews_own = fields.Bool()
246
247


Barbora Kompisova's avatar
Barbora Kompisova committed
248
class ClientSchema(BaseSchema, Schema):
Peter Stanko's avatar
Peter Stanko committed
249
    type = EnumField(ClientType, by_value=True)
Barbora Kompisova's avatar
Barbora Kompisova committed
250
251


252
class RolePermissionsSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
253
254
    """Role Permissions Schema
    """
255
256
257
258
259
260
261
262
263
264
265
    view_course_limited = fields.Bool()
    view_course_full = fields.Bool()
    update_course = fields.Bool()
    handle_notes_access_token = fields.Bool()

    write_roles = fields.Bool()
    write_groups = fields.Bool()
    write_projects = fields.Bool()
    archive_projects = fields.Bool()

    create_submissions = fields.Bool()
266
    create_submissions_other = fields.Bool()
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
    resubmit_submissions = fields.Bool()
    evaluate_submissions = fields.Bool()

    read_submissions_all = fields.Bool()
    read_submissions_groups = fields.Bool()
    read_submissions_own = fields.Bool()
    read_all_submission_files = fields.Bool()

    read_reviews_all = fields.Bool()
    read_reviews_groups = fields.Bool()
    read_reviews_own = fields.Bool()

    write_reviews_all = fields.Bool()
    write_reviews_group = fields.Bool()
    write_reviews_own = fields.Bool()


Peter Stanko's avatar
Peter Stanko committed
284
class GroupSchema(BaseSchema, NamedSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
285
286
    """Group Schema
    """
287
288
289
    course = NESTED['course']
    users = NESTED['users']
    projects = NESTED['projects']
290
291


292
class SubmissionSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
293
294
    """Submission Schema
    """
295
296
297
298
    note = fields.Str()
    state = EnumField(SubmissionState)
    scheduled_for = fields.LocalDateTime()
    parameters = fields.Dict()
299
    project = NESTED['project']
300
    course = NESTED['course']
301
    user = NESTED['user']
302
303
    points = fields.Number()
    result = fields.Str(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
304
    source_hash = fields.Str(allow_none=True)
305
306


307
class ReviewSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
308
309
    """Review Schema
    """
310
311
    submission = NESTED['submission']
    review_items = NESTED['reviewitems']
312
313


314
class ReviewItemSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
315
316
    """Review Item Schema
    """
317
    content = fields.Str()
318
319
320
321
    file = fields.Str(allow_none=True)
    line = fields.Int(allow_none=True)
    line_start = fields.Int(allow_none=True)
    line_end = fields.Int(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
322
    review = NESTED('Review', only=('id', 'submission.id'))
323
    user = NESTED['user']
324
325


Barbora Kompišová's avatar
Barbora Kompišová committed
326
class WorkerSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
327
328
    """Component Schema
    """
329
    name = fields.Str()
330
    codename = fields.Str(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
331
    type = fields.Str()
Peter Stanko's avatar
Peter Stanko committed
332
333
334
    url = fields.Str(allow_none=True)
    tags = fields.Str(allow_none=True)
    portal_secret = fields.Str(allow_none=True)
Peter Stanko's avatar
Peter Stanko committed
335
    state = EnumField(WorkerState, by_value=True)
Barbora Kompišová's avatar
Barbora Kompišová committed
336
337


338
class SecretSchema(BaseSchema, Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
339
340
341
    """Secret schema
    """
    name = fields.Str()
342
    expires_at = fields.LocalDateTime(allow_none=True)
343
    expired = fields.Boolean
Barbora Kompišová's avatar
Barbora Kompišová committed
344
    client = NESTED['client']
345
346
347


class CourseImportConfigSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
348
349
    """Course Import Config Schema
    """
350
351
352
353
354
355
    roles = fields.Str()
    groups = fields.Str()
    projects = fields.Str()


class CourseImportSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
356
357
    """Course Import Schema
    """
358
    source_course = fields.Str(required=True)
Peter Stanko's avatar
Peter Stanko committed
359
    config = NESTED('CourseImportConfig', required=True)
360
361


Barbora Kompisova's avatar
Barbora Kompisova committed
362
class ClientListUpdateSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
363
364
    """User List Update Schema
    """
365
366
367
368
369
    add = fields.List(fields.Str())
    remove = fields.List(fields.Str())


class GroupImportSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
370
371
    """Group Import Schema
    """
372
373
374
375
376
    source_course = fields.Str()
    source_group = fields.Str()
    with_users = fields.Str()


Peter Stanko's avatar
Peter Stanko committed
377
378
379
380
381
382
383
384
385
386
387
class RoleImportSchema(Schema):
    source_course = fields.Str()
    source_role = fields.Str()
    with_users = fields.Str()


class ProjectImportSchema(Schema):
    source_course = fields.Str()
    source_project = fields.Str()


388
class SubmissionResultTemplateSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
389
390
    """Submission Result Template Schema
    """
391
392
393
394
395
396
    template = fields.Str(required=True)
    role = fields.Str()
    user = fields.Str()


class SubmissionResultSchema(Schema):
Barbora Kompišová's avatar
Barbora Kompišová committed
397
398
    """Submission Result Schema
    """
399
400
401
    data = fields.Dict(required=True)
    templates = fields.List(
        fields.Nested(SubmissionResultTemplateSchema, required=True)
402
    )
403
404


Peter Stanko's avatar
Peter Stanko committed
405
406
# pylint: enable=too-few-public-methods

Peter Stanko's avatar
Peter Stanko committed
407
408
409
410
411
412
413
414
415
416
417
418
419
420

def fn_name(func):
    @functools.wraps(func)
    def __wrap(*args, **kwargs):
        return func(*args, select_params=func.__name__, **kwargs)

    return __wrap


class Schemas:
    ALWAYS_ALLOWED = ['updated_at', 'created_at', 'id']
    CODENAME_W_DESC = [*ALWAYS_ALLOWED, 'name', 'codename', 'description']
    ENT_W_COURSE = (*CODENAME_W_DESC, 'course')
    PARAMS = {
421
422
423
424
425
426
427
428
429
        'users': User.base_params(),
        'clients': Client.base_params(),
        'submissions': [*Submission.base_params(), 'course', 'project', 'user'],
        'submission_state': Submission.base_params(),
        'roles': [*Role.base_params(), 'course'],
        'groups': [*Group.base_params(), 'course'],
        'projects': [*Project.base_params(), 'course'],
        'course_reduced': Course.base_params(),
        'courses': Course.base_params(),
Peter Stanko's avatar
Peter Stanko committed
430
431
        'config_reduced': (*ALWAYS_ALLOWED, 'project', 'submissions_allowed_from',
                           'submissions_allowed_to', 'file_whitelist'),
432
433
        'secrets': (*Secret.base_params(), *_in('client', ['id', 'type', 'name', 'codename'])),
        'secret': (*Secret.base_params(), *_in('client', ['id', 'type', 'name', 'codename']))
Peter Stanko's avatar
Peter Stanko committed
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
    }

    def __get_schema(self, schema_klass, select_params=None, only=None, strict=True, **kwargs):
        if only is None:
            only = self._select_params(select_params)
        params = {'strict': strict, **kwargs}
        if only is not None:
            params['only'] = only
        return schema_klass(**params)

    def _select_params(self, select_params):
        return self.__class__.PARAMS.get(select_params)

    @fn_name
    def user(self, **kwargs):
        return self.__get_schema(UserSchema, **kwargs)

    @fn_name
    def users(self, **kwargs):
        return self.__get_schema(UserSchema, many=True, **kwargs)

455
456
457
458
459
460
461
462
    @fn_name
    def client(self, **kwargs):
        return self.__get_schema(ClientSchema, **kwargs)

    @fn_name
    def clients(self, **kwargs):
        return self.__get_schema(ClientSchema, many=True, **kwargs)

Peter Stanko's avatar
Peter Stanko committed
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
    @fn_name
    def password_change(self, **kwargs):
        return self.__get_schema(PasswordChangeSchema, **kwargs)

    @fn_name
    def submission(self, **kwargs):
        return self.__get_schema(SubmissionSchema, **kwargs)

    @fn_name
    def submissions(self, **kwargs):
        return self.__get_schema(SubmissionSchema, many=True, **kwargs)

    @fn_name
    def submission_create(self, **kwargs):
        return self.__get_schema(SubmissionCreateSchema, strict=False, **kwargs)

    @fn_name
    def submission_state(self, **kwargs):
        return self.__get_schema(SubmissionSchema, **kwargs)

    @fn_name
    def reviews(self, **kwargs):
        return self.__get_schema(ReviewSchema, many=True, **kwargs)

    @fn_name
    def review(self, **kwargs):
        return self.__get_schema(ReviewSchema, **kwargs)

491
492
493
494
495
496
497
498
    @fn_name
    def review_items(self, **kwargs):
        return self.__get_schema(ReviewItemSchema, many=True, **kwargs)

    @fn_name
    def review_item(self, **kwargs):
        return self.__get_schema(ReviewItemSchema, **kwargs)

Peter Stanko's avatar
Peter Stanko committed
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
    @fn_name
    def courses(self, **kwargs):
        return self.__get_schema(CourseSchema, many=True, **kwargs)

    @fn_name
    def course(self, **kwargs):
        return self.__get_schema(CourseSchema, **kwargs)

    @fn_name
    def course_reduced(self, **kwargs):
        return self.__get_schema(CourseSchema, **kwargs)

    @fn_name
    def roles(self, **kwargs):
        return self.__get_schema(RoleSchema, many=True, **kwargs)

    @fn_name
    def role(self, **kwargs):
        return self.__get_schema(RoleSchema, **kwargs)

    @fn_name
    def group(self, **kwargs):
        return self.__get_schema(GroupSchema, **kwargs)

    @fn_name
    def groups(self, **kwargs):
        return self.__get_schema(GroupSchema, many=True, **kwargs)

    @fn_name
    def project(self, **kwargs):
        return self.__get_schema(ProjectSchema, **kwargs)

    @fn_name
    def projects(self, **kwargs):
        return self.__get_schema(ProjectSchema, many=True, **kwargs)

    @fn_name
    def permissions(self, **kwargs):
        return self.__get_schema(RolePermissionsSchema, **kwargs)

    @fn_name
    def config(self, **kwargs):
        return self.__get_schema(ProjectConfigSchema, **kwargs)

    @fn_name
    def config_reduced(self, **kwargs) -> ProjectConfigSchema:
        return self.__get_schema(ProjectConfigSchema, **kwargs)

    @fn_name
    def client_list_update(self, **kwargs) -> ClientListUpdateSchema:
        return self.__get_schema(ClientListUpdateSchema, **kwargs)

    @fn_name
    def group_import(self, **kwargs) -> GroupImportSchema:
        return self.__get_schema(GroupImportSchema, **kwargs)

    @fn_name
    def role_import(self, **kwargs) -> RoleImportSchema:
        return self.__get_schema(RoleImportSchema, **kwargs)

    @fn_name
    def project_import(self, **kwargs) -> ProjectImportSchema:
        return self.__get_schema(ProjectImportSchema, **kwargs)

    @fn_name
    def course_import(self, **kwargs) -> CourseImportSchema:
        return self.__get_schema(CourseImportSchema, **kwargs)

    @fn_name
    def worker(self, **kwargs) -> ProjectImportSchema:
        return self.__get_schema(WorkerSchema, **kwargs)

    @fn_name
    def workers(self, **kwargs) -> WorkerSchema:
        return self.__get_schema(WorkerSchema, many=True, **kwargs)

    @fn_name
    def submission_result(self, **kwargs) -> SubmissionResultSchema:
        return self.__get_schema(SubmissionResultSchema, **kwargs)

    @fn_name
    def secret(self, **kwargs) -> SecretSchema:
        return self.__get_schema(SecretSchema, **kwargs)

    @fn_name
    def secrets(self, **kwargs) -> SecretSchema:
        return self.__get_schema(SecretSchema, many=True, **kwargs)

    def dump(self, schema_name: str, obj: object) -> dict:
        schema: Schema = getattr(self, schema_name)()
        return schema.dump(obj)[0]


SCHEMAS = Schemas()