Skip to content
Merged
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
with open("CHANGELOG.rst", encoding="UTF-8") as changelog_file:
history = changelog_file.read()

requirements = ["sqlalchemy>=1.4,<2.0"]
requirements = ["sqlalchemy>=1.4"]
extras_require = {
"docs": ["sphinx >= 1.4", "sphinx_rtd_theme", "sphinx-autodoc-typehints", "typing_extensions"],
"testing": [
Expand Down
9 changes: 6 additions & 3 deletions src/serialchemy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from .enum_field import EnumKeyField
from .field import Field
from .model_serializer import ModelSerializer
from .nested_fields import (
NestedAttributesField, NestedModelField, NestedModelListField, PrimaryKeyField)
from .nested_fields import NestedAttributesField
from .nested_fields import NestedModelField
from .nested_fields import NestedModelListField
from .nested_fields import PrimaryKeyField
from .polymorphic_serializer import PolymorphicModelSerializer
from .serializer import ColumnSerializer, Serializer
from .serializer import ColumnSerializer
from .serializer import Serializer
23 changes: 17 additions & 6 deletions src/serialchemy/_tests/sample_model.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
from datetime import datetime
from enum import Enum
from sqlalchemy import Column, Date, DateTime, Float, ForeignKey, Integer, String, Table

from sqlalchemy import Column
from sqlalchemy import Date
from sqlalchemy import DateTime
from sqlalchemy import Float
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import object_session, relationship
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import object_session
from sqlalchemy.orm import relationship
from sqlalchemy.sql import sqltypes
from sqlalchemy_utils import ChoiceType

from serialchemy.enum_field import EnumKeyField
from serialchemy.field import Field
from serialchemy.model_serializer import ModelSerializer
from serialchemy.nested_fields import NestedModelField, NestedModelListField
from serialchemy.nested_fields import NestedModelField
from serialchemy.nested_fields import NestedModelListField

Base = declarative_base()

Expand Down Expand Up @@ -75,11 +85,13 @@ class ContractType(Enum):
CONTRACTOR = 'Contractor'
OTHER = 'Other'


class MaritalStatus(Enum):
SINGLE = 'Single'
MARRIED = 'Married'
DIVORCED = 'Divorced'


class Employee(Base):

__tablename__ = 'Employee'
Expand All @@ -101,7 +113,6 @@ class Employee(Base):
contract_type = Column(ChoiceType(ContractType))
marital_status = Column(sqltypes.Enum(MaritalStatus))


password = Column(String)
created_at = Column(DateTime, default=datetime(2000, 1, 2))

Expand All @@ -128,7 +139,7 @@ class Engineer(Employee):

__tablename__ = 'Engineer'

id = Column('eng_id',Integer, ForeignKey('Employee.id'), primary_key=True)
id = Column('eng_id', Integer, ForeignKey('Employee.id'), primary_key=True)
engineer_name = Column(String(30))

__mapper_args__ = {'polymorphic_identity': 'Engineer', 'polymorphic_on': Employee.role}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
from serialchemy._tests.sample_model_imperative.model import Contact
from serialchemy._tests.sample_model_imperative.model import ContactType
from serialchemy._tests.sample_model_imperative.model import ContractType
from serialchemy._tests.sample_model_imperative.model import MaritalStatus
from serialchemy._tests.sample_model_imperative.model import Department
from serialchemy._tests.sample_model_imperative.model import Employee
from serialchemy._tests.sample_model_imperative.model import Engineer
from serialchemy._tests.sample_model_imperative.model import Manager
from serialchemy._tests.sample_model_imperative.model import MaritalStatus
from serialchemy._tests.sample_model_imperative.model import SpecialistEngineer

mapper_registry = registry()
Expand Down
8 changes: 6 additions & 2 deletions src/serialchemy/_tests/test_datetimeserializer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from datetime import datetime, timedelta, timezone, date
from datetime import date
from datetime import datetime
from datetime import timedelta
from datetime import timezone

import pytest

from serialchemy.datetime_serializer import DateTimeSerializer, DateSerializer
from serialchemy.datetime_serializer import DateSerializer
from serialchemy.datetime_serializer import DateTimeSerializer


@pytest.mark.parametrize(
Expand Down
3 changes: 2 additions & 1 deletion src/serialchemy/_tests/test_field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from serialchemy import Field, Serializer
from serialchemy import Field
from serialchemy import Serializer


class CustomSerializer(Serializer):
Expand Down
4 changes: 2 additions & 2 deletions src/serialchemy/_tests/test_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ def seed_data(model, db_session):
password='somepass',
role='Employee',
company=company,
marital_status=model.MaritalStatus.DIVORCED
marital_status=model.MaritalStatus.DIVORCED,
)
db_session.add(employee)
db_session.commit()


def test_dump(model, db_session, data_regression):
employee = db_session.query(model.Employee).get(2)
employee = db_session.get(model.Employee, 2)
serial = func.dump(employee, nest_foreign_keys=True)
data_regression.check(serial, basename='test_dump')

Expand Down
6 changes: 3 additions & 3 deletions src/serialchemy/_tests/test_nested_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_custom_serializer(model, serializer_strategy, db_session, data_regressi
if serializer_strategy == "NestedModelFields"
else EmployeeSerializerNestedAttrsFields
)
emp = db_session.query(model.Employee).get(1)
emp = db_session.get(model.Employee, 1)
serializer = serializer_class(model.Employee)
serialized = serializer.dump(emp)
data_regression.check(
Expand Down Expand Up @@ -127,7 +127,7 @@ def test_deserialize_with_custom_serializer(model, db_session, data_regression):


def test_deserialize_existing_model(model, db_session):
original = db_session.query(model.Employee).get(1)
original = db_session.get(model.Employee, 1)
assert original.firstname == "Jim"
assert original.address.zip is None

Expand Down Expand Up @@ -156,7 +156,7 @@ def test_deserialize_existing_model(model, db_session):

def test_empty_nested(model, db_session):
serializer = getEmployeeSerializerNestedModelFields(model)(model.Employee)
serialized = serializer.dump(db_session.query(model.Employee).get(3))
serialized = serializer.dump(db_session.get(model.Employee, 3))
assert serialized["company"] is None
model = serializer.load(serialized, session=db_session)
assert model.company is None
Expand Down
10 changes: 7 additions & 3 deletions src/serialchemy/_tests/test_polymorphic_serializer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from enum import Enum
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import declarative_base

from serialchemy import PolymorphicModelSerializer

Expand Down Expand Up @@ -43,4 +47,4 @@ class TestA(BaseTest):
assert serialized_obj["type"] == TestEnum.TYPE_A.value

loaded_obj = serializer.load(serialized_obj, session=db_session)
assert isinstance(loaded_obj, TestA)
assert isinstance(loaded_obj, TestA)
17 changes: 12 additions & 5 deletions src/serialchemy/_tests/test_readme.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from datetime import datetime

from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, select
from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import column_property, relationship
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import column_property
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

Expand All @@ -23,7 +28,9 @@ class Employee(Base):
admission = Column(DateTime, default=datetime(2000, 1, 1))
company_id = Column(ForeignKey('Company.id'))
company = relationship(Company)
company_name = column_property(select([Company.name]).where(Company.id == company_id))
company_name = column_property(
select(Company.name).where(Company.id == company_id).scalar_subquery()
)
password = Column(String)


Expand Down
34 changes: 18 additions & 16 deletions src/serialchemy/_tests/test_serialization.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from sqlalchemy.orm import Session

from serialchemy.enum_field import EnumKeyField
from serialchemy.field import Field
from serialchemy.func import dump
Expand Down Expand Up @@ -85,7 +87,7 @@ class EmployeeSerializer(ModelSerializer):
def test_model_dump(model, db_session, data_regression):
seed_data(db_session, model)

emp = db_session.query(model.Employee).get(1)
emp = db_session.get(model.Employee, 1)
serializer = ModelSerializer(model.Employee)
serialized = serializer.dump(emp)
data_regression.check(serialized, basename='test_model_dump')
Expand All @@ -94,7 +96,7 @@ def test_model_dump(model, db_session, data_regression):
def test_enum_key_field_dump(model, db_session, data_regression):
seed_data(db_session, model)

emp = db_session.query(model.Employee).get(1)
emp = db_session.get(model.Employee, 1)
serializer = getEmployeeSerializer(model)(model.Employee)
serialized = serializer.dump(emp)
data_regression.check(serialized, basename='test_enum_key_field_dump')
Expand Down Expand Up @@ -140,7 +142,7 @@ class EmployeeSerializerPrimaryKeyFields(ModelSerializer):
company = PrimaryKeyField(model.Company)

serializer = EmployeeSerializerPrimaryKeyFields(model.Employee)
employee = db_session.query(model.Employee).get(2)
employee = db_session.get(model.Employee, 2)
serialized = serializer.dump(employee)
data_regression.check(serialized, basename='test_one2one_pk_field')

Expand All @@ -152,14 +154,14 @@ class CompanySerializer(ModelSerializer):
employees = PrimaryKeyField(model.Employee)

serializer = CompanySerializer(model.Company)
company = db_session.query(model.Company).get(5)
company = db_session.get(model.Company, 5)
serialized = serializer.dump(company)
data_regression.check(serialized)

serialized['employees'] = [2, 3]
company = serializer.load(serialized, existing_model=company, session=db_session)
assert company.employees[0] == db_session.query(model.Employee).get(2)
assert company.employees[1] == db_session.query(model.Employee).get(3)
assert company.employees[0] == db_session.get(model.Employee, 2)
assert company.employees[1] == db_session.get(model.Employee, 3)


def test_property_serialization(model, db_session):
Expand All @@ -169,15 +171,15 @@ class EmployeeSerializerHybridProperty(ModelSerializer):
full_name = Field(dump_only=True)

serializer = EmployeeSerializerHybridProperty(model.Employee)
serialized = serializer.dump(db_session.query(model.Employee).get(2))
serialized = serializer.dump(db_session.get(model.Employee, 2))
assert serialized['full_name'] is not None


def test_protected_field_default_creation(model, db_session):
seed_data(db_session, model)

serializer = ModelSerializer(model.Employee)
employee = db_session.query(model.Employee).get(1)
employee = db_session.get(model.Employee, 1)
assert employee._salary == 400
serialized = serializer.dump(employee)
assert serialized.get('role') == 'Manager'
Expand All @@ -193,23 +195,23 @@ def test_inherited_model_serialization(model, db_session):

serializer = PolymorphicModelSerializer(model.Employee)

manager = db_session.query(model.Employee).get(1)
manager = db_session.get(model.Employee, 1)
assert isinstance(manager, model.Manager)

serialized = serializer.dump(manager)
assert serialized.get('role') == 'Manager'
entity = serializer.load(serialized, session=db_session)
assert hasattr(entity, 'manager_name')

engineer = db_session.query(model.Employee).get(2)
engineer = db_session.get(model.Employee, 2)
assert isinstance(engineer, model.Engineer)

serialized = serializer.dump(engineer)
assert serialized.get('role') == 'Engineer'
entity = serializer.load(serialized, session=db_session)
assert hasattr(entity, 'engineer_name')

engineer = db_session.query(model.Employee).get(4)
engineer = db_session.get(model.Employee, 4)
assert isinstance(engineer, model.SpecialistEngineer)

serialized = serializer.dump(engineer)
Expand All @@ -218,18 +220,18 @@ def test_inherited_model_serialization(model, db_session):
assert hasattr(entity, 'specialization')


def test_nested_inherited_model_serialization(model, db_session):
def test_nested_inherited_model_serialization(model, db_session: Session) -> None:
seed_data(db_session, model)

serializer = PolymorphicModelSerializer(model.Engineer)

engineer = db_session.query(model.Employee).get(2)
engineer = db_session.get(model.Employee, 2)
assert isinstance(engineer, model.Engineer)
serialized = serializer.dump(engineer)
assert serialized.get('role') == 'Engineer'
assert 'specialization' not in serialized.keys()

specialist_engineer = db_session.query(model.Employee).get(4)
specialist_engineer = db_session.get(model.Employee, 4)
assert isinstance(specialist_engineer, model.SpecialistEngineer)
serialized = serializer.dump(specialist_engineer)
assert serialized.get('role') == 'Specialist Engineer'
Expand Down Expand Up @@ -278,10 +280,10 @@ class EmployeeSerializerCreationOnlyField(ModelSerializer):
assert employee.firstname == 'Other'


def test_dump_choice_type(model, db_session, data_regression):
def test_dump_choice_type(model, db_session: Session, data_regression):
seed_data(db_session, model)

tychus = db_session.query(model.Employee).get(3)
tychus = db_session.get(model.Employee, 3)
serializer = ModelSerializer(model.Employee)
dump = serializer.dump(tychus)
data_regression.check(dump, basename='test_dump_choice_type')
Expand Down
8 changes: 6 additions & 2 deletions src/serialchemy/datetime_serializer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import re
import warnings
from datetime import datetime, timedelta, timezone, date
from datetime import date
from datetime import datetime
from datetime import timedelta
from datetime import timezone

from .serializer import Serializer, ColumnSerializer
from .serializer import ColumnSerializer
from .serializer import Serializer

DATETIME_REGEX = (
r"(?P<Y>\d{2,4})-(?P<m>\d{2})-(?P<d>\d{2})"
Expand Down
7 changes: 6 additions & 1 deletion src/serialchemy/enum_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@

class EnumKeyField(Field):
def __init__(self, enum_class, dump_only=False, load_only=False, creation_only=False):
super().__init__(dump_only=dump_only, load_only=load_only, creation_only=creation_only, serializer=EnumKeySerializer(enum_class))
super().__init__(
dump_only=dump_only,
load_only=load_only,
creation_only=creation_only,
serializer=EnumKeySerializer(enum_class),
)
9 changes: 5 additions & 4 deletions src/serialchemy/enum_serializer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Type

from enum import Enum
from typing import Type

from .serializer import ColumnSerializer, Serializer
from .serializer import ColumnSerializer
from .serializer import Serializer


class EnumSerializer(ColumnSerializer):
Expand All @@ -15,6 +15,7 @@ def load(self, serialized, session=None):
enum = getattr(self.column.type, 'enum_class')
return enum(serialized)


class EnumKeySerializer(Serializer):
def __init__(self, enum_class: Type[Enum]) -> None:
super().__init__()
Expand All @@ -26,4 +27,4 @@ def dump(self, value):
return value.name

def load(self, serialized, session=None):
return self.enum_class[serialized]
return self.enum_class[serialized]
Loading
Loading