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

give more context to custom schemas #165

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Colour(fields.Field):
r = "%02X" % (r,)
g = "%02X" % (g,)
b = "%02X" % (b,)
return '#' + r + g + b
return '#' + r + g + b

class Gender(fields.String):
def _jsonschema_type_mapping(self):
Expand All @@ -196,6 +196,44 @@ json_schema = JSONSchema()
json_schema.dump(schema)
```

##### Corner Case: Recursive Custom Types with Custom Containers

marshmallow-jsonschema supports recursive custom types, and it supports the case where recursion
occurs through a container, such as here where a `Colour` contains a list of its complements

```python
class Colour(Schema):
complements = fields.List(
fields.Nested("Colour")
)
```

Marshmallow built-in container types like `List` and `Tuple` are supported, as well as
the marshmallow-union extension. *However* if you define a custom container yourself, you'll
need to give your custom container's `_jsonschema_type_mapping` some additional context
to properly recognize the recusive relationship. Here's a contrived example where we used a
custom wrapper to hold complementary colours:

```python
class Complements(fields.Field):
complements: fields.List

def __init__(self, field):
self.complements = fields.List(field)
super().__init__()

# accept extra arguments here so this class can render
# a recursive $ref: #/definitions/Colour
def _jsonschema_type_mapping(self, json_schema, obj):
field_schema = json_schema._get_schema_for_field(obj, self.complements)
return field_schema

class Colour(Schema):
complements = Complements(
fields.Nested("Colour")
)
```


### React-JSONSchema-Form Extension

Expand Down
12 changes: 10 additions & 2 deletions marshmallow_jsonschema/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import decimal
import uuid
from enum import Enum
from inspect import isclass
from inspect import isclass, signature
import typing

from marshmallow import fields, missing, Schema, validate
Expand Down Expand Up @@ -254,7 +254,15 @@ def _get_python_type(self, field):
def _get_schema_for_field(self, obj, field):
"""Get schema and validators for field."""
if hasattr(field, "_jsonschema_type_mapping"):
schema = field._jsonschema_type_mapping()
sig = signature(field._jsonschema_type_mapping)
num_args = len(sig.parameters)
if num_args == 2:
# pass down extra context to nested field of
# this schema and obj context if the implementer
# explicitly expects it
schema = field._jsonschema_type_mapping(self, obj)
else:
schema = field._jsonschema_type_mapping()
elif "_jsonschema_type_mapping" in field.metadata:
schema = field.metadata["_jsonschema_type_mapping"]
else:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,3 +744,26 @@ class TestSchema(Schema):
assert (
len(data["definitions"]["TestSchema"]["properties"]["union_prop"]["anyOf"]) == 3
)

def test_recursive_custom_field():
class NoOpWrapper(fields.Field):
def __init__(self, field):
self.field = field
super().__init__()

def _jsonschema_type_mapping(self, json_schema, obj):
field_schema = json_schema._get_schema_for_field(obj, self.field)
return field_schema

class ContrivedSchema(Schema):
recursive = NoOpWrapper(
fields.Nested("ContrivedSchema"),
)

schema = ContrivedSchema()
dumped = validate_and_dump(schema)

assert dumped["definitions"]["ContrivedSchema"]["properties"]["recursive"] == {
"type": "object",
"$ref": "#/definitions/ContrivedSchema"
}