Skip to content

Commit

Permalink
Add new field types and format source
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim020 committed Mar 23, 2024
1 parent 103ff0d commit 0b99fce
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 17 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/python-format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Python Formatting
on: [push, pull_request]

concurrency:
group: test-${{ github.head_ref }}
cancel-in-progress: true

env:
PYTHONUNBUFFERED: "1"
FORCE_COLOR: "1"

jobs:
run:
name: Python Formatting on 'Linux'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.10" ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Hatch
run: pip install --upgrade hatch
- name: Run linting
run: hatch run lint:all

2 changes: 1 addition & 1 deletion .idea/couch-potato.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,18 @@ cov = [
type = "container"

[[envs.all.matrix]]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]

[envs.lint]
detached = true
dependencies = [
"black==24.3.0",
]

[envs.lint.scripts]
style = [
"black --check --diff {args:.}",
]
all = [
"style",
]
15 changes: 11 additions & 4 deletions src/couch_potato/_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ def __new__(cls, name, bases, dct):

# 2. Ensure that the model class has a key generator
if not getattr(new_class, "__key_generator__", None):
raise TypeError(f"Model class {name} must have a __key_generator__ attribute")
raise TypeError(
f"Model class {name} must have a __key_generator__ attribute"
)
if not isinstance(getattr(new_class, "__key_generator__"), KeyGenerator):
raise TypeError(f"Model class {name} __key_generator__ is not a KeyGenerator instance")
raise TypeError(
f"Model class {name} __key_generator__ is not a KeyGenerator instance"
)

# 3. Ensure that the key generator is made up of valid format parts
key_generator: KeyGenerator = getattr(new_class, "__key_generator__")
Expand All @@ -45,8 +49,10 @@ def __new__(cls, name, bases, dct):
if format_key not in fields:
missing_keys.add(format_key)
if missing_keys:
raise TypeError(f"Model class {name}'s KeyGenerator contains keys without "
f"corresponding fields: {list(missing_keys)}")
raise TypeError(
f"Model class {name}'s KeyGenerator contains keys without "
f"corresponding fields: {list(missing_keys)}"
)

# Set up the binds on the CouchPotato instance
# 1. Bucket bind
Expand Down Expand Up @@ -80,4 +86,5 @@ def __new__(cls, name, bases, dct):

# Finally, return the new model class
return new_class

return MetaModel
6 changes: 4 additions & 2 deletions src/couch_potato/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ def __init__(self, nullable: bool = True, read_only: bool = False):
@classmethod
def ensure_type(cls, value: Any):
if not isinstance(value, cls.__type__):
raise TypeError(f"Incorrect type for {value}, expected "
f"{cls.__type__} but got {type(value)}")
raise TypeError(
f"Incorrect type for {value}, expected "
f"{cls.__type__} but got {type(value)}"
)

def serialize(self, value):
self.ensure_type(value)
Expand Down
3 changes: 2 additions & 1 deletion src/couch_potato/couch_potato.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ def __init__(self, cluster: Cluster, **kwargs):
self._model_binds: Dict[BaseModel, Collection] = {}

# After init, wait until the cluster is ready
init_timeout = kwargs.get('init_timeout', 5)
init_timeout = kwargs.get("init_timeout", 5)
self._cluster.wait_until_ready(timeout=timedelta(seconds=init_timeout))

@property
def Model(self) -> Type[BaseModel]:
if self.__model__ is None:

class _Model(BaseModel, metaclass=make_meta_model(self)):
@classmethod
def bind(cls) -> Collection:
Expand Down
19 changes: 19 additions & 0 deletions src/couch_potato/fields.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime

from src.couch_potato._types import Field


Expand All @@ -11,3 +13,20 @@ class Float(Field):

class String(Field):
__type__ = str


class Boolean(Field):
__type__ = bool


class DateTime(Field):
__type__ = datetime

def serialize(self, value: datetime):
self.ensure_type(value)
return value.isoformat()

def deserialize(self, value):
ret = datetime.fromisoformat(value)
self.ensure_type(ret)
return ret
12 changes: 7 additions & 5 deletions src/couch_potato/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from src.couch_potato.errors import ModelAttributeError, FieldNotFound, ReadOnlyError

T = TypeVar('T', bound='BaseModel')
T = TypeVar("T", bound="BaseModel")


class KeyGenerator:
Expand All @@ -32,7 +32,9 @@ class BaseModel:
def __init__(self, **kwargs):
for key, value in kwargs.items():
if not hasattr(self, key):
raise ModelAttributeError(f"Model {self.__name__} does not contain field {key}")
raise ModelAttributeError(
f"Model {self.__name__} does not contain field {key}"
)

# Ensure the type of the field is correct when setting it
field = self.__fields__[key]
Expand Down Expand Up @@ -99,9 +101,9 @@ def save(self):
raise Exception("Unable to save model, as no fields defined")

serialized = self.to_json()
doc_key = self.__key_generator__.generate(**{
key: getattr(self, key) for key in self.__key_generator__.format_keys
})
doc_key = self.__key_generator__.generate(
**{key: getattr(self, key) for key in self.__key_generator__.format_keys}
)

if hasattr(self, "__cas__"):
# Updating the document
Expand Down
3 changes: 3 additions & 0 deletions test_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
black==24.3.0
coverage[toml]>=6.5
3 changes: 0 additions & 3 deletions tests/test_dummy.py

This file was deleted.

49 changes: 49 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from datetime import datetime
from typing import Type, Any

import pytest

from src.couch_potato._types import Field
from src.couch_potato.fields import Float, String, Integer, Boolean, DateTime


@pytest.mark.parametrize(
"field_class, value, expected_result",
[
pytest.param(Integer, 1, 1, id="Integer"),
pytest.param(Float, 1.0, 1.0, id="Float"),
pytest.param(String, "foobar", "foobar", id="String"),
pytest.param(Boolean, True, True, id="Boolean"),
pytest.param(
DateTime,
datetime(2024, 3, 23, 0, 0, 0),
"2024-03-23T00:00:00",
id="DateTime",
),
],
)
def test_field_serialize(field_class: Type[Field], value: Any, expected_result: Any):
field = field_class()
serialized_value = field.serialize(value)
assert serialized_value == expected_result


@pytest.mark.parametrize(
"field_class, value, expected_result",
[
pytest.param(Integer, 1, 1, id="Integer"),
pytest.param(Float, 1.0, 1.0, id="Float"),
pytest.param(String, "foobar", "foobar", id="String"),
pytest.param(Boolean, True, True, id="Boolean"),
pytest.param(
DateTime,
"2024-03-23T00:00:00",
datetime(2024, 3, 23, 0, 0, 0),
id="DateTime",
),
],
)
def test_field_deserialize(field_class: Type[Field], value: Any, expected_result: Any):
field = field_class()
serialized_value = field.deserialize(value)
assert serialized_value == expected_result

0 comments on commit 0b99fce

Please sign in to comment.