Skip to main content

A Python library for recursively loading and updating dataclasses from nested dictionaries

Project description

CK Recursive Dataclass

A Python library that extends Python's dataclasses to support recursive serialization and deserialization of nested dataclass structures. This library makes it easy to work with complex, nested data structures while maintaining type safety and providing convenient conversion methods between dataclasses and dictionaries.

Features

  • 🔄 Recursive conversion between dataclasses and dictionaries
  • 🌳 Support for multiple nested dataclass structures
  • 📦 Handle dictionaries and lists of dataclasses
  • 📝 Type-safe with full mypy support
  • ✨ Optional field support with None handling
  • 🔍 Type preservation with type field
  • 🚀 Easy serialization for API responses
  • 🐍 Compatible with Python 3.11 and 3.12

Installation

pip install ck-recursive-dataclass

Quick Start

Here's a simple example of how to use recursive dataclasses:

from dataclasses import dataclass
from ck_recursive_dataclass import RecursiveDataclass
from typing import Optional, Dict

@dataclass
class Address(RecursiveDataclass):
    street: str
    city: str
    country: str
    postal_code: Optional[str] = None

@dataclass
class Occupation(RecursiveDataclass):
    title: str
    company: str
    years_experience: int
    department: Optional[str] = None

@dataclass
class Person(RecursiveDataclass):
    name: str
    age: int
    addresses: Dict[str, Address]
    occupation: Occupation
    email: Optional[str] = None

# Create instances
home_address = Address(
    street="123 Home St",
    city="Hometown",
    country="Homeland",
    postal_code="12345"
)

work_address = Address(
    street="456 Work Ave",
    city="Workville",
    country="Workland"
)

occupation = Occupation(
    title="Senior Developer",
    company="Tech Corp",
    years_experience=5,
    department="Engineering"
)

person = Person(
    name="John Doe",
    age=30,
    addresses={"home": home_address, "work": work_address},
    occupation=occupation,
    email="john@example.com"
)

# Convert to dictionary
person_dict = person.to_dict()
print("Person as dictionary:")
print(person_dict)
# Output:
# {
#     'name': 'John Doe',
#     'age': 30,
#     'addresses': {
#         'home': {
#             'street': '123 Home St',
#             'city': 'Hometown',
#             'country': 'Homeland',
#             'postal_code': '12345',
#             '__type__': 'Address'
#         },
#         'work': {
#             'street': '456 Work Ave',
#             'city': 'Workville',
#             'country': 'Workland',
#             'postal_code': None,
#             '__type__': 'Address'
#         }
#     },
#     'occupation': {
#         'title': 'Senior Developer',
#         'company': 'Tech Corp',
#         'years_experience': 5,
#         'department': 'Engineering',
#         '__type__': 'Occupation'
#     },
#     'email': 'john@example.com',
#     '__type__': 'Person'
# }

# Create from dictionary
new_person = Person.from_dict(person_dict)

Features in Detail

Multiple Nested Dataclasses

The library handles multiple levels of nested dataclass structures automatically. For example, a Person can have both addresses and occupation as nested dataclasses:

person = Person(
    name="John Doe",
    addresses={"home": home_address},  # Address dataclass
    occupation=occupation,             # Occupation dataclass
    age=30
)

Dictionary of Dataclasses

You can use dictionaries with dataclass values, and the library will handle the conversion properly:

addresses = {
    "home": Address("123 Home St", "Hometown", "Homeland"),
    "work": Address("456 Work Ave", "Workville", "Workland")
}

Nested Dataclass Support

The library handles nested dataclass structures automatically, maintaining type information and validation throughout the conversion process.

Optional Fields

Fields marked as Optional will be properly handled during conversion:

@dataclass
class User(RecursiveDataclass):
    username: str
    email: Optional[str] = None

Type Safety

The library is fully type-hinted and works well with mypy for static type checking. During conversion:

  • Type information is preserved using the __type__ field
  • Required fields are validated
  • Type mismatches are caught during deserialization
# Type validation during deserialization
try:
    # This will raise ValueError due to missing required field 'street'
    invalid_address = Address.from_dict({
        "city": "New York",
        "country": "USA"
    })
except ValueError as e:
    print(f"Validation error: {e}")  # Missing required field street

try:
    # This will raise TypeError due to invalid input type
    invalid_person = Person.from_dict("not a dictionary")
except TypeError as e:
    print(f"Type error: {e}")  # Expected dict, got str

Serialization Support

The library is particularly useful for API development where you need to:

  • Serialize complex objects to JSON-compatible dictionaries
  • Deserialize nested JSON data into typed Python objects
  • Maintain type safety throughout the process

For example:

# API response serialization
@app.get("/user/{user_id}")
def get_user(user_id: str):
    user = get_user_from_db(user_id)  # Returns Person object
    return user.to_dict()  # Returns JSON-serializable dictionary

# API request deserialization
@app.post("/user")
def create_user(user_data: dict):
    user = Person.from_dict(user_data)  # Converts dictionary to Person object
    save_user_to_db(user)
    return {"status": "success"}

Development

Prerequisites

  • Python 3.11 or higher
  • tox for running tests

Setting Up Development Environment

  1. Clone the repository:
git clone https://github.com/kimcharli/ck_recursive_dataclass.git
cd ck_recursive_dataclass
  1. Install development dependencies:
pip install tox
  1. Run tests:
tox

Running Tests

The project uses tox to run tests across different Python versions and environments:

  • Python 3.11 and 3.12 environments for compatibility testing
  • Type checking with mypy
  • Linting with ruff

Run all tests with:

tox

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Charlie Kim (kimcharli@gmail.com)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ck_recursive_dataclass-0.1.2.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ck_recursive_dataclass-0.1.2-py3-none-any.whl (6.0 kB view details)

Uploaded Python 3

File details

Details for the file ck_recursive_dataclass-0.1.2.tar.gz.

File metadata

  • Download URL: ck_recursive_dataclass-0.1.2.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for ck_recursive_dataclass-0.1.2.tar.gz
Algorithm Hash digest
SHA256 8b5b7d4c58584d4bde25e0d5dc12b93c99d5394a64864677ec548652607fcc87
MD5 6822c926fd0cf8f13991a3794cf51603
BLAKE2b-256 1743e7b268e97693922b89710bf85935838ce6ac583b49b56016f48aeb8539d9

See more details on using hashes here.

File details

Details for the file ck_recursive_dataclass-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for ck_recursive_dataclass-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b1d517ce82b85d7e6357378798b3ff9195981ecf458774ed6357141a0f37e13a
MD5 ce11155a8c60f8a940b2e4ae3f0a006c
BLAKE2b-256 11cc6d4dc1de4248b22631a48b31aa93203595344a9bebca694e82ecde75ad43

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page