Skip to content

NeoZett-School/Data-System

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 

Repository files navigation

🐍 Data: A Flexible, Type-Checked, Transactional Data Container

Data is a highly flexible Python package that provides a superior alternative to basic dictionaries and traditional dataclasses. It combines the dynamic nature of a dictionary with the rigid, type-safe structure of an object, offering features like runtime validation, immutability, and transactional updates.

✨ Key Features

  • Hybrid Access: Access data fields using both attribute syntax (d.key) and dictionary syntax (d['key']).
  • Runtime Type Checking: Enforces type hints upon initialization and modification, including support for complex generics like List[int], Dict[str, float], and Optional[str].
  • Immutability: Easily create "frozen" (immutable) data objects.
  • Transactional Updates (Context Manager): Use the with statement to temporarily unfreeze a locked object and safely commit or rollback changes.
  • Factory Decorator: Use @data_factory to easily convert standard classes into feature-rich Data objects.

⚙️ Installation

Since this package is not yet on PyPI, you must install it directly from the GitHub repository using pip.

pip install git+https://github.com/NeoZett-School/Data-System.git

🚀 Usage

1. The Base Data Class (Dynamic Dictionary)

The Data class is perfect for dynamic data that needs type validation.

from Data import Data

# Data accepts arbitrary keywords like a dictionary, but can enforce behavior
config = Data(host="localhost", port=8080, frozen=False) # include_methods have been depricated for performance

# Hybrid Access
print(config.host)   # Output: localhost
print(config['port']) # Output: 8080

# Type-checked Mutability
try:
    # If the Data object was initialized without a type hint for 'port', 
    # the runtime type check defaults to the type of the initial value (int).
    config.timeout = 5.5 # This works if no specific type was specified for 'timeout'
except TypeError as e:
    print(f"Error: {e}")s'

2. The data_factory (Dataclass-like Structure)

Use the @data_factory decorator to define structures with explicit type annotations and class-level defaults.

from Data import data_factory, Data
from typing import List

@data_factory(frozen=True) 
class UserData:
    # Include methods defaults to false. Now you can also run methods you make.
    id: int
    name: str = "Anonymous"
    roles: List[str] = ["guest"]

class Inherited(UserData, frozen=False): 
    # When inheriting, you must respecify wheter the object is frozen.
    id = 100
    
# Create a new instance
user = UserData(id=101, name="Alice") 
print(user) 
# Output: UserData(id=101, name='Alice', roles=['guest'])

# Attempting mutation will fail because frozen=True was set on the factory
try:
    user.name = "Bob"
except AttributeError:
    print("Cannot modify user: object is frozen.")

3. Transactional Context Manager (Rollback/Commit)

The context manager enables safe, temporary mutations on a frozen object, with an automatic rollback on exception.

# Create an immutable configuration object
config = Data(host="db.prod", retries=3, frozen=True)
print(f"Before: {config.retries}") # Output: Before: 3

# Scenario 1: Successful changes (Commit)
with config as temp_config:
    temp_config.retries = 5 
    temp_config.timeout = 15 # New field added

print(f"After Commit: {config.retries}") # Output: After Commit: 5
print(f"New Field: {config.timeout}")    # Output: New Field: 15

# Scenario 2: Failing changes (Rollback)
try:
    with config as temp_config:
        temp_config.retries = 99 # Tentative change
        raise RuntimeError("Network failed!") # Exception causes rollback

except RuntimeError:
    print("Rollback executed due to error.")

print(f"After Rollback: {config.retries}") # Output: After Rollback: 5 (Restored)

4. Fields for dynamic values

The use of field() and computed_field() allows better control over dynamic variables. If you want to make a property, then you should use the computed_field() for a value codependent on other features.

@data_factory
class Date:
    year: int = 2025
    month: int = 10
    day: int = field(required=True)
    year_month_day = computed_field(lambda self: f"{self.year} - {self.month} - {self.day}")

    #@computed_field
    #def year_month(self): ... 
    # Works. But this will show as a function.

today = Date(day=31) # Day is required, or you'll get an error.
print(pretty_repr(today)) # You can import "pretty_repr" from the package.
# prints: (This has been fixed in "Small patches 7")
# year: int = 2025
# month: int = 10
# day: int = 31
# year_month_day: str = '2025 - 10 - 31'

print(today.year_month_day) # -> 2025 - 10 - 31

🛠️ Developer Tools

The package provides utility functions for introspection:

from Data import (
    FrozenData, # This class will contain frozen data. It doesn't matter what you do (you can't use the `with` keyword), it is fully immutable.
    is_data_factory, 
    make_data_factory, 
    validate_data, # For inconsistencies between annotations and actual values
    inspect_data, # Alike pretty_repr, but more detailed. It also works for data factories directly.
    patch_data, # Patch data
    diff_data, # Get what differentiate between two data objects
    sync_data, # Sync two different data objects
    to_schema, # Generate a schema with annotations. Each key will be given a value that is its type.
    diff_schema, # Get what differentiate between two different schemas.
    clone, # Clone the data (like `Data.copy()`) with new content.
    pretty_repr # Quickly and efficiently understand what the object contains.
)

@data_factory
class MyModel(Data, frozen=True):
    pass

class IllegalModel(Data, frozen=True):
    # This ONLY works if you decorate the class as a data_factory.
    # Otherwise, this will not be loaded correctly. 

    # What would and wouldn't work:
    # - Use the class like the Data class, it will now always be frozen.
    # - Anything defined inside the class will not be presented.
    # - One exception: If `include_methods` is True, then all methods defined will still be available.
    pass 

# Check if a class was created using the factory
print(is_data_factory(MyModel)) # Output: True

# Instantiation using the helper function
instance = make_data_factory(MyModel, field='value')

# For any other tool, please checkout the docstrings and the imports.

🧑‍💻 Development and Contribution

This package is built entirely on standard Python features for maximum compatibility and performance. It uses advanced techniques (metaclasses, dunder methods, __slots__) to achieve its goals.

We welcome issue reports and contributions!


Copyright (c) 2025-2026 Neo Zetterberg

About

This system was build as a replacement to the python built-in dataclass system with more functionality towards data and information control. Have fun and experience impressive features. Possibly, even learn internal python coding, using dunder methods etc.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages