@@ -37,6 +37,54 @@ def __call__(
37
37
...
38
38
39
39
40
+ def _detect_or_error (contents : D ) -> Specification [D ]:
41
+ if not isinstance (contents , Mapping ):
42
+ raise exceptions .CannotDetermineSpecification (contents )
43
+
44
+ jsonschema_dialect_id = contents .get ("$schema" ) # type: ignore[reportUnknownMemberType]
45
+ if jsonschema_dialect_id is None :
46
+ raise exceptions .CannotDetermineSpecification (contents )
47
+
48
+ from referencing .jsonschema import specification_with
49
+
50
+ return specification_with (
51
+ jsonschema_dialect_id , # type: ignore[reportUnknownArgumentType]
52
+ )
53
+
54
+
55
+ def _detect_or_default (
56
+ default : Specification [D ],
57
+ ) -> Callable [[D ], Specification [D ]]:
58
+ def _detect (contents : D ) -> Specification [D ]:
59
+ if not isinstance (contents , Mapping ):
60
+ return default
61
+
62
+ jsonschema_dialect_id = contents .get ("$schema" ) # type: ignore[reportUnknownMemberType]
63
+ if jsonschema_dialect_id is None :
64
+ return default
65
+
66
+ from referencing .jsonschema import specification_with
67
+
68
+ return specification_with (
69
+ jsonschema_dialect_id , # type: ignore[reportUnknownArgumentType]
70
+ default = default ,
71
+ )
72
+
73
+ return _detect
74
+
75
+
76
+ class _SpecificationDetector :
77
+ def __get__ (
78
+ self ,
79
+ instance : Specification [D ] | None ,
80
+ cls : type [Specification [D ]],
81
+ ) -> Callable [[D ], Specification [D ]]:
82
+ if instance is None :
83
+ return _detect_or_error
84
+ else :
85
+ return _detect_or_default (instance )
86
+
87
+
40
88
@frozen
41
89
class Specification (Generic [D ]):
42
90
"""
@@ -70,6 +118,39 @@ class Specification(Generic[D]):
70
118
#: nor internal identifiers.
71
119
OPAQUE : ClassVar [Specification [Any ]]
72
120
121
+ #: Attempt to discern which specification applies to the given contents.
122
+ #:
123
+ #: May be called either as an instance method or as a class method, with
124
+ #: slightly different behavior in the following case:
125
+ #:
126
+ #: Recall that not all contents contains enough internal information about
127
+ #: which specification it is written for -- the JSON Schema ``{}``,
128
+ #: for instance, is valid under many different dialects and may be
129
+ #: interpreted as any one of them.
130
+ #:
131
+ #: When this method is used as an instance method (i.e. called on a
132
+ #: specific specification), that specification is used as the default
133
+ #: if the given contents are unidentifiable.
134
+ #:
135
+ #: On the other hand when called as a class method, an error is raised.
136
+ #:
137
+ #: To reiterate, ``DRAFT202012.detect({})`` will return ``DRAFT202012``
138
+ #: whereas the class method ``Specification.detect({})`` will raise an
139
+ #: error.
140
+ #:
141
+ #: (Note that of course ``DRAFT202012.detect(...)`` may return some other
142
+ #: specification when given a schema which *does* identify as being for
143
+ #: another version).
144
+ #:
145
+ #: Raises:
146
+ #:
147
+ #: `CannotDetermineSpecification`
148
+ #:
149
+ #: if the given contents don't have any discernible
150
+ #: information which could be used to guess which
151
+ #: specification they identify as
152
+ detect = _SpecificationDetector ()
153
+
73
154
def __repr__ (self ) -> str :
74
155
return f"<Specification name={ self .name !r} >"
75
156
@@ -113,10 +194,11 @@ class Resource(Generic[D]):
113
194
def from_contents (
114
195
cls ,
115
196
contents : D ,
116
- default_specification : Specification [D ] | _Unset = _UNSET ,
197
+ default_specification : type [Specification [D ]]
198
+ | Specification [D ] = Specification ,
117
199
) -> Resource [D ]:
118
200
"""
119
- Attempt to discern which specification applies to the given contents.
201
+ Create a resource guessing which specification applies to the contents.
120
202
121
203
Raises:
122
204
@@ -126,20 +208,8 @@ def from_contents(
126
208
information which could be used to guess which
127
209
specification they identify as
128
210
"""
129
- specification = default_specification
130
- if isinstance (contents , Mapping ):
131
- jsonschema_dialect_id = contents .get ("$schema" ) # type: ignore[reportUnknownMemberType]
132
- if jsonschema_dialect_id is not None :
133
- from referencing .jsonschema import specification_with
134
-
135
- specification = specification_with (
136
- jsonschema_dialect_id , # type: ignore[reportUnknownArgumentType]
137
- default = default_specification ,
138
- )
139
-
140
- if specification is _UNSET :
141
- raise exceptions .CannotDetermineSpecification (contents )
142
- return cls (contents = contents , specification = specification ) # type: ignore[reportUnknownArgumentType]
211
+ specification = default_specification .detect (contents )
212
+ return specification .create_resource (contents = contents )
143
213
144
214
@classmethod
145
215
def opaque (cls , contents : D ) -> Resource [D ]:
0 commit comments