-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
302 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import pydantic | ||
from typing import List, Optional | ||
from timeit import timeit | ||
import random | ||
import string | ||
|
||
|
||
# Define the Address and User models | ||
class Address(pydantic.BaseModel): | ||
street: str | ||
city: str | ||
zipcode: str | ||
|
||
|
||
class User(pydantic.BaseModel): | ||
name: str | ||
age: int | ||
address: Address | ||
friends: List[str] | ||
email: Optional[str] = None | ||
phone: Optional[str] = None | ||
|
||
|
||
# Helper function to generate random strings | ||
def random_string(length=10): | ||
return ''.join(random.choices(string.ascii_letters, k=length)) | ||
|
||
|
||
# Helper function to generate random data for User objects | ||
def generate_random_data(num_objects): | ||
data = [] | ||
for _ in range(num_objects): | ||
user_data = { | ||
"name": random_string(), | ||
"age": random.randint(18, 80), | ||
"address": { | ||
"street": random_string(), | ||
"city": random_string(), | ||
"zipcode": random_string(5) | ||
}, | ||
"friends": [random_string() for _ in range(5)], | ||
"email": random_string() + "@example.com" if random.choice([True, False]) else None, | ||
"phone": "".join(random.choices(string.digits, k=10)) if random.choice([True, False]) else None | ||
} | ||
data.append(user_data) | ||
return data | ||
|
||
|
||
# Function to benchmark the creation of User objects using pre-generated data | ||
def benchmark_user_creation(data): | ||
for user_data in data: | ||
user = User(**user_data) | ||
|
||
|
||
# Run the benchmark | ||
if __name__ == "__main__": | ||
num_objects = 10000 # Number of User objects to create | ||
|
||
# Generate the random data first | ||
data = generate_random_data(num_objects) | ||
|
||
# Time the creation of User objects using the pre-generated data | ||
time_taken = timeit(lambda: benchmark_user_creation(data), number=1) | ||
print(f"Time taken to create {num_objects} User objects: {time_taken:.2f} seconds") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import unittest | ||
from typing import List, Optional | ||
|
||
from vlidt import BaseModel | ||
|
||
|
||
# Define a subclass of BaseModel to test the functionality | ||
|
||
class Person(BaseModel): | ||
name: str | ||
age: int | ||
hobbies: Optional[List[str]] = None | ||
|
||
|
||
class TestBaseModel(unittest.TestCase): | ||
def test_valid_data(self): | ||
# Test with valid data | ||
person = Person(name="John", age=30, hobbies=["reading", "cycling"]) | ||
self.assertEqual(person.name, "John") | ||
self.assertEqual(person.age, 30) | ||
self.assertEqual(person.hobbies, ["reading", "cycling"]) | ||
|
||
def test_invalid_type(self): | ||
# Test with invalid data (age should be int, not str) | ||
with self.assertRaises(TypeError): | ||
person = Person(name="John", age="thirty", hobbies=["reading", "cycling"]) | ||
|
||
def test_optional_field(self): | ||
# Test with None in the optional field | ||
person = Person(name="Jane", age=25, hobbies=None) | ||
self.assertEqual(person.name, "Jane") | ||
self.assertEqual(person.age, 25) | ||
self.assertIsNone(person.hobbies) | ||
|
||
def test_missing_required_field(self): | ||
# Test with a missing required field (name) | ||
with self.assertRaises(TypeError): | ||
person = Person(age=30, hobbies=["reading", "cycling"]) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import unittest | ||
from typing import List, Optional | ||
from dataclasses import dataclass | ||
|
||
# Assuming load and dump are part of your custom implementation | ||
from vlidt import load, dump, BaseModel # Replace `your_module` with the actual module name | ||
|
||
|
||
class Address(BaseModel): | ||
street: str | ||
city: str | ||
zipcode: str | ||
|
||
|
||
class User(BaseModel): | ||
name: str | ||
age: int | ||
address: Address | ||
friends: List[str] | ||
email: Optional[str] = None # Optional field | ||
phone: Optional[str] = None # Optional field | ||
|
||
|
||
class TestSerialization(unittest.TestCase): | ||
|
||
def setUp(self): | ||
# Base data without optional fields | ||
self.user_data = { | ||
"name": "John Doe", | ||
"age": 30, | ||
"address": { | ||
"street": "123 Main St", | ||
"city": "Anytown", | ||
"zipcode": "12345" | ||
}, | ||
"friends": ["Alice", "Bob"] | ||
} | ||
|
||
# Base instance without optional fields(BaseModel) | ||
self.user_instance = User( | ||
name="John Doe", | ||
age=30, | ||
address=Address( | ||
street="123 Main St", | ||
city="Anytown", | ||
zipcode="12345" | ||
), | ||
friends=["Alice", "Bob"] | ||
) | ||
|
||
def test_dump(self): | ||
dumped_data = dump(self.user_instance) | ||
#self.assertEqual(dumped_data, self.user_data) | ||
|
||
|
||
def test_load(self): | ||
loaded_instance = load(User, self.user_data) | ||
self.assertEqual(loaded_instance, self.user_instance) | ||
|
||
def test_dump_and_load(self): | ||
dumped_data = dump(self.user_instance) | ||
loaded_instance = load(User, dumped_data) | ||
self.assertEqual(loaded_instance, self.user_instance) | ||
|
||
def test_optional_fields(self): | ||
# Data with optional fields included | ||
user_data_with_optional = { | ||
"name": "John Doe", | ||
"age": 30, | ||
"address": { | ||
"street": "123 Main St", | ||
"city": "Anytown", | ||
"zipcode": "12345" | ||
}, | ||
"friends": ["Alice", "Bob"], | ||
"email": "john.doe@example.com", | ||
"phone": "123-456-7890" | ||
} | ||
|
||
# Instance with optional fields included | ||
user_instance_with_optional = User( | ||
name="John Doe", | ||
age=30, | ||
address=Address( | ||
street="123 Main St", | ||
city="Anytown", | ||
zipcode="12345" | ||
), | ||
friends=["Alice", "Bob"], | ||
email="john.doe@example.com", | ||
phone="123-456-7890" | ||
) | ||
|
||
# Dump test | ||
dumped_data = dump(user_instance_with_optional) | ||
self.assertEqual(dumped_data, user_data_with_optional) | ||
|
||
# Load test | ||
loaded_instance = load(User, user_data_with_optional) | ||
self.assertEqual(loaded_instance, user_instance_with_optional) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from .validata import BaseModel | ||
from .base import dump, load | ||
from .base import BaseModel | ||
from .lump import dump, load | ||
from .validation import validate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
|
||
class BaseModelAbc(ABC): | ||
@abstractmethod | ||
def __type__(self): | ||
NotImplemented() | ||
|
||
@abstractmethod | ||
def __type__(self): | ||
NotImplemented() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,21 @@ | ||
from typing_validation import validate as vli | ||
from typing import Generator, Any, Type, Dict | ||
from dataclasses import fields, Field, dataclass, is_dataclass | ||
from dataclasses import dataclass | ||
|
||
from .abc import BaseModelAbc | ||
from .lump import dump_type, collect_sub_dataclasses | ||
from .validation import validate | ||
|
||
|
||
@dataclass | ||
class BaseModel: | ||
class BaseModel(BaseModelAbc): | ||
"""Base class for data models that automatically becomes a dataclass.""" | ||
def __post_init__(self): | ||
validate(self) | ||
|
||
def __init_subclass__(cls, **kwargs): | ||
# every class has inherite BaseModel make it dataclass | ||
dataclass(cls) | ||
|
||
|
||
def iterate_fields(cls: Type[BaseModel]) -> Generator[Any, Field, None]: | ||
"""Yield field values and Field objects for a given dataclass.""" | ||
for fld in fields(cls): | ||
yield getattr(cls, fld.name), fld | ||
|
||
|
||
def iterate_fields_type(cls: Type[BaseModel]) -> Generator[Any, Field, None]: | ||
"""Yield field values and their corresponding types from a dataclass.""" | ||
for value, fld in iterate_fields(cls): | ||
yield getattr(cls, fld.name), fld.type | ||
|
||
|
||
def validate(cls): | ||
"""Validate each field in a dataclass using the provided validation function.""" | ||
return list(map( | ||
lambda x: vli(*x), | ||
iterate_fields_type(cls) | ||
)) | ||
|
||
|
||
def dump(dt: Type[BaseModel]) -> Dict: | ||
"""Convert a dataclass instance into a dictionary representation.""" | ||
return { | ||
fld.name: val if not is_dataclass(val) else dump(val) for val, fld in iterate_fields(dt) | ||
} | ||
|
||
|
||
def load(dt: Type[BaseModel], data: Dict) -> BaseModel: | ||
"""Create a dataclass instance from a dictionary representation.""" | ||
kwargs = { | ||
fld.name: data[fld.name] if not is_dataclass(fld.type) else load(fld.type, data[fld.name]) | ||
for fld in fields(dt) | ||
} | ||
return dt(**kwargs) | ||
cls.__type__ = dump_type(cls) | ||
cls.__sub_dataclasses__ = collect_sub_dataclasses(cls) | ||
|
||
|
||
def dump_type(dt: Type[BaseModel]) -> Dict: | ||
"""Return a dictionary mapping field names to their types for a dataclass.""" | ||
return { | ||
fld.name: fld.type if not is_dataclass(fld.type) else dump_type(fld.type) for fld in fields(dt) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from .abc import BaseModelAbc as BaseModel | ||
|
||
from typing import Type, Dict | ||
from dataclasses import fields, is_dataclass | ||
|
||
|
||
def dump(dt: Type[BaseModel]) -> Dict: | ||
"""Convert a dataclass instance into a dictionary representation.""" | ||
kwarg = dt.__dict__.copy() | ||
for name, typ in dt.__sub_dataclasses__.items(): | ||
kn = kwarg[name] | ||
if type(kn) is not dict: | ||
kwarg[name] = dump(kn) | ||
return kwarg | ||
|
||
|
||
def load(dt: Type[BaseModel], data: Dict) -> BaseModel: | ||
"""Create a dataclass instance from a dictionary representation.""" | ||
if is_dataclass(data): | ||
return data | ||
# Check if the dataclass has fields | ||
for name, dtcls in dt.__sub_dataclasses__.items(): | ||
data[name] = load(dtcls, data[name]) | ||
|
||
return dt(**data) | ||
|
||
|
||
def dump_type(dt: Type[BaseModel]) -> Dict: | ||
"""Return a dictionary mapping field names to their types for a dataclass.""" | ||
return { | ||
fld.name: fld.type if not is_dataclass(fld.type) else dump_type(fld.type) | ||
for fld in fields(dt) | ||
} | ||
|
||
|
||
def collect_sub_dataclasses(dt): | ||
return { | ||
fld.name: fld.type | ||
for fld in filter(lambda fld: is_dataclass(fld.type), fields(dt)) | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from typing import Generator, Any, Type | ||
from dataclasses import fields, Field | ||
|
||
|
||
def iterate_fields(cls) -> Generator[Any, Field, None]: | ||
"""Yield field values and Field objects for a given dataclass.""" | ||
for fld in fields(cls): | ||
fld.compare | ||
yield getattr(cls, fld.name), fld | ||
|
||
|
||
def iterate_fields_type(cls) -> Generator[Any, Field, None]: | ||
"""Yield field values and their corresponding types from a dataclass.""" | ||
for value, fld in iterate_fields(cls): | ||
yield getattr(cls, fld.name), fld.type |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from typing_validation import validate as vli | ||
|
||
from .utils import iterate_fields_type | ||
|
||
|
||
def validate(cls): | ||
"""Validate each field in a dataclass using the provided validation function.""" | ||
return list(map( | ||
lambda x: vli(*x), | ||
iterate_fields_type(cls) | ||
)) |