@@ -37,6 +37,54 @@ def __call__(
3737 ...
3838
3939
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+
4088@frozen
4189class Specification (Generic [D ]):
4290 """
@@ -70,6 +118,39 @@ class Specification(Generic[D]):
70118 #: nor internal identifiers.
71119 OPAQUE : ClassVar [Specification [Any ]]
72120
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+
73154 def __repr__ (self ) -> str :
74155 return f"<Specification name={ self .name !r} >"
75156
@@ -113,10 +194,11 @@ class Resource(Generic[D]):
113194 def from_contents (
114195 cls ,
115196 contents : D ,
116- default_specification : Specification [D ] | _Unset = _UNSET ,
197+ default_specification : type [Specification [D ]]
198+ | Specification [D ] = Specification ,
117199 ) -> Resource [D ]:
118200 """
119- Attempt to discern which specification applies to the given contents.
201+ Create a resource guessing which specification applies to the contents.
120202
121203 Raises:
122204
@@ -126,20 +208,8 @@ def from_contents(
126208 information which could be used to guess which
127209 specification they identify as
128210 """
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 )
143213
144214 @classmethod
145215 def opaque (cls , contents : D ) -> Resource [D ]:
0 commit comments