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