Skip to content

Commit 84f700b

Browse files
Merge pull request #91 from phenobarbital/remove-nulls-from-output-and-repr
add dynamically fields after created model
2 parents 72dd64b + d9fe9f9 commit 84f700b

File tree

7 files changed

+4214
-2119
lines changed

7 files changed

+4214
-2119
lines changed

datamodel/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
44
DataModel is a reimplementation of dataclasses with true inheritance and composition.
55
"""
6-
from datamodel.fields import Field, Column
6+
from datamodel.fields import Field, Column, fields
77
from .base import BaseModel
88
from .version import (
99
__title__, __description__, __version__, __author__, __author_email__
1010
)
1111

12-
__all__ = ('Field', 'Column', 'BaseModel', )
12+
__all__ = ('fields', 'Field', 'Column', 'BaseModel', )

datamodel/base.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@
1010
asdict,
1111
dataclass,
1212
is_dataclass,
13-
make_dataclass,
14-
fields
13+
make_dataclass
1514
)
1615
from typing import Any, Optional, Union
1716
from functools import partial
1817
from enum import EnumMeta
1918
from operator import attrgetter
2019
from orjson import OPT_INDENT_2
2120
from datamodel.converters import parse_type
22-
from datamodel.fields import Field
21+
from datamodel.fields import Field, fields
2322
from datamodel.types import JSON_TYPES
2423
from datamodel.validation import validator
2524

@@ -86,13 +85,14 @@ def _dc_method_setattr(
8685

8786
def create_dataclass(
8887
new_cls: Union[object, Any],
88+
strict: bool = False,
8989
frozen: bool = False
9090
) -> Callable:
9191
"""
9292
create_dataclass.
9393
Create a Dataclass from a simple Class
9494
"""
95-
dc = dataclass(unsafe_hash=True, repr=False, init=True, order=False, eq=True, frozen=frozen)(new_cls)
95+
dc = dataclass(unsafe_hash=strict, repr=False, init=True, order=False, eq=True, frozen=frozen)(new_cls)
9696
setattr(dc, "__setattr__", _dc_method_setattr)
9797
# adding a properly internal json encoder:
9898
dc.__encoder__ = DefaultEncoder()
@@ -113,9 +113,15 @@ def __new__(cls, name, bases, attrs, **kwargs):
113113
"""__new__ is a classmethod, even without @classmethod decorator"""
114114
cols = []
115115
if "__annotations__" in attrs:
116-
annotations = attrs["__annotations__"]
116+
annotations = attrs.get('__annotations__', {})
117+
try:
118+
strict = attrs['Meta'].strict
119+
except (TypeError, AttributeError, KeyError):
120+
strict = True
117121
for field, _type in annotations.items():
118-
if field in attrs:
122+
if strict is False and field not in attrs:
123+
attrs[field] = Field(factory=_type, required=False, default=None)
124+
elif field in attrs:
119125
df = attrs[field]
120126
if isinstance(df, Field):
121127
setattr(cls, field, df)
@@ -125,7 +131,6 @@ def __new__(cls, name, bases, attrs, **kwargs):
125131
df.type = _type
126132
setattr(cls, field, df)
127133
else:
128-
# print(f"HERE Field: {field}, Type: {_type}")
129134
# add a new field, based on type
130135
df = Field(factory=_type, required=False, default=None)
131136
df.name = field
@@ -137,6 +142,7 @@ def __new__(cls, name, bases, attrs, **kwargs):
137142
attr_meta = attrs.pop("Meta", None)
138143
new_cls = super().__new__(cls, name, bases, attrs, **kwargs)
139144
new_cls.Meta = attr_meta or getattr(new_cls, "Meta", Meta)
145+
new_cls.__dataclass_fields__ = cols
140146
if not new_cls.Meta:
141147
new_cls.Meta = Meta
142148
new_cls.Meta.set_connection = types.MethodType(
@@ -166,6 +172,7 @@ def __new__(cls, name, bases, attrs, **kwargs):
166172
pass
167173
dc = create_dataclass(
168174
new_cls,
175+
strict=new_cls.Meta.strict,
169176
frozen=frozen
170177
)
171178
cols = {
@@ -186,7 +193,7 @@ def __init__(cls, *args, **kwargs) -> None:
186193
# Initialized Data Model = True
187194
cls.__initialised__ = True
188195
cls.__errors__ = None
189-
super(ModelMeta, cls).__init__(*args, **kwargs)
196+
super().__init__(*args, **kwargs)
190197

191198

192199
class BaseModel(metaclass=ModelMeta):
@@ -247,6 +254,20 @@ def json(self, **kwargs):
247254
def is_valid(self):
248255
return bool(self.__valid__)
249256

257+
@classmethod
258+
def add_field(cls, name: str, value: Any = None) -> None:
259+
if cls.Meta.strict is True:
260+
raise TypeError(
261+
f'Cannot create a new field {name} on a Strict Model.'
262+
)
263+
if name != '__errors__':
264+
f = Field(required=False, default=value)
265+
f.name = name
266+
f.type = type(value)
267+
f._field_type = _FIELD
268+
cls.__columns__[name] = f
269+
cls.__dataclass_fields__[name] = f
270+
250271
def create_field(self, name: str, value: Any) -> None:
251272
"""create_field.
252273
create a new Field on Model (when strict is False).
@@ -264,7 +285,9 @@ def create_field(self, name: str, value: Any) -> None:
264285
f = Field(required=False, default=value)
265286
f.name = name
266287
f.type = type(value)
288+
f._field_type = _FIELD
267289
self.__columns__[name] = f
290+
self.__dataclass_fields__[name] = f
268291
setattr(self, name, value)
269292

270293
def set(self, name: str, value: Any) -> None:
@@ -356,14 +379,6 @@ def __post_init__(self) -> None:
356379
self._validation()
357380
except RuntimeError as err:
358381
logging.exception(err)
359-
### Post Init
360-
## check if remove nulls:
361-
# print('META >>> ', self.Meta.nulls)
362-
# if self.Meta.remove_nulls is True:
363-
# null_fields = [field_name for field_name, value in self.__dict__.items() if value is None]
364-
# print('NULL FIELDS >> ', null_fields)
365-
# for field_name in null_fields:
366-
# delattr(self, field_name)
367382

368383

369384
def is_callable(self, value) -> bool:

0 commit comments

Comments
 (0)