Skip to content

Commit

Permalink
Implement array field #63
Browse files Browse the repository at this point in the history
  • Loading branch information
hakancelikdev committed Sep 29, 2023
1 parent 1a75a1e commit e0b0177
Show file tree
Hide file tree
Showing 23 changed files with 311 additions and 66 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/setup-python@v2.3.3
- uses: actions/checkout@v4.1.0
- uses: actions/setup-python@v4.7.0
with:
python-version: "3.10"
architecture: "x64"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/setup-python@v2.3.3
- uses: actions/checkout@v4.1.0
- uses: actions/setup-python@v4.7.0
with:
python-version: "3.10"
architecture: "x64"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/setup-python@v2.3.3
- uses: actions/checkout@v4.1.0
- uses: actions/setup-python@v4.7.0
with:
python-version: "3.10"
architecture: "x64"
Expand All @@ -28,8 +28,8 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/setup-python@v2.3.3
- uses: actions/checkout@v4.1.0
- uses: actions/setup-python@v4.7.0
with:
python-version: "3.10"
architecture: "x64"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ jobs:
os: [ubuntu-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/checkout@v4.1.0

- name: Set up Python${{ matrix.python-version }}
uses: actions/setup-python@v2.3.3
uses: actions/setup-python@v4.7.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64
Expand All @@ -26,10 +26,10 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.5.0
- uses: actions/checkout@v4.1.0

- name: Set up Python3.10
uses: actions/setup-python@v2.3.3
uses: actions/setup-python@v4.7.0
with:
python-version: "3.10"
architecture: x64
Expand Down
12 changes: 7 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# Install the git hook scripts, run `$ pre-commit install` to set up the git hook scripts
# Run against all the files, `$ pre-commit run --all-files`
# Updating hooks automatically: `$ pre-commit autoupdate`

default_language_version:
python: python3.8
fail_fast: true
repos:
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort

- repo: https://github.com/hakancelikdev/unimport
rev: 0.16.0
rev: 1.0.0
hooks:
- id: unimport
args:
Expand All @@ -26,21 +28,21 @@ repos:
- --refactor

- repo: https://github.com/myint/docformatter
rev: v1.6.5
rev: v1.7.5
hooks:
- id: docformatter
args: [--in-place]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
rev: v1.5.1
hooks:
- id: mypy
args: [--no-strict-optional, --ignore-missing-imports, --show-error-codes]
additional_dependencies: [types-toml==0.1.3]
exclude: "tests/"

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
args:
Expand Down
9 changes: 8 additions & 1 deletion src/pydbm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
DbmModel,
Field,
Undefined,
validate_array_float,
validate_array_int,
validate_array_str,
validate_bool,
validate_bytes,
validate_date,
Expand All @@ -14,7 +17,7 @@
validate_none,
validate_str,
)
from pydbm.typing_extra import NormalizationT, ValidatorT
from pydbm.typing_extra import NormalizationT, ValidatorT, array

__all__ = (
"PydbmBaseException",
Expand All @@ -33,6 +36,10 @@
"validate_min_value",
"validate_none",
"validate_str",
"validate_array_str",
"validate_array_int",
"validate_array_float",
"NormalizationT",
"ValidatorT",
"array",
)
4 changes: 2 additions & 2 deletions src/pydbm/database/data_types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


class BaseDataType(abc.ABC):
data_types: dict[SupportedClassT, typing.Type[BaseDataType]] = {}
data_types: typing.ClassVar[dict[SupportedClassT, typing.Type[BaseDataType]]] = {}

@staticmethod
@abc.abstractmethod
Expand All @@ -33,4 +33,4 @@ def get_data_type(cls, item: SupportedClassT) -> typing.Type[BaseDataType]:
try:
return cls.data_types[item]
except KeyError:
raise TypeError(f"Type {item} is not supported yet!")
raise TypeError(f"Type {item!r} is not supported yet!")
29 changes: 28 additions & 1 deletion src/pydbm/database/data_types/types.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from __future__ import annotations

import ast
import datetime

from pydbm.database.data_types.base import BaseDataType
from pydbm.typing_extra import array

__all__ = (
"ArrayFloatDataType",
"ArrayIntDataType",
"ArrayTextDataType",
"BoolDataType",
"BytesDataType",
"DateDataType",
Expand Down Expand Up @@ -98,4 +103,26 @@ def get(cls, value: str) -> str:

@staticmethod
def set(value: str) -> str:
return str(value)
return value


class _ArrayDataType(BaseDataType, data_type=array):
@classmethod
def get(cls, value: str) -> array:
return array(*ast.literal_eval(value))

@staticmethod
def set(value: array) -> str:
return str(value.tolist())


class ArrayIntDataType(_ArrayDataType, data_type=array[int]):
pass


class ArrayFloatDataType(_ArrayDataType, data_type=array[float]):
pass


class ArrayTextDataType(_ArrayDataType, data_type=array[str]):
pass
4 changes: 4 additions & 0 deletions src/pydbm/database/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pydbm.database.data_types import BaseDataType
from pydbm.inspect_extra import get_obj_annotations
from pydbm.models.fields import AutoField
from pydbm.typing_extra import array

if typing.TYPE_CHECKING:
from pydbm import DbmModel
Expand All @@ -36,6 +37,9 @@
int: "int",
None: "null",
str: "str",
array[int]: "array.int",
array[float]: "array.float",
array[str]: "array.str",
}
DATABASE_EXTENSION: str = "pydbm"
DATABASE_PATH: Path = Path("pydbm") # TODO: take from env
Expand Down
6 changes: 6 additions & 0 deletions src/pydbm/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pydbm.models.base import DbmModel
from pydbm.models.fields import Field, Undefined
from pydbm.models.validators import (
validate_array_float,
validate_array_int,
validate_array_str,
validate_bool,
validate_bytes,
validate_date,
Expand All @@ -27,4 +30,7 @@
"validate_min_value",
"validate_none",
"validate_str",
"validate_array_float",
"validate_array_int",
"validate_array_str",
)
52 changes: 27 additions & 25 deletions src/pydbm/models/fields/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydbm import contstant as C
from pydbm.exceptions import ValidationError
from pydbm.logging import logger
from pydbm.models.validators import validate_max_value, validate_min_value, validator_mapping
from pydbm.models.validators import VALIDATOR_MAPPING, validate_max_value, validate_min_value

if typing.TYPE_CHECKING:
from pydbm.models.base import DbmModel
Expand Down Expand Up @@ -64,6 +64,27 @@ def __init__(

self._is_call_run = False

def get_default_value(self) -> typing.Any:
if self.default is not Undefined:
return self.default

if self.default_factory is not Undefined:
return self.default_factory()

def through_normalizers(self, value: typing.Any) -> typing.Any:
for normalizer in self.normalizers:
value = normalizer(value)

return value

def through_validators(self, value: typing.Any) -> None:
for validator in self.validators:
try:
if validator(value) is False:
raise ValueError("Value is not valid")
except ValueError as exc:
raise ValidationError(self.field_name, value, exc)

def __set_name__(self, instance: Meta, name: str) -> None:
self.public_name = name
self.private_name = "_" + name
Expand All @@ -87,11 +108,12 @@ def __set__(self, instance: DbmModel, value: typing.Any) -> None:
f"They are ignored for {value.__class__.__name__} type."
)

eligible_value = self.before_set(value)
value = self.through_normalizers(value)
self.through_validators(value)

setattr(instance, self.private_name, eligible_value)
setattr(instance, self.private_name, value)
if self.field_name != C.PRIMARY_KEY:
instance.fields[self.field_name] = eligible_value
instance.fields[self.field_name] = value

def __call__(self: Self, field_name: str, field_type: SupportedClassT, *args, **kwargs) -> Self: # type: ignore[valid-type] # noqa: E501
self._is_call_run = True
Expand All @@ -102,7 +124,7 @@ def __call__(self: Self, field_name: str, field_type: SupportedClassT, *args, **
self.public_name = field_name
self.private_name = "_" + field_name

self.validators.append(validator_mapping[field_type])
self.validators.append(VALIDATOR_MAPPING[field_type])
return self

def __repr__(self) -> str:
Expand All @@ -117,29 +139,9 @@ def __repr__(self) -> str:
f")"
)

def before_set(self, value: typing.Any) -> typing.Any:
for normalizer in self.normalizers:
value = normalizer(value)

for validator in self.validators:
try:
if validator(value) is False:
raise ValueError("Value is not valid")
except ValueError as exc:
raise ValidationError(self.field_name, value, exc)

return value

def is_required(self) -> bool:
return self.default is Undefined and self.default_factory is Undefined

def get_default_value(self) -> typing.Any:
if self.default is not Undefined:
return self.default

if self.default_factory is not Undefined:
return self.default_factory()


class Field(BaseField):
pass
12 changes: 6 additions & 6 deletions src/pydbm/models/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ class Meta(type):

@staticmethod
def __new__(mcs, cls_name: str, bases: tuple[Meta, ...], namespace: dict[str, typing.Any], **kwargs: typing.Any) -> type: # noqa: E501
annotations = namespace.pop("__annotations__", {})
annotations[C.PRIMARY_KEY] = str
slots = mcs.generate_slots(annotations)
_annotations = namespace.pop("__annotations__", {})
_annotations[C.PRIMARY_KEY] = str
slots = mcs.generate_slots(_annotations)
if not [b for b in bases if isinstance(b, mcs)]:
slots.remove("_id")
slots.append("fields")
else:
if not annotations or list(annotations.keys()) == [C.PRIMARY_KEY]:
if not _annotations or list(_annotations.keys()) == [C.PRIMARY_KEY]:
raise EmptyModelError("Empty model is not allowed.")
slots.append("database")

namespace["__slots__"] = tuple(slots)
namespace["__annotations__"] = annotations
namespace["__annotations__"] = _annotations
return super().__new__(mcs, cls_name, bases, namespace)

def __init__(cls, cls_name: str, bases: tuple[Meta, ...], namespace: dict[str, typing.Any], **kwargs: typing.Any) -> None: # noqa: E501
Expand All @@ -62,7 +62,7 @@ def __init__(cls, cls_name: str, bases: tuple[Meta, ...], namespace: dict[str, t
setattr(cls, key, value)

def __call__(cls, **kwargs):
for extra_field_name in (set(kwargs.keys()) - set(cls.__annotations__.keys())):
for extra_field_name in set(kwargs.keys()) - set(cls.__annotations__.keys()):
raise UnnecessaryParamsError(f"{extra_field_name} is not defined in {cls.__name__}")

for field in cls.not_required_fields:
Expand Down
Loading

0 comments on commit e0b0177

Please sign in to comment.