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

Add mypy checker #165

Draft
wants to merge 8 commits into
base: 3.x
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ jobs:
with:
python-version: "3.x"
- uses: pre-commit/[email protected]
- uses: jpetrucciani/mypy-check@master
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"esbonio.sphinx.confDir": ""
}
146 changes: 81 additions & 65 deletions jsonmodels/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,80 @@


from collections import defaultdict
from typing import Any, Dict, List, Optional, Set, cast

from . import errors
from .fields import NotSet
from .fields import NotSet, Value
from .types import Builder, ClassValidator, Field, JsonSchema, Model, SchemaPart


class Builder:
def __init__(self, parent=None, nullable=False, default=NotSet):
class BaseBuilder:
def __init__(
self,
parent: Optional[Builder] = None,
nullable: bool = False,
default: Any = NotSet,
) -> None:
self.parent = parent
self.types_builders = {}
self.types_count = defaultdict(int)
self.definitions = set()
self.types_builders: Dict[Model, Builder] = {}
self.types_count: Dict[Model, int] = defaultdict(int)
self.definitions: Set[Builder] = set()
self.nullable = nullable
self.default = default

@property
def has_default(self):
def has_default(self) -> bool:
return self.default is not NotSet

def register_type(self, type, builder):
def register_type(self, model_type: Model, builder: Builder) -> None:
if self.parent:
return self.parent.register_type(type, builder)
self.parent.register_type(model_type, builder)
return

self.types_count[type] += 1
if type not in self.types_builders:
self.types_builders[type] = builder
self.types_count[model_type] += 1
if model_type not in self.types_builders:
self.types_builders[model_type] = builder

def get_builder(self, type):
def get_builder(self, model_type: Model) -> Builder:
if self.parent:
return self.parent.get_builder(type)
return self.parent.get_builder(model_type)

return self.types_builders[type]
return self.types_builders[model_type]

def count_type(self, type):
def count_type(self, type: Model) -> int:
if self.parent:
return self.parent.count_type(type)

return self.types_count[type]

@staticmethod
def maybe_build(value):
def maybe_build(value: Value) -> SchemaPart | Value:
return value.build() if isinstance(value, Builder) else value

def add_definition(self, builder):
def add_definition(self, builder: Builder) -> None:
if self.parent:
return self.parent.add_definition(builder)

self.definitions.add(builder)


class ObjectBuilder(Builder):
def __init__(self, model_type, *args, **kwargs):
class ObjectBuilder(BaseBuilder):
def __init__(self, model_type: Model, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.properties = {}
self.required = []
self.type = model_type
self.properties: Dict[str, JsonSchema] = {}
self.required: List[str] = []
self.model_type = model_type

self.register_type(self.type, self)
self.register_type(self.model_type, self)

def add_field(self, name, field, schema):
def add_field(self, name: str, field: Field, schema: JsonSchema) -> None:
_apply_validators_modifications(schema, field)
self.properties[name] = schema
if field.required:
self.required.append(name)

def build(self):
builder = self.get_builder(self.type)
def build(self) -> str | JsonSchema:
builder = self.get_builder(self.model_type)
if self.is_definition and not self.is_root:
self.add_definition(builder)
[self.maybe_build(value) for _, value in self.properties.items()]
Expand All @@ -76,11 +84,13 @@ def build(self):
return builder.build_definition(nullable=self.nullable)

@property
def type_name(self):
module_name = f"{self.type.__module__}.{self.type.__name__}"
def type_name(self) -> str:
module_name = f"{self.model_type.__module__}.{self.model_type.__name__}"
return module_name.replace(".", "_").lower()

def build_definition(self, add_defintitions=True, nullable=False):
def build_definition(
self, add_defintitions: bool = True, nullable: bool = False
) -> JsonSchema:
properties = {
name: self.maybe_build(value) for name, value in self.properties.items()
}
Expand All @@ -99,53 +109,52 @@ def build_definition(self, add_defintitions=True, nullable=False):
return schema

@property
def is_definition(self):
if self.count_type(self.type) > 1:
def is_definition(self) -> bool:
if self.count_type(self.model_type) > 1:
return True
elif self.parent:
return self.parent.is_definition
else:
return False

@property
def is_root(self):
def is_root(self) -> bool:
return not bool(self.parent)


def _apply_validators_modifications(field_schema, field):
def _apply_validators_modifications(field_schema: JsonSchema, field: Field) -> None:
for validator in field.validators:
try:
if isinstance(validator, ClassValidator):
validator.modify_schema(field_schema)
except AttributeError:
pass

if "items" in field_schema:
for validator in field.item_validators:
try:
if isinstance(validator, ClassValidator):
validator.modify_schema(field_schema["items"])
except AttributeError: # Case when validator is simple function.
pass


class PrimitiveBuilder(Builder):
def __init__(self, type, *args, **kwargs):
class PrimitiveBuilder(BaseBuilder):
def __init__(self, value_type: type, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.type = type
self.value_type = value_type

def build(self):
def build(self) -> JsonSchema:
obj_type: list | str
schema = {}
if issubclass(self.type, str):
if issubclass(self.value_type, str):
obj_type = "string"
elif issubclass(self.type, bool):
elif issubclass(self.value_type, bool):
obj_type = "boolean"
elif issubclass(self.type, int):
elif issubclass(self.value_type, int):
obj_type = "number"
elif issubclass(self.type, float):
elif issubclass(self.value_type, float):
obj_type = "number"
elif issubclass(self.type, dict):
elif issubclass(self.value_type, dict):
obj_type = "object"
else:
raise errors.FieldNotSupported("Can't specify value schema!", self.type)
raise errors.FieldNotSupported(
"Can't specify value schema!", self.value_type
)

if self.nullable:
obj_type = [obj_type, "null"]
Expand All @@ -157,16 +166,19 @@ def build(self):
return schema


class ListBuilder(Builder):
def __init__(self, *args, **kwargs):
class ListBuilder(BaseBuilder):

parent: Builder

def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.schemas = []
self.schemas: list[JsonSchema] = []

def add_type_schema(self, schema):
def add_type_schema(self, schema: JsonSchema) -> None:
self.schemas.append(schema)

def build(self):
schema = {"type": "array"}
def build(self) -> JsonSchema:
schema: Dict[str, Any] = {"type": "array"}
if self.nullable:
self.add_type_schema({"type": "null"})

Expand All @@ -183,27 +195,31 @@ def build(self):
return schema

@property
def is_definition(self):
def is_definition(self) -> bool:
return self.parent.is_definition

@staticmethod
def to_struct(item):
def to_struct(item: Value) -> Value:
return item


class EmbeddedBuilder(Builder):
def __init__(self, *args, **kwargs):
class EmbeddedBuilder(BaseBuilder):
parent: Builder

def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.schemas = []
self.schemas: list[JsonSchema] = []

def add_type_schema(self, schema):
def add_type_schema(self, schema: JsonSchema) -> None:
self.schemas.append(schema)

def build(self):
def build(self) -> JsonSchema:
if self.nullable:
self.add_type_schema({"type": "null"})

schemas = [self.maybe_build(schema) for schema in self.schemas]
schemas = cast(
List[JsonSchema], [self.maybe_build(schema) for schema in self.schemas]
)
if len(schemas) == 1:
schema = schemas[0]
else:
Expand All @@ -217,5 +233,5 @@ def build(self):
return schema

@property
def is_definition(self):
def is_definition(self) -> bool:
return self.parent.is_definition
Loading