Skip to content

Commit 1d412c9

Browse files
committed
Support for complex json-schemas with external $ref
1 parent 93b8bd8 commit 1d412c9

File tree

4 files changed

+132
-14
lines changed

4 files changed

+132
-14
lines changed

datamodel/abstract.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Meta:
2323
datasource: Optional[str] = None
2424
connection: Optional[Callable] = None
2525
remove_nulls: bool = False
26+
endpoint: str = ""
2627

2728

2829
def set_connection(cls, conn: Callable):

datamodel/base.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,12 @@ def _get_ref_info(_type, field):
6161
}
6262
}
6363
elif isinstance(_type, ModelMeta):
64+
_schema = _type.schema(as_dict=True)
6465
return {
6566
"type": "object",
66-
"enum_type": None,
67-
"schema": _type.schema(as_dict=True),
68-
"ref": field.metadata.get('fk').split("|") if 'fk' in field.metadata else _type.schema(as_dict=True)['$id']
67+
"schema": _schema,
68+
"$ref": _schema.get('$id', f"/schemas/{_type.__name__}"),
69+
"columns": field.metadata.get('fk').split("|") if 'fk' in field.metadata else []
6970
}
7071
return None
7172

@@ -411,6 +412,11 @@ def schema(cls, as_dict=False):
411412
except Exception:
412413
pass
413414

415+
try:
416+
endpoint = cls.Meta.endpoint
417+
except AttributeError:
418+
endpoint = None
419+
414420
schema = cls.Meta.schema
415421
table = cls.Meta.name.lower() if cls.Meta.name else title.lower()
416422
columns = cls.get_columns().items()
@@ -423,12 +429,16 @@ def schema(cls, as_dict=False):
423429
_type = field.type
424430
type_info = _get_type_info(_type, name, title)
425431
ref_info = _get_ref_info(_type, field)
432+
if ref_info:
433+
defs[name] = ref_info.pop('schema', None)
434+
else:
435+
ref_info = {}
436+
426437
minimum = field.metadata.get('min', None)
427438
maximum = field.metadata.get('max', None)
428439
secret = field.metadata.get('secret', None)
429-
label = field.metadata.get('label', None)
430440

431-
if field.metadata.get('required', False):
441+
if field.metadata.get('required', False) or field.metadata.get('primary', False):
432442
required.append(name)
433443

434444
# UI objects:
@@ -441,24 +451,23 @@ def schema(cls, as_dict=False):
441451
fields[name] = {
442452
"type": type_info,
443453
"nullable": field.metadata.get('nullable', False),
444-
"label": label,
454+
"label": field.metadata.get('label', None),
445455
"attrs": {
446456
"placeholder": field.metadata.get('description', None),
447457
"format": field.metadata.get('format', None),
448458
},
449459
"readOnly": field.metadata.get('readonly', False),
450-
"writeOnly": False,
451460
**ui_objects,
452-
**schema_extra
461+
**schema_extra,
462+
**ref_info
453463
}
454464

465+
if 'write_only' in field.metadata:
466+
fields[name]["writeOnly"] = field.metadata.get('write_only', False)
467+
455468
if 'pattern' in field.metadata:
456469
fields[name]["attrs"]["pattern"] = field.metadata['pattern']
457470

458-
ref = ref_info.get('ref') if ref_info else None
459-
if ref:
460-
fields[name]["$ref"] = ref
461-
462471
if field.repr is False:
463472
fields[name]["attrs"]["visible"] = False
464473

@@ -477,17 +486,22 @@ def schema(cls, as_dict=False):
477486
if maximum:
478487
fields[name]['maximum'] = maximum
479488

489+
endpoint_kwargs = {}
490+
if endpoint is not None:
491+
endpoint_kwargs["endpoint"] = endpoint
492+
480493
base_schema = {
481494
"$schema": "https://json-schema.org/draft/2020-12/schema",
482495
"$id": f"/schemas/{table}",
496+
**endpoint_kwargs,
483497
"additionalProperties": cls.Meta.strict,
484498
"title": title,
485499
"description": description,
486500
"type": "object",
487501
"table": table,
488502
"schema": schema,
489503
"properties": fields,
490-
"required": required
504+
"required": required,
491505
}
492506

493507
if defs:

datamodel/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
__title__ = 'python-datamodel'
44
__description__ = ('simple library based on python +3.8 to use Dataclass-syntax'
55
'for interacting with Data')
6-
__version__ = '0.6.5'
6+
__version__ = '0.6.6'
77
__author__ = 'Jesus Lara'
88
__author_email__ = '[email protected]'
99
__license__ = 'BSD'

examples/form_schema.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from typing import Optional
2+
from datetime import datetime
3+
import pprint
4+
from enum import Enum
5+
from datamodel import BaseModel, Field
6+
7+
pp = pprint.PrettyPrinter(width=41, compact=True)
8+
9+
10+
class Reward(BaseModel):
11+
reward_id: int = Field(
12+
primary_key=True, required=False, db_default="auto", repr=False
13+
)
14+
reward: str = Field(required=True, nullable=False)
15+
description: str = Field(required=False)
16+
points: int = Field(required=False, default=1)
17+
programs: str = Field(required=False)
18+
program_slug: str = Field(required=False, default="")
19+
region: str = Field(required=False, default="")
20+
department: list = Field(required=False, default_factory=list)
21+
icon: str = Field(
22+
required=False,
23+
default="",
24+
ui_widget="ImageUploader",
25+
ui_help="Badge Icon, Hint: please use a transparent PNG."
26+
)
27+
attributes: Optional[dict] = Field(
28+
required=False, default_factory=dict, db_type="jsonb"
29+
)
30+
resource_link: str = Field(required=False, default="")
31+
effective_date: datetime = Field(required=False, default=datetime.now())
32+
inserted_at: datetime = Field(required=False, default=datetime.now())
33+
34+
class Meta:
35+
driver = "pg"
36+
name = "rewards"
37+
schema = "navigator"
38+
app_label = "navigator"
39+
strict = True
40+
41+
class Employee(BaseModel):
42+
associate_id: str = Field(required=True)
43+
position_id: str = Field(required=True)
44+
file_number: str = Field(required=True)
45+
operator_name: str = Field(required=True)
46+
first_name: str = Field(required=False)
47+
last_name: str = Field(required=False)
48+
display_name: str = Field(required=False)
49+
corporate_email: str = Field(required=True)
50+
job_code: str = Field(required=False)
51+
job_code_title: str = Field(required=False)
52+
region_code: str = Field(required=False)
53+
department: str = Field(required=False)
54+
department_code: str = Field(required=False)
55+
location_code: str = Field(required=False)
56+
work_location: str = Field(required=False)
57+
reports_to_associate_oid: str = Field(required=False)
58+
reports_to_associate_id: str = Field(required=False)
59+
reports_to_position_id: str = Field(required=False)
60+
61+
def email(self):
62+
return self.corporate_email
63+
64+
class Meta:
65+
name = 'vw_active_employees'
66+
schema = 'troc'
67+
strict = True
68+
69+
70+
class BadgeAssign(BaseModel):
71+
reward_id: Reward = Field(
72+
required=True, fk='reward_id|reward', endpoint='rewards', label="Badge"
73+
)
74+
reward: str = Field(repr=False)
75+
receiver_email: Employee = Field(
76+
required=True,
77+
fk='corporate_email|display_name',
78+
api='adp_employees'
79+
)
80+
# receiver_user: User = Field(
81+
# required=True,
82+
# fk='user_id|display_name',
83+
# api='programs',
84+
# repr=False
85+
# )
86+
giver_user: int = Field(required=True)
87+
giver_email: str = Field(required=True)
88+
giver_employee: str = Field(required=False)
89+
giver_message: str = Field(
90+
ui_widget='textarea',
91+
ui_help='Message to the receiver',
92+
label="Message"
93+
)
94+
95+
class Meta:
96+
name = 'users_rewards'
97+
schema = 'navigator'
98+
endpoint: str = 'api/v1/assign_rewards'
99+
strict = True
100+
101+
schema = BadgeAssign.schema(as_dict=False)
102+
print(schema)
103+
# pp.pprint(schema)

0 commit comments

Comments
 (0)