4
4
import contextlib
5
5
import inspect
6
6
import json
7
+ from __future__ import annotations
8
+ from typing import Any , Callable , DefaultDict , Dict , FrozenSet , Iterable , Iterator , List , Literal , Mapping , Optional , Sequence , Set , Tuple , Type , TypeAlias , TypeVar , Union
7
9
8
10
import jsonschema
11
+ from jsonschema .validators import RefResolver
9
12
import numpy as np
13
+ import numpy .typing as npt
10
14
import pandas as pd
11
15
12
16
16
20
# Individual schema classes can override this by setting the
17
21
# class-level _class_is_valid_at_instantiation attribute to False
18
22
DEBUG_MODE = True
23
+ GenericT = TypeVar ("GenericT" )
24
+ T = TypeVar ("T" , bound = "SchemaBase" )
25
+ AltairObj : TypeAlias = Union ["SchemaBase" , List [Any ], Tuple [Any ], npt .NDArray [Any ], Dict [Any , Any ], np .number [Any ], pd .Timestamp , np .datetime64 ]
19
26
20
-
21
- def enable_debug_mode ():
27
+ def enable_debug_mode () -> None :
22
28
global DEBUG_MODE
23
29
DEBUG_MODE = True
24
30
25
31
26
- def disable_debug_mode ():
32
+ def disable_debug_mode () -> None :
27
33
global DEBUG_MODE
28
34
DEBUG_MODE = True
29
35
30
36
31
37
@contextlib .contextmanager
32
- def debug_mode (arg ) :
38
+ def debug_mode (arg : bool ) -> Iterator [ Optional [ bool ]] :
33
39
global DEBUG_MODE
34
40
original = DEBUG_MODE
35
41
DEBUG_MODE = arg
@@ -39,9 +45,9 @@ def debug_mode(arg):
39
45
DEBUG_MODE = original
40
46
41
47
42
- def _subclasses (cls ) :
48
+ def _subclasses (cls : Type [ GenericT ]) -> Iterable [ Type [ GenericT ]] :
43
49
"""Breadth-first sequence of all classes which inherit from cls."""
44
- seen = set ()
50
+ seen : Set [ Type [ GenericT ]] = set ()
45
51
current_set = {cls }
46
52
while current_set :
47
53
seen |= current_set
@@ -50,7 +56,7 @@ def _subclasses(cls):
50
56
yield cls
51
57
52
58
53
- def _todict (obj , validate , context ) :
59
+ def _todict (obj : AltairObj , validate : Union [ bool , str ], context : Optional [ Dict [ Any , Any ]]) -> Union [ AltairObj , Dict [ Any , Any ], str , float ] :
54
60
"""Convert an object to a dict representation."""
55
61
if isinstance (obj , SchemaBase ):
56
62
return obj .to_dict (validate = validate , context = context )
@@ -72,9 +78,9 @@ def _todict(obj, validate, context):
72
78
return obj
73
79
74
80
75
- def _resolve_references (schema , root = None ):
81
+ def _resolve_references (schema : Mapping [ str , Any ], root : Optional [ Mapping [ str , Any ]] = None ) -> Mapping [ str , Any ] :
76
82
"""Resolve schema references."""
77
- resolver = jsonschema .RefResolver .from_schema (root or schema )
83
+ resolver : RefResolver = jsonschema .RefResolver .from_schema (root or schema )
78
84
while "$ref" in schema :
79
85
with resolver .resolving (schema ["$ref" ]) as resolved :
80
86
schema = resolved
@@ -84,12 +90,12 @@ def _resolve_references(schema, root=None):
84
90
class SchemaValidationError (jsonschema .ValidationError ):
85
91
"""A wrapper for jsonschema.ValidationError with friendlier traceback"""
86
92
87
- def __init__ (self , obj , err ) :
93
+ def __init__ (self , obj : Any , err : jsonschema . ValidationError ) -> None :
88
94
super (SchemaValidationError , self ).__init__ (** self ._get_contents (err ))
89
95
self .obj = obj
90
96
91
97
@staticmethod
92
- def _get_contents (err ) :
98
+ def _get_contents (err : jsonschema . ValidationError ) -> Dict [ str , Any ] :
93
99
"""Get a dictionary with the contents of a ValidationError"""
94
100
try :
95
101
# works in jsonschema 2.3 or later
@@ -104,7 +110,7 @@ def _get_contents(err):
104
110
contents = {key : getattr (err , key ) for key in spec .args [1 :]}
105
111
return contents
106
112
107
- def __str__ (self ):
113
+ def __str__ (self ) -> str :
108
114
cls = self .obj .__class__
109
115
schema_path = ["{}.{}" .format (cls .__module__ , cls .__name__ )]
110
116
schema_path .extend (self .schema_path )
@@ -128,12 +134,12 @@ class UndefinedType(object):
128
134
129
135
__instance = None
130
136
131
- def __new__ (cls , * args , ** kwargs ) :
137
+ def __new__ (cls , * args : Any , ** kwargs : Any ) -> UndefinedType :
132
138
if not isinstance (cls .__instance , cls ):
133
139
cls .__instance = object .__new__ (cls , * args , ** kwargs )
134
140
return cls .__instance
135
141
136
- def __repr__ (self ):
142
+ def __repr__ (self ) -> Literal [ "Undefined" ] :
137
143
return "Undefined"
138
144
139
145
@@ -147,12 +153,12 @@ class SchemaBase(object):
147
153
the _rootschema class attribute) which is used for validation.
148
154
"""
149
155
150
- _schema = None
151
- _rootschema = None
156
+ _schema : Optional [ Mapping [ str , Any ]] = None
157
+ _rootschema : Optional [ Mapping [ str , Any ]] = None
152
158
_class_is_valid_at_instantiation = True
153
159
_validator = jsonschema .Draft7Validator
154
160
155
- def __init__ (self , * args , ** kwds ) :
161
+ def __init__ (self , * args : Any , ** kwds : Any ) -> None :
156
162
# Two valid options for initialization, which should be handled by
157
163
# derived classes:
158
164
# - a single arg with no kwds, for, e.g. {'type': 'string'}
@@ -176,7 +182,7 @@ def __init__(self, *args, **kwds):
176
182
if DEBUG_MODE and self ._class_is_valid_at_instantiation :
177
183
self .to_dict (validate = True )
178
184
179
- def copy (self , deep = True , ignore = ()):
185
+ def copy (self : T , deep : Union [ bool , Sequence [ Any ]] = True , ignore : Sequence [ Any ] = ()) -> T :
180
186
"""Return a copy of the object
181
187
182
188
Parameters
@@ -191,7 +197,7 @@ def copy(self, deep=True, ignore=()):
191
197
only stored by reference.
192
198
"""
193
199
194
- def _shallow_copy (obj ) :
200
+ def _shallow_copy (obj : T ) -> T :
195
201
if isinstance (obj , SchemaBase ):
196
202
return obj .copy (deep = False )
197
203
elif isinstance (obj , list ):
@@ -201,7 +207,7 @@ def _shallow_copy(obj):
201
207
else :
202
208
return obj
203
209
204
- def _deep_copy (obj , ignore = ()):
210
+ def _deep_copy (obj : T , ignore : Sequence [ Any ] = ()) -> T :
205
211
if isinstance (obj , SchemaBase ):
206
212
args = tuple (_deep_copy (arg ) for arg in obj ._args )
207
213
kwds = {
@@ -221,7 +227,7 @@ def _deep_copy(obj, ignore=()):
221
227
return obj
222
228
223
229
try :
224
- deep = list (deep )
230
+ deep : List [ Any ] = list (deep )
225
231
except TypeError :
226
232
deep_is_list = False
227
233
else :
@@ -237,36 +243,36 @@ def _deep_copy(obj, ignore=()):
237
243
copy [attr ] = _shallow_copy (copy ._get (attr ))
238
244
return copy
239
245
240
- def _get (self , attr , default = Undefined ):
246
+ def _get (self , attr : str , default : Any = Undefined ) -> Any :
241
247
"""Get an attribute, returning default if not present."""
242
- attr = self ._kwds .get (attr , Undefined )
248
+ attr : Any = self ._kwds .get (attr , Undefined )
243
249
if attr is Undefined :
244
250
attr = default
245
251
return attr
246
252
247
- def __getattr__ (self , attr ) :
253
+ def __getattr__ (self , attr : str ) -> Any :
248
254
# reminder: getattr is called after the normal lookups
249
255
if attr == "_kwds" :
250
256
raise AttributeError ()
251
257
if attr in self ._kwds :
252
258
return self ._kwds [attr ]
253
259
else :
254
260
try :
255
- _getattr = super (SchemaBase , self ).__getattr__
261
+ _getattr : Callable [[ str ], Any ] = super (SchemaBase , self ).__getattr__
256
262
except AttributeError :
257
263
_getattr = super (SchemaBase , self ).__getattribute__
258
264
return _getattr (attr )
259
265
260
- def __setattr__ (self , item , val ) :
266
+ def __setattr__ (self , item : str , val : Any ) -> None :
261
267
self ._kwds [item ] = val
262
268
263
- def __getitem__ (self , item ) :
269
+ def __getitem__ (self , item : str ) -> Any :
264
270
return self ._kwds [item ]
265
271
266
- def __setitem__ (self , item , val ) :
272
+ def __setitem__ (self , item : str , val : Any ) -> None :
267
273
self ._kwds [item ] = val
268
274
269
- def __repr__ (self ):
275
+ def __repr__ (self ) -> str :
270
276
if self ._kwds :
271
277
args = (
272
278
"{}: {!r}" .format (key , val )
@@ -280,14 +286,14 @@ def __repr__(self):
280
286
else :
281
287
return "{}({!r})" .format (self .__class__ .__name__ , self ._args [0 ])
282
288
283
- def __eq__ (self , other ) :
289
+ def __eq__ (self , other : Any ) -> bool :
284
290
return (
285
291
type (self ) is type (other )
286
292
and self ._args == other ._args
287
293
and self ._kwds == other ._kwds
288
294
)
289
295
290
- def to_dict (self , validate = True , ignore = None , context = None ):
296
+ def to_dict (self , validate : Union [ bool , str ] = True , ignore : Optional [ Sequence [ str ]] = None , context : Optional [ Dict [ Any , Any ]] = None ) -> Union [ AltairObj , Dict [ Any , Any ], str , float ] :
291
297
"""Return a dictionary representation of the object
292
298
293
299
Parameters
@@ -341,8 +347,8 @@ def to_dict(self, validate=True, ignore=None, context=None):
341
347
return result
342
348
343
349
def to_json (
344
- self , validate = True , ignore = [] , context = {} , indent = 2 , sort_keys = True , ** kwargs
345
- ):
350
+ self , validate : Union [ bool , str ] = True , ignore : Sequence [ str ] = () , context : Optional [ Dict [ Any , Any ]] = None , indent : int = 2 , sort_keys : bool = True , ** kwargs : Any
351
+ ) -> str :
346
352
"""Emit the JSON representation for this object as a string.
347
353
348
354
Parameters
@@ -370,16 +376,18 @@ def to_json(
370
376
spec : string
371
377
The JSON specification of the chart object.
372
378
"""
379
+ if not context :
380
+ context = {}
373
381
dct = self .to_dict (validate = validate , ignore = ignore , context = context )
374
382
return json .dumps (dct , indent = indent , sort_keys = sort_keys , ** kwargs )
375
383
376
384
@classmethod
377
- def _default_wrapper_classes (cls ):
385
+ def _default_wrapper_classes (cls ) -> Iterable [ Type [ SchemaBase ]] :
378
386
"""Return the set of classes used within cls.from_dict()"""
379
387
return _subclasses (SchemaBase )
380
388
381
389
@classmethod
382
- def from_dict (cls , dct , validate = True , _wrapper_classes = None ):
390
+ def from_dict (cls : Type [ T ] , dct : Mapping [ str , Any ], validate : bool = True , _wrapper_classes : Optional [ Union [ Iterable [ Type [ SchemaBase ]], Iterable [ Type [ T ]]]] = None ) -> T :
383
391
"""Construct class from a dictionary representation
384
392
385
393
Parameters
@@ -411,7 +419,7 @@ def from_dict(cls, dct, validate=True, _wrapper_classes=None):
411
419
return converter .from_dict (dct , cls )
412
420
413
421
@classmethod
414
- def from_json (cls , json_string , validate = True , ** kwargs ) :
422
+ def from_json (cls : Type [ T ] , json_string : str , validate : bool = True , ** kwargs : Any ) -> T :
415
423
"""Instantiate the object from a valid JSON string
416
424
417
425
Parameters
@@ -432,42 +440,42 @@ def from_json(cls, json_string, validate=True, **kwargs):
432
440
return cls .from_dict (dct , validate = validate )
433
441
434
442
@classmethod
435
- def validate (cls , instance , schema = None ):
443
+ def validate (cls : Type [ T ] , instance : Any , schema : Optional [ Mapping [ str , Any ]] = None ) -> None :
436
444
"""
437
445
Validate the instance against the class schema in the context of the
438
446
rootschema.
439
447
"""
440
448
if schema is None :
441
449
schema = cls ._schema
442
- resolver = jsonschema .RefResolver .from_schema (cls ._rootschema or cls ._schema )
450
+ resolver : RefResolver = jsonschema .RefResolver .from_schema (cls ._rootschema or cls ._schema )
443
451
return jsonschema .validate (
444
452
instance , schema , cls = cls ._validator , resolver = resolver
445
453
)
446
454
447
455
@classmethod
448
- def resolve_references (cls , schema = None ):
456
+ def resolve_references (cls , schema : Optional [ Mapping [ str , Any ]] = None ) -> Mapping [ str , Any ] :
449
457
"""Resolve references in the context of this object's schema or root schema."""
450
458
return _resolve_references (
451
459
schema = (schema or cls ._schema ),
452
460
root = (cls ._rootschema or cls ._schema or schema ),
453
461
)
454
462
455
463
@classmethod
456
- def validate_property (cls , name , value , schema = None ):
464
+ def validate_property (cls , name : str , value : Any , schema : Optional [ Mapping [ str , Any ]] = None ) -> None :
457
465
"""
458
466
Validate a property against property schema in the context of the
459
467
rootschema
460
468
"""
461
469
value = _todict (value , validate = False , context = {})
462
470
props = cls .resolve_references (schema or cls ._schema ).get ("properties" , {})
463
- resolver = jsonschema .RefResolver .from_schema (cls ._rootschema or cls ._schema )
471
+ resolver : RefResolver = jsonschema .RefResolver .from_schema (cls ._rootschema or cls ._schema )
464
472
return jsonschema .validate (value , props .get (name , {}), resolver = resolver )
465
473
466
- def __dir__ (self ):
474
+ def __dir__ (self ) -> List [ str ] :
467
475
return list (self ._kwds .keys ())
468
476
469
477
470
- def _passthrough (* args , ** kwds ) :
478
+ def _passthrough (* args : Any , ** kwds : Any ) -> Union [ Any , Dict [ str , Any ]] :
471
479
return args [0 ] if args else kwds
472
480
473
481
@@ -481,16 +489,16 @@ class _FromDict(object):
481
489
482
490
_hash_exclude_keys = ("definitions" , "title" , "description" , "$schema" , "id" )
483
491
484
- def __init__ (self , class_list ) :
492
+ def __init__ (self , class_list : Iterable [ Type [ Any ]]) -> None :
485
493
# Create a mapping of a schema hash to a list of matching classes
486
494
# This lets us quickly determine the correct class to construct
487
- self .class_dict = collections .defaultdict (list )
495
+ self .class_dict : DefaultDict [ Any , List [ Any ] ] = collections .defaultdict (list )
488
496
for cls in class_list :
489
497
if cls ._schema is not None :
490
498
self .class_dict [self .hash_schema (cls ._schema )].append (cls )
491
499
492
500
@classmethod
493
- def hash_schema (cls , schema , use_json = True ):
501
+ def hash_schema (cls , schema : Mapping [ str , Any ], use_json : bool = True ) -> int :
494
502
"""
495
503
Compute a python hash for a nested dictionary which
496
504
properly handles dicts, lists, sets, and tuples.
@@ -513,7 +521,7 @@ def hash_schema(cls, schema, use_json=True):
513
521
return hash (s )
514
522
else :
515
523
516
- def _freeze (val ) :
524
+ def _freeze (val : Union [ Dict [ Any , Any ], Set [ Any ], Sequence [ Any ], GenericT ]) -> Union [ FrozenSet [ Any ], Tuple [ Any ], GenericT ] :
517
525
if isinstance (val , dict ):
518
526
return frozenset ((k , _freeze (v )) for k , v in val .items ())
519
527
elif isinstance (val , set ):
@@ -526,8 +534,8 @@ def _freeze(val):
526
534
return hash (_freeze (schema ))
527
535
528
536
def from_dict (
529
- self , dct , cls = None , schema = None , rootschema = None , default_class = _passthrough
530
- ):
537
+ self , dct : Union [ Mapping [ str , Any ], SchemaBase ], cls : Optional [ Type [ T ]] = None , schema : Optional [ Mapping [ str , Any ]] = None , rootschema : Optional [ Mapping [ str , Any ]] = None , default_class : Any = _passthrough
538
+ ) -> Union [ T , SchemaBase ] :
531
539
"""Construct an object from a dict representation"""
532
540
if (schema is None ) == (cls is None ):
533
541
raise ValueError ("Must provide either cls or schema, but not both." )
@@ -553,7 +561,7 @@ def from_dict(
553
561
if "anyOf" in schema or "oneOf" in schema :
554
562
schemas = schema .get ("anyOf" , []) + schema .get ("oneOf" , [])
555
563
for possible_schema in schemas :
556
- resolver = jsonschema .RefResolver .from_schema (rootschema )
564
+ resolver : RefResolver = jsonschema .RefResolver .from_schema (rootschema )
557
565
try :
558
566
jsonschema .validate (dct , possible_schema , resolver = resolver )
559
567
except jsonschema .ValidationError :
@@ -569,7 +577,7 @@ def from_dict(
569
577
if isinstance (dct , dict ):
570
578
# TODO: handle schemas for additionalProperties/patternProperties
571
579
props = schema .get ("properties" , {})
572
- kwds = {}
580
+ kwds : Mapping [ str , Any ] = {}
573
581
for key , val in dct .items ():
574
582
if key in props :
575
583
val = self .from_dict (val , schema = props [key ], rootschema = rootschema )
@@ -578,7 +586,7 @@ def from_dict(
578
586
579
587
elif isinstance (dct , list ):
580
588
item_schema = schema .get ("items" , {})
581
- dct = [
589
+ dct : List [ Union [ T , SchemaBase ]] = [
582
590
self .from_dict (val , schema = item_schema , rootschema = rootschema )
583
591
for val in dct
584
592
]
0 commit comments