Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to resolve #297 #301

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions python_jsonschema_objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,14 @@ def build_classes(
A namespace containing all the generated classes

"""
kw = {"strict": strict, "any_of": any_of}
builder = classbuilder.ClassBuilder(self.resolver)
opts = {"strict": strict, "any_of": any_of}
builder = classbuilder.ClassBuilder(self.resolver, opts)
for nm, defn in self.schema.get("definitions", {}).items():
resolved = self.resolver.lookup("#/definitions/" + nm)
uri = python_jsonschema_objects.util.resolve_ref_uri(
self.resolver._base_uri, "#/definitions/" + nm
)
builder.construct(uri, resolved.contents, **kw)
builder.construct(uri, resolved.contents)

if standardize_names:
name_transform = lambda t: inflection.camelize(
Expand All @@ -236,7 +236,7 @@ def build_classes(
nm = self.schema["title"] if "title" in self.schema else self.schema["$id"]
nm = inflection.parameterize(str(nm), "_")

builder.construct(nm, self.schema, **kw)
builder.construct(nm, self.schema)
self._resolved = builder.resolved

classes = {}
Expand Down
56 changes: 34 additions & 22 deletions python_jsonschema_objects/classbuilder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import collections.abc
from urllib.parse import urldefrag, urljoin
import typing
import copy
import itertools
import logging
Expand Down Expand Up @@ -452,11 +452,19 @@ def __call__(self, *a, **kw):
)


class ClassBuilderOptions(typing.TypedDict):
strict: bool
any_of: str


class ClassBuilder(object):
def __init__(self, resolver: referencing._core.Resolver):
def __init__(
self, resolver: referencing._core.Resolver, options: ClassBuilderOptions
):
self.resolver = resolver
self.resolved = {}
self.under_construction = set()
self.options = options

def expand_references(self, source_uri, iterable):
"""Give an iterable of jsonschema descriptors, expands any
Expand Down Expand Up @@ -494,7 +502,7 @@ def resolve_type(self, ref, source):
)
resolved = self.resolver.lookup(uri)
if resolved.resolver != self.resolver:
sub_cb = ClassBuilder(resolved.resolver)
sub_cb = ClassBuilder(resolved.resolver, self.options)
self.resolved[uri] = sub_cb.construct(
uri, resolved.contents, (ProtocolBase,)
)
Expand All @@ -505,34 +513,38 @@ def resolve_type(self, ref, source):

return self.resolved[uri]

def construct(self, uri, *args, **kw):
def construct(
self, uri: str, clsdata: typing.Mapping[str, any], parent=(ProtocolBase,)
):
"""Wrapper to debug things"""
logger.debug(util.lazy_format("Constructing {0}", uri))
if ("override" not in kw or kw["override"] is False) and uri in self.resolved:
if uri in self.resolved:
logger.debug(util.lazy_format("Using existing {0}", uri))
assert self.resolved[uri] is not None
return self.resolved[uri]
else:
ret = self._construct(uri, *args, **kw)
ret = self._construct(uri, clsdata, parent=parent)
logger.debug(util.lazy_format("Constructed {0}", ret))

return ret

def _construct(self, uri, clsdata, parent=(ProtocolBase,), **kw):
def _construct(
self, uri: str, clsdata: typing.Mapping[str, any], parent=(ProtocolBase,)
):
if "anyOf" in clsdata:
if kw.get("any_of", None) is None:
if self.options.get("any_of", None) is None:
raise NotImplementedError(
"anyOf is not supported as bare property (workarounds available by setting any_of flag)"
)
if kw["any_of"] == "use-first":
if self.options["any_of"] == "use-first":
# Patch so the first anyOf becomes a single oneOf
clsdata["oneOf"] = [
clsdata["anyOf"].pop(0),
]
del clsdata["anyOf"]
else:
raise NotImplementedError(
f"anyOf workaround is not a recognized type (any_of = {kw['any_of']})"
f"anyOf workaround is not a recognized type (any_of = {self.options['any_of']})"
)

if "oneOf" in clsdata:
Expand All @@ -557,7 +569,7 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,), **kw):
elif util.safe_issubclass(p, ProtocolBase):
parents.append(p)

self.resolved[uri] = self._build_object(uri, clsdata, parents, **kw)
self.resolved[uri] = self._build_object(uri, clsdata, parents)
return self.resolved[uri]

elif "$ref" in clsdata:
Expand Down Expand Up @@ -611,7 +623,7 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,), **kw):
or clsdata.get("properties", None) is not None
or clsdata.get("additionalProperties", False)
):
self.resolved[uri] = self._build_object(uri, clsdata, parent, **kw)
self.resolved[uri] = self._build_object(uri, clsdata, parent)
return self.resolved[uri]
elif clsdata.get("type") in ("integer", "number", "string", "boolean", "null"):
self.resolved[uri] = self._build_literal(uri, clsdata)
Expand Down Expand Up @@ -654,7 +666,7 @@ def _build_literal(self, nm, clsdata):
"default",
"asldkfn24olkjalskdfn e;laishd;1loj;flkansd;iow;naosdinfe;lkamjsdfj",
)
is not "asldkfn24olkjalskdfn e;laishd;1loj;flkansd;iow;naosdinfe;lkamjsdfj"
!= "asldkfn24olkjalskdfn e;laishd;1loj;flkansd;iow;naosdinfe;lkamjsdfj"
else clsdata.get("const")
),
}
Expand All @@ -663,7 +675,7 @@ def _build_literal(self, nm, clsdata):

return cls

def _build_object(self, nm, clsdata, parents, **kw):
def _build_object(self, nm, clsdata, parents):
logger.debug(util.lazy_format("Building object {0}", nm))

# To support circular references, we tag objects that we're
Expand Down Expand Up @@ -713,7 +725,7 @@ def _build_object(self, nm, clsdata, parents, **kw):

if detail.get("type", None) == "object":
uri = "{0}/{1}_{2}".format(nm, prop, "<anonymous>")
self.resolved[uri] = self.construct(uri, detail, (ProtocolBase,), **kw)
self.resolved[uri] = self.construct(uri, detail, (ProtocolBase,))

props[prop] = make_property(
prop, {"type": self.resolved[uri]}, self.resolved[uri].__doc__
Expand Down Expand Up @@ -741,7 +753,7 @@ def _build_object(self, nm, clsdata, parents, **kw):
if "$ref" in detail["items"]:
typ = self.resolve_type(detail["items"]["$ref"], nm)
constraints = copy.copy(detail)
constraints["strict"] = kw.get("strict")
constraints["strict"] = self.options.get("strict")
propdata = {
"type": "array",
"validator": wrapper_types.ArrayWrapper.create(
Expand All @@ -760,10 +772,10 @@ def _build_object(self, nm, clsdata, parents, **kw):
)
)
else:
typ = self.construct(uri, detail["items"], **kw)
typ = self.construct(uri, detail["items"])

constraints = copy.copy(detail)
constraints["strict"] = kw.get("strict")
constraints["strict"] = self.options.get("strict")
propdata = {
"type": "array",
"validator": wrapper_types.ArrayWrapper.create(
Expand All @@ -774,7 +786,7 @@ def _build_object(self, nm, clsdata, parents, **kw):
except NotImplementedError:
typ = detail["items"]
constraints = copy.copy(detail)
constraints["strict"] = kw.get("strict")
constraints["strict"] = self.options.get("strict")
propdata = {
"type": "array",
"validator": wrapper_types.ArrayWrapper.create(
Expand All @@ -787,15 +799,15 @@ def _build_object(self, nm, clsdata, parents, **kw):
typs = []
for i, elem in enumerate(detail["items"]):
uri = "{0}/{1}/<anonymous_{2}>".format(nm, prop, i)
typ = self.construct(uri, elem, **kw)
typ = self.construct(uri, elem)
typs.append(typ)

props[prop] = make_property(prop, {"type": typs})

else:
desc = detail["description"] if "description" in detail else ""
uri = "{0}/{1}".format(nm, prop)
typ = self.construct(uri, detail, **kw)
typ = self.construct(uri, detail)

props[prop] = make_property(prop, {"type": typ}, desc)

Expand All @@ -821,7 +833,7 @@ def _build_object(self, nm, clsdata, parents, **kw):

props["__required__"] = required
props["__has_default__"] = defaults
if required and kw.get("strict"):
if required and self.options.get("strict"):
props["__strict__"] = True

props["__title__"] = clsdata.get("title")
Expand Down
Loading
Loading