vtjson 2.1.7__py3-none-any.whl → 2.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vtjson/vtjson.py CHANGED
@@ -12,7 +12,17 @@ import urllib.parse
12
12
  import warnings
13
13
  from collections.abc import Sequence, Set, Sized
14
14
  from dataclasses import dataclass
15
- from typing import Any, Callable, Container, Mapping, Type, TypeVar, Union, cast
15
+ from typing import (
16
+ Any,
17
+ Callable,
18
+ Container,
19
+ Generic,
20
+ Mapping,
21
+ Type,
22
+ TypeVar,
23
+ Union,
24
+ cast,
25
+ )
16
26
 
17
27
  try:
18
28
  from typing import Literal
@@ -78,23 +88,88 @@ import idna
78
88
  T = TypeVar("T")
79
89
 
80
90
 
81
- def safe_cast(t: Type[T], object_: Any) -> T:
82
- validate(t, object_)
83
- return cast(T, object_)
91
+ def safe_cast(schema: Type[T], obj: Any) -> T:
92
+ """
93
+ Validates the given object against the given schema and changes its type
94
+ accordingly.
95
+
96
+ :param schema: the given schema
97
+ :param obj: the object to be validated
98
+ :return: the validated object with its type changed
99
+
100
+ :raises ValidationError: exception thrown when the object does not
101
+ validate; the exception message contains an explanation about what went
102
+ wrong
103
+
104
+ :raises SchemaError: exception thrown when the schema definition is found
105
+ to contain an error
106
+ """
107
+
108
+ validate(schema, obj)
109
+ return cast(T, obj)
84
110
 
85
111
 
86
112
  class compiled_schema:
113
+ """
114
+ The result of compiling a schema. A :py:class:`compiled_schema` is
115
+ produced by the factory function :py:func:`vtjson.compile`.
116
+ """
117
+
87
118
  def __validate__(
88
119
  self,
89
- object_: object,
120
+ obj: object,
90
121
  name: str,
91
122
  strict: bool,
92
123
  subs: Mapping[str, object],
93
124
  ) -> str:
125
+ """
126
+ Validates the given object against the given schema.
127
+
128
+ :param schema: the given schema
129
+ :param obj: the object to be validated
130
+ :param name: common name for the object to be validated; used in
131
+ non-validation messages
132
+ :param strict: indicates whether or not the object being validated is
133
+ allowed to have keys/entries which are not in the schema
134
+ :param subs: a dictionary whose keys are labels and whose values are
135
+ substitution schemas for schemas with those labels
136
+ :return: an empty string if validation succeeds; otherwise an
137
+ explanation about what went wrong
138
+ """
139
+
94
140
  return ""
95
141
 
96
142
 
143
+ class wrapper:
144
+ """
145
+ Base class for schemas that refer to other schemas.
146
+
147
+ Handling such schemas is somewhat delicate since `vtjson` allows them to
148
+ be recursive.
149
+ """
150
+
151
+ def __compile__(
152
+ self, _deferred_compiles: _mapping | None = None
153
+ ) -> compiled_schema:
154
+ """
155
+ Compiles a schema.
156
+
157
+ :param _deferred_compiles: an opaque data structure used for handling
158
+ recursive schemas; it should be passed unmodifed to any internal
159
+ invocations of :py:func:`vtjson._compile` or
160
+ :py:meth:`vtjson.wrapper.__compile__`
161
+
162
+ :raises SchemaError: exception thrown when the schema definition is
163
+ found to contain an error
164
+ """
165
+ return anything()
166
+
167
+
97
168
  class comparable(Protocol):
169
+ """
170
+ Base class for objects that are comparable with each other.
171
+ """
172
+
98
173
  def __eq__(self, x: Any) -> bool: ...
99
174
 
100
175
  def __lt__(self, x: Any) -> bool: ...
@@ -114,18 +189,39 @@ except Exception:
114
189
 
115
190
 
116
191
  class ValidationError(Exception):
192
+ """
193
+ Raised if validation fails. The associated message explains what went
194
+ wrong.
195
+ """
196
+
117
197
  pass
118
198
 
119
199
 
120
200
  class SchemaError(Exception):
201
+ """
202
+ Raised if a schema contains an error.
203
+ """
204
+
121
205
  pass
122
206
 
123
207
 
124
- __version__ = "2.1.7"
208
+ __version__ = "2.1.9"
125
209
 
126
210
 
127
211
  @dataclass
128
212
  class Apply:
213
+ """
214
+ Modifies the treatement of the previous arguments in an `Annotated` schema.
215
+
216
+ :param skip_first: if `True` do not use the first argument (the Python
217
+ type annotation) in an `Annotated` construct for validation because this
218
+ is already covered by the other arguments
219
+ :param name: apply the corresponding :py:class:`vtjson.set_name` command
220
+ to the previous arguments
221
+ :param labels: apply the corresponding :py:class:`vtjson.set_label`
222
+ command to the previous arguments
223
+ """
224
+
129
225
  skip_first: bool | None = None
130
226
  name: str | None = None
131
227
  labels: Sequence[str] | None = None
@@ -177,13 +273,13 @@ def _get_type_hints(schema: object) -> dict[str, object]:
177
273
 
178
274
  def _to_dict(
179
275
  type_hints: Mapping[str, object], total: bool = True
180
- ) -> dict[object, object]:
181
- d: dict[object, object] = {}
276
+ ) -> dict[str | optional_key[str], object]:
277
+ d: dict[str | optional_key[str], object] = {}
182
278
  if not supports_Generics:
183
279
  raise SchemaError("Generic types are not supported")
184
280
  for k, v in type_hints.items():
185
281
  v_ = v
186
- k_: str | optional_key = k
282
+ k_: str | optional_key[str] = k
187
283
  value_type = typing.get_origin(v)
188
284
  if supports_NotRequired and value_type in (Required, NotRequired):
189
285
  v_ = typing.get_args(v)[0]
@@ -225,14 +321,14 @@ def _c(s: object) -> str:
225
321
 
226
322
 
227
323
  def _wrong_type_message(
228
- object_: object,
324
+ obj: object,
229
325
  name: str,
230
326
  type_name: str,
231
327
  explanation: str | None = None,
232
328
  skip_value: bool = False,
233
329
  ) -> str:
234
330
  if not skip_value:
235
- message = f"{name} (value:{_c(object_)}) is not of type '{type_name}'"
331
+ message = f"{name} (value:{_c(obj)}) is not of type '{type_name}'"
236
332
  else:
237
333
  message = f"{name} is not of type '{type_name}'"
238
334
  if explanation is not None:
@@ -246,9 +342,9 @@ class _validate_meta(type):
246
342
  __subs__: Mapping[str, object]
247
343
  __dbg__: bool
248
344
 
249
- def __instancecheck__(cls, object_: object) -> bool:
345
+ def __instancecheck__(cls, obj: object) -> bool:
250
346
  valid = _validate(
251
- cls.__schema__, object_, "object", strict=cls.__strict__, subs=cls.__subs__
347
+ cls.__schema__, obj, "object", strict=cls.__strict__, subs=cls.__subs__
252
348
  )
253
349
  if cls.__dbg__ and valid != "":
254
350
  print(f"DEBUG: {valid}")
@@ -262,6 +358,20 @@ def make_type(
262
358
  debug: bool = False,
263
359
  subs: Mapping[str, object] = {},
264
360
  ) -> _validate_meta:
361
+ """
362
+ Transforms a schema into a genuine Python type.
363
+
364
+ :param schema: the given schema
365
+ :param name: sets the `__name__` attribute of the type; if it is not
366
+ supplied then `vtjson` makes an educated guess
367
+ :param strict: indicates whether or not the object being validated is
368
+ allowed to have keys/entries which are not in the schema
369
+ :param debug: print feedback on the console if validation fails
370
+ :param subs: a dictionary whose keys are labels and whose values are
371
+ substitution schemas for schemas with those labels
372
+ :raises SchemaError: exception thrown when the schema definition is found
373
+ to contain an error
374
+ """
265
375
  if name is None:
266
376
  if hasattr(schema, "__name__"):
267
377
  name = schema.__name__
@@ -279,16 +389,28 @@ def make_type(
279
389
  )
280
390
 
281
391
 
282
- class optional_key:
283
- key: object
392
+ K = TypeVar("K")
393
+
394
+
395
+ class optional_key(Generic[K]):
396
+ """
397
+ Make a key in a Mapping schema optional. In the common case that the key
398
+ is a string, the same effect can be achieved by appending `?`.
399
+ """
284
400
 
285
- def __init__(self, key: object) -> None:
401
+ key: K
402
+
403
+ def __init__(self, key: K) -> None:
404
+ """
405
+ :param key: the key to be made optional
406
+ """
286
407
  self.key = key
287
408
 
288
409
  def __eq__(self, key: object) -> bool:
289
410
  if not isinstance(key, optional_key):
290
411
  return False
291
- return self.key == key.key
412
+ k: object = key.key
413
+ return self.key == k
292
414
 
293
415
  def __hash__(self) -> int:
294
416
  return hash(self.key)
@@ -308,14 +430,14 @@ class _union(compiled_schema):
308
430
 
309
431
  def __validate__(
310
432
  self,
311
- object_: object,
433
+ obj: object,
312
434
  name: str = "object",
313
435
  strict: bool = True,
314
436
  subs: Mapping[str, object] = {},
315
437
  ) -> str:
316
438
  messages = []
317
439
  for schema in self.schemas:
318
- message = schema.__validate__(object_, name=name, strict=strict, subs=subs)
440
+ message = schema.__validate__(obj, name=name, strict=strict, subs=subs)
319
441
  if message == "":
320
442
  return ""
321
443
  else:
@@ -323,10 +445,18 @@ class _union(compiled_schema):
323
445
  return " and ".join(messages)
324
446
 
325
447
 
326
- class union:
448
+ class union(wrapper):
449
+ """
450
+ An object matches the schema `union(schema1, ..., schemaN)` if it matches
451
+ one of the schemas `schema1, ..., schemaN`.
452
+ """
453
+
327
454
  schemas: tuple[object, ...]
328
455
 
329
456
  def __init__(self, *schemas: object) -> None:
457
+ """
458
+ :param schemas: collection of schemas, one of which should match
459
+ """
330
460
  self.schemas = schemas
331
461
 
332
462
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _union:
@@ -347,22 +477,30 @@ class _intersect(compiled_schema):
347
477
 
348
478
  def __validate__(
349
479
  self,
350
- object_: object,
480
+ obj: object,
351
481
  name: str = "object",
352
482
  strict: bool = True,
353
483
  subs: Mapping[str, object] = {},
354
484
  ) -> str:
355
485
  for schema in self.schemas:
356
- message = schema.__validate__(object_, name=name, strict=strict, subs=subs)
486
+ message = schema.__validate__(obj, name=name, strict=strict, subs=subs)
357
487
  if message != "":
358
488
  return message
359
489
  return ""
360
490
 
361
491
 
362
- class intersect:
492
+ class intersect(wrapper):
493
+ """
494
+ An object matches the schema `intersect(schema1, ..., schemaN)` if it
495
+ matches all the schemas `schema1, ..., schemaN`.
496
+ """
497
+
363
498
  schemas: tuple[object, ...]
364
499
 
365
500
  def __init__(self, *schemas: object) -> None:
501
+ """
502
+ :param schemas: collection of schemas, all of which should match
503
+ """
366
504
  self.schemas = schemas
367
505
 
368
506
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _intersect:
@@ -379,22 +517,30 @@ class _complement(compiled_schema):
379
517
 
380
518
  def __validate__(
381
519
  self,
382
- object_: object,
520
+ obj: object,
383
521
  name: str = "object",
384
522
  strict: bool = True,
385
523
  subs: Mapping[str, object] = {},
386
524
  ) -> str:
387
- message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
525
+ message = self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
388
526
  if message != "":
389
527
  return ""
390
528
  else:
391
529
  return f"{name} does not match the complemented schema"
392
530
 
393
531
 
394
- class complement:
532
+ class complement(wrapper):
533
+ """
534
+ An object matches the schema `complement(schema)` if it does not match
535
+ `schema`.
536
+ """
537
+
395
538
  schema: object
396
539
 
397
540
  def __init__(self, schema: object) -> None:
541
+ """
542
+ :param schema: the schema that should not be matched
543
+ """
398
544
  self.schema = schema
399
545
 
400
546
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _complement:
@@ -411,18 +557,27 @@ class _lax(compiled_schema):
411
557
 
412
558
  def __validate__(
413
559
  self,
414
- object_: object,
560
+ obj: object,
415
561
  name: str = "object",
416
562
  strict: bool = True,
417
563
  subs: Mapping[str, object] = {},
418
564
  ) -> str:
419
- return self.schema.__validate__(object_, name=name, strict=False, subs=subs)
565
+ return self.schema.__validate__(obj, name=name, strict=False, subs=subs)
566
+
420
567
 
568
+ class lax(wrapper):
569
+ """
570
+ An object matches the schema `lax(schema)` if it matches `schema` when
571
+ validated with `strict=False`.
572
+ """
421
573
 
422
- class lax:
423
574
  schema: object
424
575
 
425
576
  def __init__(self, schema: object) -> None:
577
+ """
578
+ :param schema: schema that should be validated against with
579
+ `strict=False`
580
+ """
426
581
  self.schema = schema
427
582
 
428
583
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _lax:
@@ -439,16 +594,25 @@ class _strict(compiled_schema):
439
594
 
440
595
  def __validate__(
441
596
  self,
442
- object_: object,
597
+ obj: object,
443
598
  name: str = "object",
444
599
  strict: bool = True,
445
600
  subs: Mapping[str, object] = {},
446
601
  ) -> str:
447
- return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
602
+ return self.schema.__validate__(obj, name=name, strict=True, subs=subs)
603
+
448
604
 
605
+ class strict(wrapper):
606
+ """
607
+ An object matches the schema `strict(schema)` if it matches `schema` when
608
+ validated with `strict=True`.
609
+ """
449
610
 
450
- class strict:
451
611
  def __init__(self, schema: object) -> None:
612
+ """
613
+ :param schema: schema that should be validated against with
614
+ `strict=True`
615
+ """
452
616
  self.schema = schema
453
617
 
454
618
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _strict:
@@ -473,7 +637,7 @@ class _set_label(compiled_schema):
473
637
 
474
638
  def __validate__(
475
639
  self,
476
- object_: object,
640
+ obj: object,
477
641
  name: str = "object",
478
642
  strict: bool = True,
479
643
  subs: Mapping[str, object] = {},
@@ -492,17 +656,31 @@ class _set_label(compiled_schema):
492
656
  # known at schema creation time.
493
657
  #
494
658
  # But the user can always pre-compile subs[key].
495
- return _validate(subs[key], object_, name=name, strict=True, subs=subs)
659
+ return _validate(subs[key], obj, name=name, strict=True, subs=subs)
496
660
  else:
497
- return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
661
+ return self.schema.__validate__(obj, name=name, strict=True, subs=subs)
498
662
 
499
663
 
500
- class set_label:
664
+ class set_label(wrapper):
665
+ """
666
+ An object matches the schema `set_label(schema, label1, ..., labelN,
667
+ debug=False)` if it matches `schema`, unless the schema is replaced by a
668
+ different one via the `subs` argument to `validate`.
669
+ """
670
+
501
671
  schema: object
502
672
  labels: set[str]
503
673
  debug: bool
504
674
 
505
675
  def __init__(self, schema: object, *labels: str, debug: bool = False) -> None:
676
+ """
677
+ :param schema: the schema that will be labeled
678
+ :param labels: labels that will be attached to the schema
679
+ :param debug: it `True` print a message on the console if the schema
680
+ was changed via substitution
681
+ :raises SchemaError: exception thrown when the schema definition is
682
+ found to contain an error
683
+ """
506
684
  self.schema = schema
507
685
  for L in labels:
508
686
  if not isinstance(L, str):
@@ -519,19 +697,28 @@ class set_label:
519
697
 
520
698
 
521
699
  class quote(compiled_schema):
700
+ """
701
+ An object matches the schema `quote(schema)` if it is equal to `schema`.
702
+ For example the schema `str` matches strings but the schema `quote(str)`
703
+ matches the object `str`.
704
+ """
705
+
522
706
  schema: _const
523
707
 
524
708
  def __init__(self, schema: object) -> None:
709
+ """
710
+ :param schema: the schema to be quoted
711
+ """
525
712
  self.schema = _const(schema, strict_eq=True)
526
713
 
527
714
  def __validate__(
528
715
  self,
529
- object_: object,
716
+ obj: object,
530
717
  name: str = "object",
531
718
  strict: bool = True,
532
719
  subs: Mapping[str, object] = {},
533
720
  ) -> str:
534
- return self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
721
+ return self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
535
722
 
536
723
 
537
724
  class _set_name(compiled_schema):
@@ -552,28 +739,40 @@ class _set_name(compiled_schema):
552
739
 
553
740
  def __validate__(
554
741
  self,
555
- object_: object,
742
+ obj: object,
556
743
  name: str = "object",
557
744
  strict: bool = True,
558
745
  subs: Mapping[str, object] = {},
559
746
  ) -> str:
560
- message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
747
+ message = self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
561
748
  if message != "":
562
749
  if not self.reason:
563
- return _wrong_type_message(object_, name, self.__name__)
750
+ return _wrong_type_message(obj, name, self.__name__)
564
751
  else:
565
752
  return _wrong_type_message(
566
- object_, name, self.__name__, explanation=message, skip_value=True
753
+ obj, name, self.__name__, explanation=message, skip_value=True
567
754
  )
568
755
  return ""
569
756
 
570
757
 
571
- class set_name:
758
+ class set_name(wrapper):
759
+ """
760
+ An object matches the schema `set_name(schema, name, reason=False)` if it
761
+ matches `schema`, but the `name` argument will be used in non-validation
762
+ messages.
763
+ """
764
+
572
765
  reason: bool
573
766
  schema: object
574
767
  name: str
575
768
 
576
769
  def __init__(self, schema: object, name: str, reason: bool = False) -> None:
770
+ """
771
+ :param schema: the original schema
772
+ :param name: name for use in non-validation messages
773
+ :param reason: indicates whether the original non-validation message
774
+ should be suppressed
775
+ """
577
776
  if not isinstance(name, str):
578
777
  raise SchemaError(f"The name {_c(name)} is not a string")
579
778
  self.schema = schema
@@ -590,6 +789,10 @@ class set_name:
590
789
 
591
790
 
592
791
  class regex(compiled_schema):
792
+ """
793
+ This matches the strings which match the given pattern.
794
+ """
795
+
593
796
  regex: str
594
797
  fullmatch: bool
595
798
  __name__: str
@@ -602,6 +805,17 @@ class regex(compiled_schema):
602
805
  fullmatch: bool = True,
603
806
  flags: int = 0,
604
807
  ) -> None:
808
+ """
809
+ :param regex: the regular expression pattern
810
+ :param name: common name for the pattern that will be used in
811
+ non-validation messages
812
+ :param fullmatch: indicates whether or not the full string should be
813
+ matched
814
+ :param flags: the flags argument used when invoking `re.compile`
815
+
816
+ :raises SchemaError: exception thrown when the schema definition is
817
+ found to contain an error
818
+ """
605
819
  self.regex = regex
606
820
  self.fullmatch = fullmatch
607
821
  if name is not None:
@@ -623,28 +837,42 @@ class regex(compiled_schema):
623
837
 
624
838
  def __validate__(
625
839
  self,
626
- object_: object,
840
+ obj: object,
627
841
  name: str = "object",
628
842
  strict: bool = True,
629
843
  subs: Mapping[str, object] = {},
630
844
  ) -> str:
631
- if not isinstance(object_, str):
632
- return _wrong_type_message(object_, name, self.__name__)
845
+ if not isinstance(obj, str):
846
+ return _wrong_type_message(obj, name, self.__name__)
633
847
  try:
634
- if self.fullmatch and self.pattern.fullmatch(object_):
848
+ if self.fullmatch and self.pattern.fullmatch(obj):
635
849
  return ""
636
- elif not self.fullmatch and self.pattern.match(object_):
850
+ elif not self.fullmatch and self.pattern.match(obj):
637
851
  return ""
638
852
  except Exception:
639
853
  pass
640
- return _wrong_type_message(object_, name, self.__name__)
854
+ return _wrong_type_message(obj, name, self.__name__)
641
855
 
642
856
 
643
857
  class glob(compiled_schema):
858
+ """
859
+ Unix style filename matching. This is implemented using
860
+ `pathlib.PurePath().match()`.
861
+ """
862
+
644
863
  pattern: str
645
864
  __name__: str
646
865
 
647
866
  def __init__(self, pattern: str, name: str | None = None) -> None:
867
+ """
868
+ :param pattern: the wild card pattern to match
869
+ :param name: common name for the pattern that will be used in
870
+ non-validation messages
871
+
872
+ :raises SchemaError: exception thrown when the schema definition is
873
+ found to contain an error
874
+ """
875
+
648
876
  self.pattern = pattern
649
877
 
650
878
  if name is None:
@@ -662,27 +890,39 @@ class glob(compiled_schema):
662
890
 
663
891
  def __validate__(
664
892
  self,
665
- object_: object,
893
+ obj: object,
666
894
  name: str = "object",
667
895
  strict: bool = True,
668
896
  subs: Mapping[str, object] = {},
669
897
  ) -> str:
670
- if not isinstance(object_, str):
671
- return _wrong_type_message(object_, name, self.__name__)
898
+ if not isinstance(obj, str):
899
+ return _wrong_type_message(obj, name, self.__name__)
672
900
  try:
673
- if pathlib.PurePath(object_).match(self.pattern):
901
+ if pathlib.PurePath(obj).match(self.pattern):
674
902
  return ""
675
903
  else:
676
- return _wrong_type_message(object_, name, self.__name__)
904
+ return _wrong_type_message(obj, name, self.__name__)
677
905
  except Exception as e:
678
- return _wrong_type_message(object_, name, self.__name__, str(e))
906
+ return _wrong_type_message(obj, name, self.__name__, str(e))
679
907
 
680
908
 
681
909
  class magic(compiled_schema):
910
+ """
911
+ Checks if a buffer (for example a string or a byte array) has the given
912
+ mime type. This is implemented using the `python-magic` package.
913
+ """
914
+
682
915
  mime_type: str
683
916
  __name__: str
684
917
 
685
918
  def __init__(self, mime_type: str, name: str | None = None) -> None:
919
+ """
920
+ :param mime_type: the mime type
921
+ :param name: common name to refer to this mime type
922
+
923
+ :raises SchemaError: exception thrown when the schema definition is
924
+ found to contain an error
925
+ """
686
926
  if not HAS_MAGIC:
687
927
  raise SchemaError("Failed to load python-magic")
688
928
 
@@ -698,28 +938,32 @@ class magic(compiled_schema):
698
938
 
699
939
  def __validate__(
700
940
  self,
701
- object_: object,
941
+ obj: object,
702
942
  name: str = "object",
703
943
  strict: bool = True,
704
944
  subs: Mapping[str, object] = {},
705
945
  ) -> str:
706
- if not isinstance(object_, (str, bytes)):
707
- return _wrong_type_message(object_, name, self.__name__)
946
+ if not isinstance(obj, (str, bytes)):
947
+ return _wrong_type_message(obj, name, self.__name__)
708
948
  try:
709
- object_mime_type = magic_.from_buffer(object_, mime=True)
949
+ objmime_type = magic_.from_buffer(obj, mime=True)
710
950
  except Exception as e:
711
- return _wrong_type_message(object_, name, self.__name__, str(e))
712
- if object_mime_type != self.mime_type:
951
+ return _wrong_type_message(obj, name, self.__name__, str(e))
952
+ if objmime_type != self.mime_type:
713
953
  return _wrong_type_message(
714
- object_,
954
+ obj,
715
955
  name,
716
956
  self.__name__,
717
- f"{repr(object_mime_type)} is different from {repr(self.mime_type)}",
957
+ f"{repr(objmime_type)} is different from {repr(self.mime_type)}",
718
958
  )
719
959
  return ""
720
960
 
721
961
 
722
962
  class div(compiled_schema):
963
+ """
964
+ This matches the integers `x` such that `(x - remainder) % divisor` == 0.
965
+ """
966
+
723
967
  divisor: int
724
968
  remainder: int
725
969
  __name__: str
@@ -727,6 +971,15 @@ class div(compiled_schema):
727
971
  def __init__(
728
972
  self, divisor: int, remainder: int = 0, name: str | None = None
729
973
  ) -> None:
974
+ """
975
+ :param divisor: the divisor
976
+ :param remainder: the remainer
977
+ :param name: common name (e.g. `even` or `odd`) that will be used in
978
+ non-validation messages
979
+
980
+ :raises SchemaError: exception thrown when the schema definition is
981
+ found to contain an error
982
+ """
730
983
  if not isinstance(divisor, int):
731
984
  raise SchemaError(f"The divisor {repr(divisor)} is not an integer")
732
985
  if divisor == 0:
@@ -747,20 +1000,25 @@ class div(compiled_schema):
747
1000
 
748
1001
  def __validate__(
749
1002
  self,
750
- object_: object,
1003
+ obj: object,
751
1004
  name: str = "object",
752
1005
  strict: bool = True,
753
1006
  subs: Mapping[str, object] = {},
754
1007
  ) -> str:
755
- if not isinstance(object_, int):
756
- return _wrong_type_message(object_, name, "int")
757
- elif (object_ - self.remainder) % self.divisor == 0:
1008
+ if not isinstance(obj, int):
1009
+ return _wrong_type_message(obj, name, "int")
1010
+ elif (obj - self.remainder) % self.divisor == 0:
758
1011
  return ""
759
1012
  else:
760
- return _wrong_type_message(object_, name, self.__name__)
1013
+ return _wrong_type_message(obj, name, self.__name__)
761
1014
 
762
1015
 
763
1016
  class close_to(compiled_schema):
1017
+ """
1018
+ This matches the real numbers that are close to `x` in the sense of
1019
+ `math.isclose`.
1020
+ """
1021
+
764
1022
  kw: dict[str, float]
765
1023
  x: int | float
766
1024
  __name__: str
@@ -771,6 +1029,13 @@ class close_to(compiled_schema):
771
1029
  rel_tol: int | float | None = None,
772
1030
  abs_tol: int | float | None = None,
773
1031
  ) -> None:
1032
+ """
1033
+ :param rel_tol: the maximal allowed relative deviation
1034
+ :param abs_tol: the maximal allowed absolute deviation
1035
+
1036
+ :raises SchemaError: exception thrown when the schema definition is
1037
+ found to contain an error
1038
+ """
774
1039
  self.kw = {}
775
1040
  if not isinstance(x, (int, float)):
776
1041
  raise SchemaError(f"{repr(x)} is not a number")
@@ -794,23 +1059,33 @@ class close_to(compiled_schema):
794
1059
 
795
1060
  def __validate__(
796
1061
  self,
797
- object_: object,
1062
+ obj: object,
798
1063
  name: str = "object",
799
1064
  strict: bool = True,
800
1065
  subs: Mapping[str, object] = {},
801
1066
  ) -> str:
802
- if not isinstance(object_, (float, int)):
803
- return _wrong_type_message(object_, name, "number")
804
- elif math.isclose(object_, self.x, **self.kw):
1067
+ if not isinstance(obj, (float, int)):
1068
+ return _wrong_type_message(obj, name, "number")
1069
+ elif math.isclose(obj, self.x, **self.kw):
805
1070
  return ""
806
1071
  else:
807
- return _wrong_type_message(object_, name, self.__name__)
1072
+ return _wrong_type_message(obj, name, self.__name__)
808
1073
 
809
1074
 
810
1075
  class gt(compiled_schema):
1076
+ """
1077
+ This checks if `object > lb`.
1078
+ """
1079
+
811
1080
  lb: comparable
812
1081
 
813
1082
  def __init__(self, lb: comparable) -> None:
1083
+ """
1084
+ :param lb: the strict lower bound
1085
+
1086
+ :raises SchemaError: exception thrown when the schema definition is
1087
+ found to contain an error
1088
+ """
814
1089
  try:
815
1090
  lb <= lb
816
1091
  except Exception:
@@ -819,29 +1094,39 @@ class gt(compiled_schema):
819
1094
  ) from None
820
1095
  self.lb = lb
821
1096
 
822
- def message(self, name: str, object_: object) -> str:
823
- return f"{name} (value:{_c(object_)}) is not strictly greater than {self.lb}"
1097
+ def message(self, name: str, obj: object) -> str:
1098
+ return f"{name} (value:{_c(obj)}) is not strictly greater than {self.lb}"
824
1099
 
825
1100
  def __validate__(
826
1101
  self,
827
- object_: object,
1102
+ obj: object,
828
1103
  name: str = "object",
829
1104
  strict: bool = True,
830
1105
  subs: Mapping[str, object] = {},
831
1106
  ) -> str:
832
1107
  try:
833
- if self.lb < object_:
1108
+ if self.lb < obj:
834
1109
  return ""
835
1110
  else:
836
- return self.message(name, object_)
1111
+ return self.message(name, obj)
837
1112
  except Exception as e:
838
- return f"{self.message(name, object_)}: {str(e)}"
1113
+ return f"{self.message(name, obj)}: {str(e)}"
839
1114
 
840
1115
 
841
1116
  class ge(compiled_schema):
1117
+ """
1118
+ This checks if `object >= lb`.
1119
+ """
1120
+
842
1121
  lb: comparable
843
1122
 
844
1123
  def __init__(self, lb: comparable) -> None:
1124
+ """
1125
+ :param lb: the lower bound
1126
+
1127
+ :raises SchemaError: exception thrown when the schema definition is
1128
+ found to contain an error
1129
+ """
845
1130
  try:
846
1131
  lb <= lb
847
1132
  except Exception:
@@ -850,29 +1135,39 @@ class ge(compiled_schema):
850
1135
  ) from None
851
1136
  self.lb = lb
852
1137
 
853
- def message(self, name: str, object_: object) -> str:
854
- return f"{name} (value:{_c(object_)}) is not greater than or equal to {self.lb}"
1138
+ def message(self, name: str, obj: object) -> str:
1139
+ return f"{name} (value:{_c(obj)}) is not greater than or equal to {self.lb}"
855
1140
 
856
1141
  def __validate__(
857
1142
  self,
858
- object_: object,
1143
+ obj: object,
859
1144
  name: str = "object",
860
1145
  strict: bool = True,
861
1146
  subs: Mapping[str, object] = {},
862
1147
  ) -> str:
863
1148
  try:
864
- if self.lb <= object_:
1149
+ if self.lb <= obj:
865
1150
  return ""
866
1151
  else:
867
- return self.message(name, object_)
1152
+ return self.message(name, obj)
868
1153
  except Exception as e:
869
- return f"{self.message(name, object_)}: {str(e)}"
1154
+ return f"{self.message(name, obj)}: {str(e)}"
870
1155
 
871
1156
 
872
1157
  class lt(compiled_schema):
1158
+ """
1159
+ This checks if `object < ub`.
1160
+ """
1161
+
873
1162
  ub: comparable
874
1163
 
875
1164
  def __init__(self, ub: comparable) -> None:
1165
+ """
1166
+ :param ub: the strict upper bound
1167
+
1168
+ :raises SchemaError: exception thrown when the schema definition is
1169
+ found to contain an error
1170
+ """
876
1171
  try:
877
1172
  ub <= ub
878
1173
  except Exception:
@@ -881,29 +1176,39 @@ class lt(compiled_schema):
881
1176
  ) from None
882
1177
  self.ub = ub
883
1178
 
884
- def message(self, name: str, object_: object) -> str:
885
- return f"{name} (value:{_c(object_)}) is not strictly less than {self.ub}"
1179
+ def message(self, name: str, obj: object) -> str:
1180
+ return f"{name} (value:{_c(obj)}) is not strictly less than {self.ub}"
886
1181
 
887
1182
  def __validate__(
888
1183
  self,
889
- object_: object,
1184
+ obj: object,
890
1185
  name: str = "object",
891
1186
  strict: bool = True,
892
1187
  subs: Mapping[str, object] = {},
893
1188
  ) -> str:
894
1189
  try:
895
- if self.ub > object_:
1190
+ if self.ub > obj:
896
1191
  return ""
897
1192
  else:
898
- return self.message(name, object_)
1193
+ return self.message(name, obj)
899
1194
  except Exception as e:
900
- return f"{self.message(name, object_)}: {str(e)}"
1195
+ return f"{self.message(name, obj)}: {str(e)}"
901
1196
 
902
1197
 
903
1198
  class le(compiled_schema):
1199
+ """
1200
+ This checks if `object <= ub`.
1201
+ """
1202
+
904
1203
  ub: comparable
905
1204
 
906
1205
  def __init__(self, ub: comparable) -> None:
1206
+ """
1207
+ :param ub: the upper bound
1208
+
1209
+ :raises SchemaError: exception thrown when the schema definition is
1210
+ found to contain an error
1211
+ """
907
1212
  try:
908
1213
  ub <= ub
909
1214
  except Exception:
@@ -912,26 +1217,30 @@ class le(compiled_schema):
912
1217
  ) from None
913
1218
  self.ub = ub
914
1219
 
915
- def message(self, name: str, object_: object) -> str:
916
- return f"{name} (value:{_c(object_)}) is not less than or equal to {self.ub}"
1220
+ def message(self, name: str, obj: object) -> str:
1221
+ return f"{name} (value:{_c(obj)}) is not less than or equal to {self.ub}"
917
1222
 
918
1223
  def __validate__(
919
1224
  self,
920
- object_: object,
1225
+ obj: object,
921
1226
  name: str = "object",
922
1227
  strict: bool = True,
923
1228
  subs: Mapping[str, object] = {},
924
1229
  ) -> str:
925
1230
  try:
926
- if self.ub >= object_:
1231
+ if self.ub >= obj:
927
1232
  return ""
928
1233
  else:
929
- return self.message(name, object_)
1234
+ return self.message(name, obj)
930
1235
  except Exception as e:
931
- return f"{self.message(name, object_)}: {str(e)}"
1236
+ return f"{self.message(name, obj)}: {str(e)}"
932
1237
 
933
1238
 
934
1239
  class interval(compiled_schema):
1240
+ """
1241
+ This checks if `lb <= object <= ub`, provided the comparisons make sense.
1242
+ """
1243
+
935
1244
  lb_s: str
936
1245
  ub_s: str
937
1246
 
@@ -942,7 +1251,15 @@ class interval(compiled_schema):
942
1251
  strict_lb: bool = False,
943
1252
  strict_ub: bool = False,
944
1253
  ) -> None:
945
-
1254
+ """
1255
+ :param lb: lower bound; ... (ellipsis) means no lower bound
1256
+ :param ub: upper bound; ... (ellipsis) means no upper bound
1257
+ :param strict_lb: if True use a strict lower bound
1258
+ :param strict_ub: if True use a strict upper bound
1259
+
1260
+ :raises SchemaError: exception thrown when the schema definition is
1261
+ found to contain an error
1262
+ """
946
1263
  self.lb_s = "..." if lb == ... else repr(lb)
947
1264
  self.ub_s = "..." if ub == ... else repr(ub)
948
1265
 
@@ -995,9 +1312,22 @@ class interval(compiled_schema):
995
1312
 
996
1313
 
997
1314
  class size(compiled_schema):
1315
+ """
1316
+ Matches the objects (which support `len()` such as strings or lists) whose
1317
+ length is in the interval `[lb, ub]`.
1318
+ """
1319
+
998
1320
  interval_: interval
999
1321
 
1000
1322
  def __init__(self, lb: int, ub: int | types.EllipsisType | None = None) -> None:
1323
+ """
1324
+ :param lb: the lower bound for the length
1325
+ :param ub: the upper bound for the length; ... (ellipsis) means that
1326
+ there is no upper bound; `None` means `lb==ub`
1327
+
1328
+ :raises SchemaError: exception thrown when the schema definition is
1329
+ found to contain an error
1330
+ """
1001
1331
  if ub is None:
1002
1332
  ub = lb
1003
1333
  if not isinstance(lb, int):
@@ -1021,15 +1351,15 @@ class size(compiled_schema):
1021
1351
 
1022
1352
  def __validate__(
1023
1353
  self,
1024
- object_: object,
1354
+ obj: object,
1025
1355
  name: str = "object",
1026
1356
  strict: bool = True,
1027
1357
  subs: Mapping[str, object] = {},
1028
1358
  ) -> str:
1029
- if not isinstance(object_, Sized):
1030
- return f"{name} (value:{_c(object_)}) has no len()"
1359
+ if not isinstance(obj, Sized):
1360
+ return f"{name} (value:{_c(obj)}) has no len()"
1031
1361
 
1032
- L = len(object_)
1362
+ L = len(obj)
1033
1363
 
1034
1364
  return self.interval_.__validate__(L, f"len({name})", strict, subs)
1035
1365
 
@@ -1044,7 +1374,7 @@ class _deferred(compiled_schema):
1044
1374
 
1045
1375
  def __validate__(
1046
1376
  self,
1047
- object_: object,
1377
+ obj: object,
1048
1378
  name: str = "object",
1049
1379
  strict: bool = True,
1050
1380
  subs: Mapping[str, object] = {},
@@ -1052,7 +1382,7 @@ class _deferred(compiled_schema):
1052
1382
  if self.key not in self.collection:
1053
1383
  raise ValidationError(f"{name}: key {self.key} is unknown")
1054
1384
  return self.collection[self.key].__validate__(
1055
- object_, name=name, strict=strict, subs=subs
1385
+ obj, name=name, strict=strict, subs=subs
1056
1386
  )
1057
1387
 
1058
1388
 
@@ -1092,12 +1422,32 @@ class _validate_schema(compiled_schema):
1092
1422
 
1093
1423
 
1094
1424
  def compile(schema: object) -> compiled_schema:
1425
+ """
1426
+ Compiles a schema. Internally invokes :py:func:`vtjson._compile`.
1427
+
1428
+ :param schema: the schema that should be compiled
1429
+
1430
+ :raises SchemaError: exception thrown when the schema definition is found
1431
+ to contain an error
1432
+ """
1095
1433
  return _compile(schema, _deferred_compiles=None)
1096
1434
 
1097
1435
 
1098
1436
  def _compile(
1099
1437
  schema: object, _deferred_compiles: _mapping | None = None
1100
1438
  ) -> compiled_schema:
1439
+ """
1440
+ Compiles a schema.
1441
+
1442
+ :param schema: the schema that should be compiled
1443
+ :param _deferred_compiles: an opaque data structure used for handling
1444
+ recursive schemas; it should be passed unmodifed to any internal
1445
+ invocations of :py:func:`vtjson._compile` or
1446
+ :py:meth:`vtjson.wrapper.__compile__`
1447
+
1448
+ :raises SchemaError: exception thrown when the schema definition is found
1449
+ to contain an error
1450
+ """
1101
1451
  if _deferred_compiles is None:
1102
1452
  _deferred_compiles = _mapping()
1103
1453
  # avoid infinite loop in case of a recursive schema
@@ -1123,7 +1473,7 @@ def _compile(
1123
1473
  ) from None
1124
1474
  elif hasattr(schema, "__validate__"):
1125
1475
  ret = _validate_schema(schema)
1126
- elif hasattr(schema, "__compile__"):
1476
+ elif isinstance(schema, wrapper):
1127
1477
  ret = schema.__compile__(_deferred_compiles=_deferred_compiles)
1128
1478
  elif isinstance(schema, compiled_schema):
1129
1479
  ret = schema
@@ -1194,24 +1544,41 @@ def _compile(
1194
1544
 
1195
1545
  def _validate(
1196
1546
  schema: object,
1197
- object_: object,
1547
+ obj: object,
1198
1548
  name: str = "object",
1199
1549
  strict: bool = True,
1200
1550
  subs: Mapping[str, object] = {},
1201
1551
  ) -> str:
1202
- return compile(schema).__validate__(object_, name=name, strict=strict, subs=subs)
1552
+ return compile(schema).__validate__(obj, name=name, strict=strict, subs=subs)
1203
1553
 
1204
1554
 
1205
1555
  def validate(
1206
1556
  schema: object,
1207
- object_: object,
1557
+ obj: object,
1208
1558
  name: str = "object",
1209
1559
  strict: bool = True,
1210
1560
  subs: Mapping[str, object] = {},
1211
1561
  ) -> None:
1562
+ """
1563
+ Validates the given object against the given schema.
1564
+
1565
+ :param schema: the given schema
1566
+ :param obj: the object to be validated
1567
+ :param name: common name for the object to be validated; used in
1568
+ non-validation messages
1569
+ :param strict: indicates whether or not the object being validated is
1570
+ allowed to have keys/entries which are not in the schema
1571
+ :param subs: a dictionary whose keys are labels and whose values are
1572
+ substitution schemas for schemas with those labels
1573
+ :raises ValidationError: exception thrown when the object does not
1574
+ validate; the exception message contains an explanation about what went
1575
+ wrong
1576
+ :raises SchemaError: exception thrown when the schema definition is found
1577
+ to contain an error
1578
+ """
1212
1579
  message = _validate(
1213
1580
  schema,
1214
- object_,
1581
+ obj,
1215
1582
  name=name,
1216
1583
  strict=strict,
1217
1584
  subs=subs,
@@ -1224,7 +1591,10 @@ def validate(
1224
1591
 
1225
1592
 
1226
1593
  class number(compiled_schema):
1227
- # functionally equivalent to float
1594
+ """
1595
+ A deprecated alias for `float`.
1596
+ """
1597
+
1228
1598
  def __init__(self) -> None:
1229
1599
  warnings.warn(
1230
1600
  "The schema 'number' is deprecated. Use 'float' instead.",
@@ -1233,21 +1603,31 @@ class number(compiled_schema):
1233
1603
 
1234
1604
  def __validate__(
1235
1605
  self,
1236
- object_: object,
1606
+ obj: object,
1237
1607
  name: str = "object",
1238
1608
  strict: bool = True,
1239
1609
  subs: Mapping[str, object] = {},
1240
1610
  ) -> str:
1241
- if isinstance(object_, (int, float)):
1611
+ if isinstance(obj, (int, float)):
1242
1612
  return ""
1243
1613
  else:
1244
- return _wrong_type_message(object_, name, "number")
1614
+ return _wrong_type_message(obj, name, "number")
1245
1615
 
1246
1616
 
1247
1617
  class email(compiled_schema):
1618
+ """
1619
+ Checks if the object is a valid email address. This uses the package
1620
+ `email_validator`. The `email` schema accepts the same options as
1621
+ `validate_email` in loc. cit.
1622
+ """
1623
+
1248
1624
  kw: dict[str, Any]
1249
1625
 
1250
1626
  def __init__(self, **kw: Any) -> None:
1627
+ """
1628
+ :param kw: optional keyword arguments to be forwarded to
1629
+ `email_validator.validate_email`
1630
+ """
1251
1631
  self.kw = kw
1252
1632
  if "dns_resolver" not in kw:
1253
1633
  self.kw["dns_resolver"] = _get_dns_resolver()
@@ -1256,28 +1636,34 @@ class email(compiled_schema):
1256
1636
 
1257
1637
  def __validate__(
1258
1638
  self,
1259
- object_: object,
1639
+ obj: object,
1260
1640
  name: str = "object",
1261
1641
  strict: bool = True,
1262
1642
  subs: Mapping[str, object] = {},
1263
1643
  ) -> str:
1264
- if not isinstance(object_, str):
1265
- return _wrong_type_message(
1266
- object_, name, "email", f"{_c(object_)} is not a string"
1267
- )
1644
+ if not isinstance(obj, str):
1645
+ return _wrong_type_message(obj, name, "email", f"{_c(obj)} is not a string")
1268
1646
  try:
1269
- email_validator.validate_email(object_, **self.kw)
1647
+ email_validator.validate_email(obj, **self.kw)
1270
1648
  return ""
1271
1649
  except Exception as e:
1272
- return _wrong_type_message(object_, name, "email", str(e))
1650
+ return _wrong_type_message(obj, name, "email", str(e))
1273
1651
 
1274
1652
 
1275
1653
  class ip_address(compiled_schema):
1654
+ """
1655
+ Matches ip addresses of the specified version which can be 4, 6 or None.
1656
+ """
1276
1657
 
1277
1658
  __name__: str
1278
1659
  method: Callable[[Any], Any]
1279
1660
 
1280
- def __init__(self, version: Literal[4, 6] | None = None) -> None:
1661
+ def __init__(self, version: Literal[4, 6, None] = None) -> None:
1662
+ """
1663
+ :param version: the version of the ip protocol
1664
+ :raises SchemaError: exception thrown when the schema definition is
1665
+ found to contain an error
1666
+ """
1281
1667
  if version is not None and version not in (4, 6):
1282
1668
  raise SchemaError("version is not 4 or 6")
1283
1669
  if version is None:
@@ -1293,41 +1679,53 @@ class ip_address(compiled_schema):
1293
1679
 
1294
1680
  def __validate__(
1295
1681
  self,
1296
- object_: object,
1682
+ obj: object,
1297
1683
  name: str = "object",
1298
1684
  strict: bool = True,
1299
1685
  subs: Mapping[str, object] = {},
1300
1686
  ) -> str:
1301
- if not isinstance(object_, (int, str, bytes)):
1302
- return _wrong_type_message(object_, name, self.__name__)
1687
+ if not isinstance(obj, (int, str, bytes)):
1688
+ return _wrong_type_message(obj, name, self.__name__)
1303
1689
  try:
1304
- self.method(object_)
1690
+ self.method(obj)
1305
1691
  except ValueError as e:
1306
- return _wrong_type_message(object_, name, self.__name__, explanation=str(e))
1692
+ return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
1307
1693
  return ""
1308
1694
 
1309
1695
 
1310
1696
  class url(compiled_schema):
1697
+ """
1698
+ Matches valid urls.
1699
+ """
1700
+
1311
1701
  def __validate__(
1312
1702
  self,
1313
- object_: object,
1703
+ obj: object,
1314
1704
  name: str = "object",
1315
1705
  strict: bool = True,
1316
1706
  subs: Mapping[str, object] = {},
1317
1707
  ) -> str:
1318
- if not isinstance(object_, str):
1319
- return _wrong_type_message(object_, name, "url")
1320
- result = urllib.parse.urlparse(object_)
1708
+ if not isinstance(obj, str):
1709
+ return _wrong_type_message(obj, name, "url")
1710
+ result = urllib.parse.urlparse(obj)
1321
1711
  if all([result.scheme, result.netloc]):
1322
1712
  return ""
1323
- return _wrong_type_message(object_, name, "url")
1713
+ return _wrong_type_message(obj, name, "url")
1324
1714
 
1325
1715
 
1326
1716
  class date_time(compiled_schema):
1717
+ """
1718
+ Without argument this represents an ISO 8601 date-time. The `format`
1719
+ argument represents a format string for `strftime`.
1720
+ """
1721
+
1327
1722
  format: str | None
1328
1723
  __name__: str
1329
1724
 
1330
1725
  def __init__(self, format: str | None = None) -> None:
1726
+ """
1727
+ :param format: format string for `strftime`
1728
+ """
1331
1729
  self.format = format
1332
1730
  if format is not None:
1333
1731
  self.__name__ = f"date_time({repr(format)})"
@@ -1336,75 +1734,91 @@ class date_time(compiled_schema):
1336
1734
 
1337
1735
  def __validate__(
1338
1736
  self,
1339
- object_: object,
1737
+ obj: object,
1340
1738
  name: str = "object",
1341
1739
  strict: bool = True,
1342
1740
  subs: Mapping[str, object] = {},
1343
1741
  ) -> str:
1344
- if not isinstance(object_, str):
1345
- return _wrong_type_message(object_, name, self.__name__)
1742
+ if not isinstance(obj, str):
1743
+ return _wrong_type_message(obj, name, self.__name__)
1346
1744
  if self.format is not None:
1347
1745
  try:
1348
- datetime.datetime.strptime(object_, self.format)
1746
+ datetime.datetime.strptime(obj, self.format)
1349
1747
  except Exception as e:
1350
- return _wrong_type_message(object_, name, self.__name__, str(e))
1748
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1351
1749
  else:
1352
1750
  try:
1353
- datetime.datetime.fromisoformat(object_)
1751
+ datetime.datetime.fromisoformat(obj)
1354
1752
  except Exception as e:
1355
- return _wrong_type_message(object_, name, self.__name__, str(e))
1753
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1356
1754
  return ""
1357
1755
 
1358
1756
 
1359
1757
  class date(compiled_schema):
1758
+ """
1759
+ Matches an ISO 8601 date.
1760
+ """
1761
+
1360
1762
  def __validate__(
1361
1763
  self,
1362
- object_: object,
1764
+ obj: object,
1363
1765
  name: str = "object",
1364
1766
  strict: bool = True,
1365
1767
  subs: Mapping[str, object] = {},
1366
1768
  ) -> str:
1367
- if not isinstance(object_, str):
1368
- return _wrong_type_message(object_, name, "date")
1769
+ if not isinstance(obj, str):
1770
+ return _wrong_type_message(obj, name, "date")
1369
1771
  try:
1370
- datetime.date.fromisoformat(object_)
1772
+ datetime.date.fromisoformat(obj)
1371
1773
  except Exception as e:
1372
- return _wrong_type_message(object_, name, "date", str(e))
1774
+ return _wrong_type_message(obj, name, "date", str(e))
1373
1775
  return ""
1374
1776
 
1375
1777
 
1376
1778
  class time(compiled_schema):
1779
+ """
1780
+ Matches an ISO 8601 time.
1781
+ """
1782
+
1377
1783
  def __validate__(
1378
1784
  self,
1379
- object_: object,
1785
+ obj: object,
1380
1786
  name: str = "object",
1381
1787
  strict: bool = True,
1382
1788
  subs: Mapping[str, object] = {},
1383
1789
  ) -> str:
1384
- if not isinstance(object_, str):
1385
- return _wrong_type_message(object_, name, "date")
1790
+ if not isinstance(obj, str):
1791
+ return _wrong_type_message(obj, name, "date")
1386
1792
  try:
1387
- datetime.time.fromisoformat(object_)
1793
+ datetime.time.fromisoformat(obj)
1388
1794
  except Exception as e:
1389
- return _wrong_type_message(object_, name, "time", str(e))
1795
+ return _wrong_type_message(obj, name, "time", str(e))
1390
1796
  return ""
1391
1797
 
1392
1798
 
1393
1799
  class nothing(compiled_schema):
1800
+ """
1801
+ Matches nothing.
1802
+ """
1803
+
1394
1804
  def __validate__(
1395
1805
  self,
1396
- object_: object,
1806
+ obj: object,
1397
1807
  name: str = "object",
1398
1808
  strict: bool = True,
1399
1809
  subs: Mapping[str, object] = {},
1400
1810
  ) -> str:
1401
- return _wrong_type_message(object_, name, "nothing")
1811
+ return _wrong_type_message(obj, name, "nothing")
1402
1812
 
1403
1813
 
1404
1814
  class anything(compiled_schema):
1815
+ """
1816
+ Matchess anything.
1817
+ """
1818
+
1405
1819
  def __validate__(
1406
1820
  self,
1407
- object_: object,
1821
+ obj: object,
1408
1822
  name: str = "object",
1409
1823
  strict: bool = True,
1410
1824
  subs: Mapping[str, object] = {},
@@ -1413,12 +1827,20 @@ class anything(compiled_schema):
1413
1827
 
1414
1828
 
1415
1829
  class domain_name(compiled_schema):
1830
+ """
1831
+ Checks if the object is a valid domain name.
1832
+ """
1833
+
1416
1834
  re_asci: re.Pattern[str]
1417
1835
  ascii_only: bool
1418
1836
  resolve: bool
1419
1837
  __name__: str
1420
1838
 
1421
1839
  def __init__(self, ascii_only: bool = True, resolve: bool = False) -> None:
1840
+ """
1841
+ :param ascii_only: if `False` then allow IDNA domain names
1842
+ :param resolve: if `True` check if the domain names resolves
1843
+ """
1422
1844
  self.re_ascii = re.compile(r"[\x00-\x7F]*")
1423
1845
  self.ascii_only = ascii_only
1424
1846
  self.resolve = resolve
@@ -1435,129 +1857,161 @@ class domain_name(compiled_schema):
1435
1857
 
1436
1858
  def __validate__(
1437
1859
  self,
1438
- object_: object,
1860
+ obj: object,
1439
1861
  name: str = "object",
1440
1862
  strict: bool = True,
1441
1863
  subs: Mapping[str, object] = {},
1442
1864
  ) -> str:
1443
- if not isinstance(object_, str):
1444
- return _wrong_type_message(object_, name, self.__name__)
1865
+ if not isinstance(obj, str):
1866
+ return _wrong_type_message(obj, name, self.__name__)
1445
1867
  if self.ascii_only:
1446
- if not self.re_ascii.fullmatch(object_):
1868
+ if not self.re_ascii.fullmatch(obj):
1447
1869
  return _wrong_type_message(
1448
- object_, name, self.__name__, "Non-ascii characters"
1870
+ obj, name, self.__name__, "Non-ascii characters"
1449
1871
  )
1450
1872
  try:
1451
- idna.encode(object_, uts46=False)
1873
+ idna.encode(obj, uts46=False)
1452
1874
  except idna.core.IDNAError as e:
1453
- return _wrong_type_message(object_, name, self.__name__, str(e))
1875
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1454
1876
 
1455
1877
  if self.resolve:
1456
1878
  try:
1457
- _get_dns_resolver().resolve(object_)
1879
+ _get_dns_resolver().resolve(obj)
1458
1880
  except Exception as e:
1459
- return _wrong_type_message(object_, name, self.__name__, str(e))
1881
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1460
1882
  return ""
1461
1883
 
1462
1884
 
1463
1885
  class at_least_one_of(compiled_schema):
1886
+ """
1887
+ This represents a dictionary with a least one key among a collection of
1888
+ keys.
1889
+ """
1890
+
1464
1891
  args: tuple[object, ...]
1465
1892
  __name__: str
1466
1893
 
1467
1894
  def __init__(self, *args: object) -> None:
1895
+ """
1896
+ :param args: a collection of keys
1897
+ """
1468
1898
  self.args = args
1469
1899
  args_s = [repr(a) for a in args]
1470
1900
  self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
1471
1901
 
1472
1902
  def __validate__(
1473
1903
  self,
1474
- object_: object,
1904
+ obj: object,
1475
1905
  name: str = "object",
1476
1906
  strict: bool = True,
1477
1907
  subs: Mapping[str, object] = {},
1478
1908
  ) -> str:
1479
- if not isinstance(object_, Mapping):
1480
- return _wrong_type_message(object_, name, self.__name__)
1909
+ if not isinstance(obj, Mapping):
1910
+ return _wrong_type_message(obj, name, self.__name__)
1481
1911
  try:
1482
- if any([a in object_ for a in self.args]):
1912
+ if any([a in obj for a in self.args]):
1483
1913
  return ""
1484
1914
  else:
1485
- return _wrong_type_message(object_, name, self.__name__)
1915
+ return _wrong_type_message(obj, name, self.__name__)
1486
1916
  except Exception as e:
1487
- return _wrong_type_message(object_, name, self.__name__, str(e))
1917
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1488
1918
 
1489
1919
 
1490
1920
  class at_most_one_of(compiled_schema):
1921
+ """
1922
+ This represents an dictionary with at most one key among a collection of
1923
+ keys.
1924
+ """
1925
+
1491
1926
  args: tuple[object, ...]
1492
1927
  __name__: str
1493
1928
 
1494
1929
  def __init__(self, *args: object) -> None:
1930
+ """
1931
+ :param args: a collection of keys
1932
+ """
1495
1933
  self.args = args
1496
1934
  args_s = [repr(a) for a in args]
1497
1935
  self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
1498
1936
 
1499
1937
  def __validate__(
1500
1938
  self,
1501
- object_: object,
1939
+ obj: object,
1502
1940
  name: str = "object",
1503
1941
  strict: bool = True,
1504
1942
  subs: Mapping[str, object] = {},
1505
1943
  ) -> str:
1506
- if not isinstance(object_, Mapping):
1507
- return _wrong_type_message(object_, name, self.__name__)
1944
+ if not isinstance(obj, Mapping):
1945
+ return _wrong_type_message(obj, name, self.__name__)
1508
1946
  try:
1509
- if sum([a in object_ for a in self.args]) <= 1:
1947
+ if sum([a in obj for a in self.args]) <= 1:
1510
1948
  return ""
1511
1949
  else:
1512
- return _wrong_type_message(object_, name, self.__name__)
1950
+ return _wrong_type_message(obj, name, self.__name__)
1513
1951
  except Exception as e:
1514
- return _wrong_type_message(object_, name, self.__name__, str(e))
1952
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1515
1953
 
1516
1954
 
1517
1955
  class one_of(compiled_schema):
1956
+ """
1957
+ This represents a dictionary with exactly one key among a collection of
1958
+ keys.
1959
+ """
1960
+
1518
1961
  args: tuple[object, ...]
1519
1962
  __name__: str
1520
1963
 
1521
1964
  def __init__(self, *args: object) -> None:
1965
+ """
1966
+ :param args: a collection of keys
1967
+ """
1522
1968
  self.args = args
1523
1969
  args_s = [repr(a) for a in args]
1524
1970
  self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
1525
1971
 
1526
1972
  def __validate__(
1527
1973
  self,
1528
- object_: object,
1974
+ obj: object,
1529
1975
  name: str = "object",
1530
1976
  strict: bool = True,
1531
1977
  subs: Mapping[str, object] = {},
1532
1978
  ) -> str:
1533
- if not isinstance(object_, Mapping):
1534
- return _wrong_type_message(object_, name, self.__name__)
1979
+ if not isinstance(obj, Mapping):
1980
+ return _wrong_type_message(obj, name, self.__name__)
1535
1981
  try:
1536
- if sum([a in object_ for a in self.args]) == 1:
1982
+ if sum([a in obj for a in self.args]) == 1:
1537
1983
  return ""
1538
1984
  else:
1539
- return _wrong_type_message(object_, name, self.__name__)
1985
+ return _wrong_type_message(obj, name, self.__name__)
1540
1986
  except Exception as e:
1541
- return _wrong_type_message(object_, name, self.__name__, str(e))
1987
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1542
1988
 
1543
1989
 
1544
1990
  class keys(compiled_schema):
1991
+ """
1992
+ This represents a dictionary containing all the keys in a collection of
1993
+ keys
1994
+ """
1995
+
1545
1996
  args: tuple[object, ...]
1546
1997
 
1547
1998
  def __init__(self, *args: object) -> None:
1999
+ """
2000
+ :param args: a collection of keys
2001
+ """
1548
2002
  self.args = args
1549
2003
 
1550
2004
  def __validate__(
1551
2005
  self,
1552
- object_: object,
2006
+ obj: object,
1553
2007
  name: str = "object",
1554
2008
  strict: bool = True,
1555
2009
  subs: Mapping[str, object] = {},
1556
2010
  ) -> str:
1557
- if not isinstance(object_, Mapping):
1558
- return _wrong_type_message(object_, name, "Mapping") # TODO: __name__
2011
+ if not isinstance(obj, Mapping):
2012
+ return _wrong_type_message(obj, name, "Mapping") # TODO: __name__
1559
2013
  for k in self.args:
1560
- if k not in object_:
2014
+ if k not in obj:
1561
2015
  return f"{name}[{repr(k)}] is missing"
1562
2016
  return ""
1563
2017
 
@@ -1585,26 +2039,29 @@ class _ifthen(compiled_schema):
1585
2039
 
1586
2040
  def __validate__(
1587
2041
  self,
1588
- object_: object,
2042
+ obj: object,
1589
2043
  name: str = "object",
1590
2044
  strict: bool = True,
1591
2045
  subs: Mapping[str, object] = {},
1592
2046
  ) -> str:
1593
- if (
1594
- self.if_schema.__validate__(object_, name=name, strict=strict, subs=subs)
1595
- == ""
1596
- ):
2047
+ if self.if_schema.__validate__(obj, name=name, strict=strict, subs=subs) == "":
1597
2048
  return self.then_schema.__validate__(
1598
- object_, name=name, strict=strict, subs=subs
2049
+ obj, name=name, strict=strict, subs=subs
1599
2050
  )
1600
2051
  elif self.else_schema is not None:
1601
2052
  return self.else_schema.__validate__(
1602
- object_, name=name, strict=strict, subs=subs
2053
+ obj, name=name, strict=strict, subs=subs
1603
2054
  )
1604
2055
  return ""
1605
2056
 
1606
2057
 
1607
- class ifthen:
2058
+ class ifthen(wrapper):
2059
+ """
2060
+ If the object matches the `if_schema` then it should also match the
2061
+ `then_schema`. If the object does not match the `if_schema` then it should
2062
+ match the `else_schema`, if present.
2063
+ """
2064
+
1608
2065
  if_schema: object
1609
2066
  then_schema: object
1610
2067
  else_schema: object | None
@@ -1615,6 +2072,11 @@ class ifthen:
1615
2072
  then_schema: object,
1616
2073
  else_schema: object | None = None,
1617
2074
  ) -> None:
2075
+ """
2076
+ :param if_schema: the `if_schema`
2077
+ :param then_schema: the `then_schema`
2078
+ :param else_schema: the `else_schema`
2079
+ """
1618
2080
  self.if_schema = if_schema
1619
2081
  self.then_schema = then_schema
1620
2082
  self.else_schema = else_schema
@@ -1647,21 +2109,35 @@ class _cond(compiled_schema):
1647
2109
 
1648
2110
  def __validate__(
1649
2111
  self,
1650
- object_: object,
2112
+ obj: object,
1651
2113
  name: str = "object",
1652
2114
  strict: bool = True,
1653
2115
  subs: Mapping[str, object] = {},
1654
2116
  ) -> str:
1655
2117
  for c in self.conditions:
1656
- if c[0].__validate__(object_, name=name, strict=strict, subs=subs) == "":
1657
- return c[1].__validate__(object_, name=name, strict=strict, subs=subs)
2118
+ if c[0].__validate__(obj, name=name, strict=strict, subs=subs) == "":
2119
+ return c[1].__validate__(obj, name=name, strict=strict, subs=subs)
1658
2120
  return ""
1659
2121
 
1660
2122
 
1661
- class cond:
2123
+ class cond(wrapper):
2124
+ """
2125
+ An object is successively validated against `if_schema1`, `if_schema2`,
2126
+ ... until a validation succeeds. When this happens the object should match
2127
+ the corresponding `then_schema`. If no `if_schema` succeeds then the
2128
+ object is considered to have been validated. If one sets `if_schemaN`
2129
+ equal to `anything` then this serves as a catch all.
2130
+ """
2131
+
1662
2132
  args: tuple[tuple[object, object], ...]
1663
2133
 
1664
2134
  def __init__(self, *args: tuple[object, object]) -> None:
2135
+ """
2136
+ :param args: list of tuples pairing `if_schemas` with `then_schemas`
2137
+
2138
+ :raises SchemaError: exception thrown when the schema definition is
2139
+ found to contain an error
2140
+ """
1665
2141
  for c in args:
1666
2142
  if not isinstance(c, tuple) or len(c) != 2:
1667
2143
  raise SchemaError(f"{repr(c)} is not a tuple of length two")
@@ -1672,10 +2148,12 @@ class cond:
1672
2148
 
1673
2149
 
1674
2150
  class _fields(compiled_schema):
1675
- d: dict[str, compiled_schema]
2151
+ d: dict[str | optional_key[str], compiled_schema]
1676
2152
 
1677
2153
  def __init__(
1678
- self, d: Mapping[str, object], _deferred_compiles: _mapping | None = None
2154
+ self,
2155
+ d: Mapping[str | optional_key[str], object],
2156
+ _deferred_compiles: _mapping | None = None,
1679
2157
  ) -> None:
1680
2158
  self.d = {}
1681
2159
  for k, v in d.items():
@@ -1683,31 +2161,56 @@ class _fields(compiled_schema):
1683
2161
 
1684
2162
  def __validate__(
1685
2163
  self,
1686
- object_: object,
2164
+ obj: object,
1687
2165
  name: str = "object",
1688
2166
  strict: bool = True,
1689
2167
  subs: Mapping[str, object] = {},
1690
2168
  ) -> str:
1691
2169
  for k, v in self.d.items():
1692
2170
  name_ = f"{name}.{k}"
1693
- if not hasattr(object_, k):
2171
+ if not isinstance(k, optional_key) and not hasattr(obj, k):
1694
2172
  return f"{name_} is missing"
2173
+ if isinstance(k, optional_key):
2174
+ k_ = k.key
2175
+ else:
2176
+ k_ = k
1695
2177
  ret = self.d[k].__validate__(
1696
- getattr(object_, k), name=name_, strict=strict, subs=subs
2178
+ getattr(obj, k_), name=name_, strict=strict, subs=subs
1697
2179
  )
1698
2180
  if ret != "":
1699
2181
  return ret
1700
2182
  return ""
1701
2183
 
1702
2184
 
1703
- class fields:
1704
- def __init__(self, d: object) -> None:
2185
+ class fields(wrapper):
2186
+ """
2187
+ Matches Python objects with attributes `field1, field2, ..., fieldN` whose
2188
+ corresponding values should validate against `schema1, schema2, ...,
2189
+ schemaN` respectively
2190
+ """
2191
+
2192
+ d: dict[str | optional_key[str], object]
2193
+
2194
+ def __init__(self, d: Mapping[str | optional_key[str], object]) -> None:
2195
+ """
2196
+ :param d: a dictionary associating fields with schemas
2197
+
2198
+ :raises SchemaError: exception thrown when the schema definition is
2199
+ found to contain an error
2200
+ """
2201
+ self.d = {}
1705
2202
  if not isinstance(d, Mapping):
1706
2203
  raise SchemaError(f"{repr(d)} is not a Mapping")
1707
- for k in d:
1708
- if not isinstance(k, str):
1709
- raise SchemaError(f"key {repr(k)} in {repr(d)} is not a string")
1710
- self.d = d
2204
+ for k, v in d.items():
2205
+ if not isinstance(k, str) and not isinstance(k, optional_key):
2206
+ raise SchemaError(
2207
+ f"key {repr(k)} in {repr(d)} is not an instance of"
2208
+ " optional_key and not a string"
2209
+ )
2210
+ if isinstance(k, str) and k[-1] == "?":
2211
+ self.d[optional_key(k[:-1])] = v
2212
+ else:
2213
+ self.d[k] = v
1711
2214
 
1712
2215
  def __compile__(self, _deferred_compiles: _mapping | None = None) -> _fields:
1713
2216
  return _fields(self.d, _deferred_compiles=_deferred_compiles)
@@ -1739,25 +2242,28 @@ class _filter(compiled_schema):
1739
2242
 
1740
2243
  def __validate__(
1741
2244
  self,
1742
- object_: object,
2245
+ obj: object,
1743
2246
  name: str = "object",
1744
2247
  strict: bool = True,
1745
2248
  subs: Mapping[str, object] = {},
1746
2249
  ) -> str:
1747
2250
  try:
1748
- object_ = self.filter(object_)
2251
+ obj = self.filter(obj)
1749
2252
  except Exception as e:
1750
2253
  return (
1751
2254
  f"Applying {self.filter_name} to {name} "
1752
- f"(value: {_c(object_)}) failed: {str(e)}"
2255
+ f"(value: {_c(obj)}) failed: {str(e)}"
1753
2256
  )
1754
2257
  name = f"{self.filter_name}({name})"
1755
- return self.schema.__validate__(
1756
- object_, name="object", strict=strict, subs=subs
1757
- )
2258
+ return self.schema.__validate__(obj, name="object", strict=strict, subs=subs)
1758
2259
 
1759
2260
 
1760
- class filter:
2261
+ class filter(wrapper):
2262
+ """
2263
+ Applies `callable` to the object and validates the result with `schema`.
2264
+ If the callable throws an exception then validation fails.
2265
+ """
2266
+
1761
2267
  filter: Callable[[Any], object]
1762
2268
  schema: object
1763
2269
  filter_name: str | None
@@ -1768,6 +2274,15 @@ class filter:
1768
2274
  schema: object,
1769
2275
  filter_name: str | None = None,
1770
2276
  ) -> None:
2277
+ """
2278
+ :param filter: the filter to apply to the object
2279
+ :param schema: the schema used for validation once the filter has been
2280
+ applied
2281
+ :param filter_name: common name to refer to the filter
2282
+
2283
+ :raises SchemaError: exception thrown when the schema definition is
2284
+ found to contain an error
2285
+ """
1771
2286
  if filter_name is not None and not isinstance(filter_name, str):
1772
2287
  raise SchemaError("The filter name is not a string")
1773
2288
  if not callable(filter):
@@ -1798,16 +2313,16 @@ class _type(compiled_schema):
1798
2313
 
1799
2314
  def __validate__(
1800
2315
  self,
1801
- object_: object,
2316
+ obj: object,
1802
2317
  name: str = "object",
1803
2318
  strict: bool = True,
1804
2319
  subs: Mapping[str, object] = {},
1805
2320
  ) -> str:
1806
2321
  try:
1807
- if self.schema == float and isinstance(object_, int):
2322
+ if self.schema == float and isinstance(obj, int):
1808
2323
  return ""
1809
- if not isinstance(object_, self.schema):
1810
- return _wrong_type_message(object_, name, self.schema.__name__)
2324
+ if not isinstance(obj, self.schema):
2325
+ return _wrong_type_message(obj, name, self.schema.__name__)
1811
2326
  else:
1812
2327
  return ""
1813
2328
  except Exception as e:
@@ -1815,29 +2330,29 @@ class _type(compiled_schema):
1815
2330
 
1816
2331
  def __validate_float__(
1817
2332
  self,
1818
- object_: object,
2333
+ obj: object,
1819
2334
  name: str = "object",
1820
2335
  strict: bool = True,
1821
2336
  subs: Mapping[str, object] = {},
1822
2337
  ) -> str:
1823
2338
  # consider int as a subtype of float
1824
- if isinstance(object_, (int, float)):
2339
+ if isinstance(obj, (int, float)):
1825
2340
  return ""
1826
2341
  else:
1827
- return _wrong_type_message(object_, name, "float")
2342
+ return _wrong_type_message(obj, name, "float")
1828
2343
 
1829
2344
  def __validate_complex__(
1830
2345
  self,
1831
- object_: object,
2346
+ obj: object,
1832
2347
  name: str = "object",
1833
2348
  strict: bool = True,
1834
2349
  subs: Mapping[str, object] = {},
1835
2350
  ) -> str:
1836
2351
  # consider int, float as subtypes of complex
1837
- if isinstance(object_, (int, float, complex)):
2352
+ if isinstance(obj, (int, float, complex)):
1838
2353
  return ""
1839
2354
  else:
1840
- return _wrong_type_message(object_, name, "complex")
2355
+ return _wrong_type_message(obj, name, "complex")
1841
2356
 
1842
2357
  def __str__(self) -> str:
1843
2358
  return self.schema.__name__
@@ -1871,15 +2386,15 @@ class _sequence(compiled_schema):
1871
2386
 
1872
2387
  def __validate__(
1873
2388
  self,
1874
- object_: object,
2389
+ obj: object,
1875
2390
  name: str = "object",
1876
2391
  strict: bool = True,
1877
2392
  subs: Mapping[str, object] = {},
1878
2393
  ) -> str:
1879
- if not isinstance(object_, self.type_schema):
1880
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2394
+ if not isinstance(obj, self.type_schema):
2395
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
1881
2396
  ls = len(self.schema)
1882
- lo = len(object_)
2397
+ lo = len(obj)
1883
2398
  if strict:
1884
2399
  if lo > ls:
1885
2400
  return f"{name}[{ls}] is not in the schema"
@@ -1887,32 +2402,32 @@ class _sequence(compiled_schema):
1887
2402
  return f"{name}[{lo}] is missing"
1888
2403
  for i in range(ls):
1889
2404
  name_ = f"{name}[{i}]"
1890
- ret = self.schema[i].__validate__(object_[i], name_, strict, subs)
2405
+ ret = self.schema[i].__validate__(obj[i], name_, strict, subs)
1891
2406
  if ret != "":
1892
2407
  return ret
1893
2408
  return ""
1894
2409
 
1895
2410
  def __validate_ellipsis__(
1896
2411
  self,
1897
- object_: object,
2412
+ obj: object,
1898
2413
  name: str = "object",
1899
2414
  strict: bool = True,
1900
2415
  subs: Mapping[str, object] = {},
1901
2416
  ) -> str:
1902
- if not isinstance(object_, self.type_schema):
1903
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2417
+ if not isinstance(obj, self.type_schema):
2418
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
1904
2419
  ls = len(self.schema)
1905
- lo = len(object_)
2420
+ lo = len(obj)
1906
2421
  if ls > lo:
1907
2422
  return f"{name}[{lo}] is missing"
1908
2423
  for i in range(ls):
1909
2424
  name_ = f"{name}[{i}]"
1910
- ret = self.schema[i].__validate__(object_[i], name_, strict, subs)
2425
+ ret = self.schema[i].__validate__(obj[i], name_, strict, subs)
1911
2426
  if ret != "":
1912
2427
  return ret
1913
2428
  for i in range(ls, lo):
1914
2429
  name_ = f"{name}[{i}]"
1915
- ret = self.fill.__validate__(object_[i], name_, strict, subs)
2430
+ ret = self.fill.__validate__(obj[i], name_, strict, subs)
1916
2431
  if ret != "":
1917
2432
  return ret
1918
2433
  return ""
@@ -1929,18 +2444,18 @@ class _const(compiled_schema):
1929
2444
  if isinstance(schema, float) and not strict_eq:
1930
2445
  setattr(self, "__validate__", close_to(schema).__validate__)
1931
2446
 
1932
- def message(self, name: str, object_: object) -> str:
1933
- return f"{name} (value:{_c(object_)}) is not equal to {repr(self.schema)}"
2447
+ def message(self, name: str, obj: object) -> str:
2448
+ return f"{name} (value:{_c(obj)}) is not equal to {repr(self.schema)}"
1934
2449
 
1935
2450
  def __validate__(
1936
2451
  self,
1937
- object_: object,
2452
+ obj: object,
1938
2453
  name: str = "object",
1939
2454
  strict: bool = True,
1940
2455
  subs: Mapping[str, object] = {},
1941
2456
  ) -> str:
1942
- if object_ != self.schema:
1943
- return self.message(name, object_)
2457
+ if obj != self.schema:
2458
+ return self.message(name, obj)
1944
2459
  return ""
1945
2460
 
1946
2461
  def __str__(self) -> str:
@@ -1960,18 +2475,18 @@ class _callable(compiled_schema):
1960
2475
 
1961
2476
  def __validate__(
1962
2477
  self,
1963
- object_: object,
2478
+ obj: object,
1964
2479
  name: str = "object",
1965
2480
  strict: bool = True,
1966
2481
  subs: Mapping[str, object] = {},
1967
2482
  ) -> str:
1968
2483
  try:
1969
- if self.schema(object_):
2484
+ if self.schema(obj):
1970
2485
  return ""
1971
2486
  else:
1972
- return _wrong_type_message(object_, name, self.__name__)
2487
+ return _wrong_type_message(obj, name, self.__name__)
1973
2488
  except Exception as e:
1974
- return _wrong_type_message(object_, name, self.__name__, str(e))
2489
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1975
2490
 
1976
2491
  def __str__(self) -> str:
1977
2492
  return str(self.schema)
@@ -2016,25 +2531,25 @@ class _dict(compiled_schema):
2016
2531
 
2017
2532
  def __validate__(
2018
2533
  self,
2019
- object_: object,
2534
+ obj: object,
2020
2535
  name: str = "object",
2021
2536
  strict: bool = True,
2022
2537
  subs: Mapping[str, object] = {},
2023
2538
  ) -> str:
2024
- if not isinstance(object_, self.type_schema):
2025
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2539
+ if not isinstance(obj, self.type_schema):
2540
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
2026
2541
 
2027
2542
  for k in self.min_keys:
2028
- if k not in object_:
2543
+ if k not in obj:
2029
2544
  name_ = f"{name}[{repr(k)}]"
2030
2545
  return f"{name_} is missing"
2031
2546
 
2032
- for k in object_:
2547
+ for k in obj:
2033
2548
  vals = []
2034
2549
  name_ = f"{name}[{repr(k)}]"
2035
2550
  if k in self.const_keys:
2036
2551
  val = self.schema[k].__validate__(
2037
- object_[k], name=name_, strict=strict, subs=subs
2552
+ obj[k], name=name_, strict=strict, subs=subs
2038
2553
  )
2039
2554
  if val == "":
2040
2555
  continue
@@ -2044,7 +2559,7 @@ class _dict(compiled_schema):
2044
2559
  for kk in self.other_keys:
2045
2560
  if kk.__validate__(k, name="key", strict=strict, subs=subs) == "":
2046
2561
  val = self.schema[kk].__validate__(
2047
- object_[k], name=name_, strict=strict, subs=subs
2562
+ obj[k], name=name_, strict=strict, subs=subs
2048
2563
  )
2049
2564
  if val == "":
2050
2565
  break
@@ -2085,27 +2600,27 @@ class _set(compiled_schema):
2085
2600
 
2086
2601
  def __validate_empty_set__(
2087
2602
  self,
2088
- object_: object,
2603
+ obj: object,
2089
2604
  name: str = "object",
2090
2605
  strict: bool = True,
2091
2606
  subs: Mapping[str, object] = {},
2092
2607
  ) -> str:
2093
- if not isinstance(object_, self.type_schema):
2094
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2095
- if len(object_) != 0:
2096
- return f"{name} (value:{_c(object_)}) is not empty"
2608
+ if not isinstance(obj, self.type_schema):
2609
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
2610
+ if len(obj) != 0:
2611
+ return f"{name} (value:{_c(obj)}) is not empty"
2097
2612
  return ""
2098
2613
 
2099
2614
  def __validate_singleton__(
2100
2615
  self,
2101
- object_: object,
2616
+ obj: object,
2102
2617
  name: str = "object",
2103
2618
  strict: bool = True,
2104
2619
  subs: Mapping[str, object] = {},
2105
2620
  ) -> str:
2106
- if not isinstance(object_, set):
2107
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2108
- for i, o in enumerate(object_):
2621
+ if not isinstance(obj, set):
2622
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
2623
+ for i, o in enumerate(obj):
2109
2624
  name_ = f"{name}{{{i}}}"
2110
2625
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
2111
2626
  if v != "":
@@ -2114,14 +2629,14 @@ class _set(compiled_schema):
2114
2629
 
2115
2630
  def __validate__(
2116
2631
  self,
2117
- object_: object,
2632
+ obj: object,
2118
2633
  name: str = "object",
2119
2634
  strict: bool = True,
2120
2635
  subs: Mapping[str, object] = {},
2121
2636
  ) -> str:
2122
- if not isinstance(object_, self.type_schema):
2123
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2124
- for i, o in enumerate(object_):
2637
+ if not isinstance(obj, self.type_schema):
2638
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
2639
+ for i, o in enumerate(obj):
2125
2640
  name_ = f"{name}{{{i}}}"
2126
2641
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
2127
2642
  if v != "":
@@ -2132,12 +2647,26 @@ class _set(compiled_schema):
2132
2647
  return str(self.schema_)
2133
2648
 
2134
2649
 
2135
- class protocol:
2136
- type_dict: dict[object, object]
2650
+ class protocol(wrapper):
2651
+ """
2652
+ An object matches the schema `protocol(schema, dict=False)` if `schema` is
2653
+ a class (or class like object) and its fields are annotated with schemas
2654
+ which validate the corresponding fields in the object.
2655
+ """
2656
+
2657
+ type_dict: dict[str | optional_key[str], object]
2137
2658
  dict: bool
2138
2659
  __name__: str
2139
2660
 
2140
2661
  def __init__(self, schema: object, dict: bool = False):
2662
+ """
2663
+ :param schema: a type annotated class (or class like object such as
2664
+ Protocol or TypedDict) serving as prototype
2665
+ :param dict: if `True` parse the object as a `dict`
2666
+
2667
+ :raises SchemaError: exception thrown when the schema does not support
2668
+ type_hints
2669
+ """
2141
2670
  if not isinstance(dict, bool):
2142
2671
  raise SchemaError("bool flag is not a bool")
2143
2672
  type_hints = _get_type_hints(schema)
@@ -2225,16 +2754,16 @@ class _Mapping(compiled_schema):
2225
2754
 
2226
2755
  def __validate__(
2227
2756
  self,
2228
- object_: object,
2757
+ obj: object,
2229
2758
  name: str = "object",
2230
2759
  strict: bool = True,
2231
2760
  subs: Mapping[str, object] = {},
2232
2761
  ) -> str:
2233
2762
 
2234
- if not isinstance(object_, self.type_schema):
2235
- return _wrong_type_message(object_, name, self.__name__)
2763
+ if not isinstance(obj, self.type_schema):
2764
+ return _wrong_type_message(obj, name, self.__name__)
2236
2765
 
2237
- for k, v in object_.items():
2766
+ for k, v in obj.items():
2238
2767
  _name = f"{name}[{repr(k)}]"
2239
2768
  message = self.key.__validate__(k, name=str(k), strict=strict, subs=subs)
2240
2769
  if message != "":
@@ -2265,17 +2794,17 @@ class _Container(compiled_schema):
2265
2794
 
2266
2795
  def __validate__(
2267
2796
  self,
2268
- object_: object,
2797
+ obj: object,
2269
2798
  name: str = "object",
2270
2799
  strict: bool = True,
2271
2800
  subs: Mapping[str, object] = {},
2272
2801
  ) -> str:
2273
2802
 
2274
- if not isinstance(object_, self.type_schema):
2275
- return _wrong_type_message(object_, name, self.type_schema.__name__)
2803
+ if not isinstance(obj, self.type_schema):
2804
+ return _wrong_type_message(obj, name, self.type_schema.__name__)
2276
2805
 
2277
2806
  try:
2278
- for i, o in enumerate(object_):
2807
+ for i, o in enumerate(obj):
2279
2808
  _name = f"{name}[{i}]"
2280
2809
  message = self.schema.__validate__(
2281
2810
  o, name=_name, strict=strict, subs=subs
@@ -2283,7 +2812,7 @@ class _Container(compiled_schema):
2283
2812
  if message != "":
2284
2813
  return message
2285
2814
  except Exception as e:
2286
- return _wrong_type_message(object_, name, self.__name__, explanation=str(e))
2815
+ return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
2287
2816
 
2288
2817
  return ""
2289
2818