Skip to content

Auto-generate Pydantic models from JSON Schemas #2

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

Merged
merged 16 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Generated Pydantic BaseModels under jsondoc/models/autogen
  • Loading branch information
osolmaz committed Aug 15, 2024
commit 44b340c4df53d28efbfc55bc2d4a5836556a79db
17 changes: 17 additions & 0 deletions docs/notes-on-python-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# JSON-DOC Python Implementation

We use datamodel-code-generator to generate Pydantic `BaseModel`s from JSON-DOC JSON schemas. This saves us from writing the Pydantic models by hand.

The JSON Schemas as refer to each other, and there are circular dependencies in some cases, like blocks that have children blocks whose types are also the general block under `/block/block_schema.json`.

Since we can't inject types infinitely, we stop at the first-level of depth. That is, after first level `$ref`s are resolved, we replace the remaining `$ref`s with an arbitrary JSON schema object which allows any key-value pairs.

```py
ARBITRARY_JSON_SCHEMA_OBJECT = {
"type": "object",
"properties": {},
"additionalProperties": True,
}
```

These are then annotated as `Dict[str, Any]` in the corresponding Pydantic model fields.
34 changes: 34 additions & 0 deletions jsondoc/models/autogen/block/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# generated by datamodel-codegen:
# filename: example.json
# timestamp: 2024-08-15T07:20:03+00:00

from __future__ import annotations

from enum import Enum

from pydantic import BaseModel


class Type(Enum):
paragraph = 'paragraph'
to_do = 'to_do'
bulleted_list_item = 'bulleted_list_item'
numbered_list_item = 'numbered_list_item'
code = 'code'
column = 'column'
column_list = 'column_list'
divider = 'divider'
equation = 'equation'
heading_1 = 'heading_1'
heading_2 = 'heading_2'
heading_3 = 'heading_3'
image = 'image'
quote = 'quote'
equation_1 = 'equation'
table = 'table'
table_row = 'table_row'
toggle = 'toggle'


class Model(BaseModel):
type: Type
50 changes: 50 additions & 0 deletions jsondoc/models/autogen/block/common/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# generated by datamodel-codegen:
# filename: example.json
# timestamp: 2024-08-15T07:20:03+00:00

from __future__ import annotations

from typing import Any, Dict, Optional

from pydantic import AwareDatetime, BaseModel, ConfigDict
from typing_extensions import Literal


class Parent(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
type: str
block_id: Optional[str] = None
page_id: Optional[str] = None


class CreatedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class LastEditedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class Model(BaseModel):
object: Literal['block']
id: str
parent: Optional[Parent] = None
type: str
created_time: AwareDatetime
created_by: Optional[CreatedBy] = None
last_edited_time: Optional[AwareDatetime] = None
last_edited_by: Optional[LastEditedBy] = None
archived: bool
in_trash: Optional[bool] = None
has_children: bool
metadata: Optional[Dict[str, Any]] = None
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# generated by datamodel-codegen:
# filename: example.json
# timestamp: 2024-08-15T07:20:03+00:00

from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import AwareDatetime, BaseModel, ConfigDict
from typing_extensions import Literal


class Parent(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
type: str
block_id: Optional[str] = None
page_id: Optional[str] = None


class CreatedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class LastEditedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class Type(Enum):
text = 'text'
equation = 'equation'


class RichTextItem(BaseModel):
type: Type


class Color(Enum):
blue = 'blue'
blue_background = 'blue_background'
brown = 'brown'
brown_background = 'brown_background'
default = 'default'
gray = 'gray'
gray_background = 'gray_background'
green = 'green'
green_background = 'green_background'
orange = 'orange'
orange_background = 'orange_background'
yellow = 'yellow'
pink = 'pink'
pink_background = 'pink_background'
purple = 'purple'
purple_background = 'purple_background'
red = 'red'
red_background = 'red_background'
yellow_background = 'yellow_background'


class Type1(Enum):
paragraph = 'paragraph'
to_do = 'to_do'
bulleted_list_item = 'bulleted_list_item'
numbered_list_item = 'numbered_list_item'
code = 'code'
column = 'column'
column_list = 'column_list'
divider = 'divider'
equation = 'equation'
heading_1 = 'heading_1'
heading_2 = 'heading_2'
heading_3 = 'heading_3'
image = 'image'
quote = 'quote'
equation_1 = 'equation'
table = 'table'
table_row = 'table_row'
toggle = 'toggle'


class Child(BaseModel):
type: Type1


class BulletedListItem(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
rich_text: List[RichTextItem]
color: Optional[Color] = None
children: Optional[List[Child]] = None


class BulletedListItemBlock(BaseModel):
object: Literal['block']
id: str
parent: Optional[Parent] = None
type: str
created_time: AwareDatetime
created_by: Optional[CreatedBy] = None
last_edited_time: Optional[AwareDatetime] = None
last_edited_by: Optional[LastEditedBy] = None
archived: bool
in_trash: Optional[bool] = None
has_children: bool
metadata: Optional[Dict[str, Any]] = None
bulleted_list_item: BulletedListItem
175 changes: 175 additions & 0 deletions jsondoc/models/autogen/block/types/code/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# generated by datamodel-codegen:
# filename: example.json
# timestamp: 2024-08-15T07:20:03+00:00

from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import AwareDatetime, BaseModel, ConfigDict
from typing_extensions import Literal


class Parent(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
type: str
block_id: Optional[str] = None
page_id: Optional[str] = None


class CreatedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class LastEditedBy(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
object: Literal['user']
id: str


class Type(Enum):
text = 'text'
equation = 'equation'


class CaptionItem(BaseModel):
type: Type


class RichTextItem(BaseModel):
type: Type


class Language(Enum):
abap = 'abap'
arduino = 'arduino'
bash = 'bash'
basic = 'basic'
c = 'c'
clojure = 'clojure'
coffeescript = 'coffeescript'
c__ = 'c++'
c_ = 'c#'
css = 'css'
dart = 'dart'
diff = 'diff'
docker = 'docker'
elixir = 'elixir'
elm = 'elm'
erlang = 'erlang'
flow = 'flow'
fortran = 'fortran'
f_ = 'f#'
gherkin = 'gherkin'
glsl = 'glsl'
go = 'go'
graphql = 'graphql'
groovy = 'groovy'
haskell = 'haskell'
html = 'html'
java = 'java'
javascript = 'javascript'
json = 'json'
julia = 'julia'
kotlin = 'kotlin'
latex = 'latex'
less = 'less'
lisp = 'lisp'
livescript = 'livescript'
lua = 'lua'
makefile = 'makefile'
markdown = 'markdown'
markup = 'markup'
matlab = 'matlab'
mermaid = 'mermaid'
nix = 'nix'
objective_c = 'objective-c'
ocaml = 'ocaml'
pascal = 'pascal'
perl = 'perl'
php = 'php'
plain_text = 'plain text'
powershell = 'powershell'
prolog = 'prolog'
protobuf = 'protobuf'
python = 'python'
r = 'r'
reason = 'reason'
ruby = 'ruby'
rust = 'rust'
sass = 'sass'
scala = 'scala'
scheme = 'scheme'
scss = 'scss'
shell = 'shell'
sql = 'sql'
swift = 'swift'
typescript = 'typescript'
vb_net = 'vb.net'
verilog = 'verilog'
vhdl = 'vhdl'
visual_basic = 'visual basic'
webassembly = 'webassembly'
xml = 'xml'
yaml = 'yaml'
java_c_c___c_ = 'java/c/c++/c#'


class Type2(Enum):
paragraph = 'paragraph'
to_do = 'to_do'
bulleted_list_item = 'bulleted_list_item'
numbered_list_item = 'numbered_list_item'
code = 'code'
column = 'column'
column_list = 'column_list'
divider = 'divider'
equation = 'equation'
heading_1 = 'heading_1'
heading_2 = 'heading_2'
heading_3 = 'heading_3'
image = 'image'
quote = 'quote'
equation_1 = 'equation'
table = 'table'
table_row = 'table_row'
toggle = 'toggle'


class Child(BaseModel):
type: Type2


class Code(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
caption: Optional[List[CaptionItem]] = None
rich_text: List[RichTextItem]
language: Optional[Language] = None
children: Optional[List[Child]] = None


class CodeBlock(BaseModel):
object: Literal['block']
id: str
parent: Optional[Parent] = None
type: str
created_time: AwareDatetime
created_by: Optional[CreatedBy] = None
last_edited_time: Optional[AwareDatetime] = None
last_edited_by: Optional[LastEditedBy] = None
archived: bool
in_trash: Optional[bool] = None
has_children: bool
metadata: Optional[Dict[str, Any]] = None
code: Code
Loading
Loading