Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/wilsonwang371/betterschema

a python library for define and validate schema data
https://github.com/wilsonwang371/betterschema

Last synced: about 6 hours ago
JSON representation

a python library for define and validate schema data

Awesome Lists containing this project

README

        

# BetterSchema

BetterSchema is a schema validation and data watching framework for Python. It allows you to define data schemas, validate data against these schemas, and watch for changes in the data with custom watchers.

## Features

- **Schema Definition**: Easily define data schemas using type hints and decorators.
- **Data Validation**: Automatically validate data against the defined schemas.
- **Nested Schemas**: Support for nested schemas within data models.
- **Watchers**: Monitor changes to data attributes and perform custom actions.
- **Optional Fields**: Handle optional fields within your schemas.
- **Type Checking**: Enforce type constraints on data attributes.

## Installation

You can install BetterSchema via pip:

```sh
pip install betterschema==0.1.18
```

## Usage

### Defining Schemas

To define a schema, you can use the `@core.schema` decorator on a class definition. Inside the class, you can define the attributes of the schema using type hints. You can also define nested schemas within the main schema class.

```python
import betterschema.core as core

@core.schema
class Foo:
foo1: str # String field
foo2: int # Integer field
foo3: bool # Boolean field
foo4: list[str] # List of strings
foo5: core.Optional[int] # Optional field
foo6: dict[str, int] # Dictionary field
foo7: core.Optional[core.Union[int, str]] # Optional field with multiple types

@core.schema
class EmbeddedSchema:
bar1: str
bar2: int
bar3: bool

bar: EmbeddedSchema # Nested schema field
```

### Creating Instances

You can create instances of the schema by passing a dictionary with the required fields to the schema class constructor. The data will be validated against the schema automatically.

```python

# Creating an instance of the schema
foo = Foo({
"foo1": "hello",
"foo2": 0,
"foo3": True,
"foo4": ["a", "b", "c"],
"foo6": {"a": 1, "b": 2},
"bar": Foo.EmbeddedSchema({
"bar1": "world",
"bar2": 20,
"bar3": False,
})
})

# For nested schemas, you can also give a dictionary directly
foo = Foo({
"foo1": "hello",
"foo2": 0,
"foo3": True,
"foo4": ["a", "b", "c"],
"foo6": {"a": 1, "b": 2},
"bar": {
"bar1": "world",
"bar2": 20,
"bar3": False,
}
})

```

### Updating Values

You can update the values of the schema attributes directly. The data will be validated on each update.

```python

# Updating values
foo.foo1 = "hello2"
foo.foo2 = 1

# if you try to set an invalid value, it will raise an exception
foo.foo1 = 1 # This will raise a TypeError

# you can also specify a dictionary to update multiple values at once
# in order to do this, you need to use the `<<` operator
foo << {
"foo1": "hello3",
"foo2": 2,
}

# for nested schemas, you can update the whole schema at once
foo.bar = {
"bar1": "world2",
"bar2": 30,
"bar3": True,
}

# or use the `<<` operator to do partial updates

foo.bar << {
"bar1": "world3",
"bar2": 40,
}

```

### Watching Values

You can define watchers to monitor changes to specific attributes of the schema. Watchers are defined using the `@core.watch` decorator, which takes a tuple of the form `(schema_class, attribute_name)` as an argument.

```python

@core.watch((Foo, "foo1"), (Foo, "foo2"))
def watch_values(inst, name: str, old, new):
print(f"watch_values: {inst}.{name}, {old} -> {new}")
if name == "foo1" and new == "hi":
raise ValueError("foo1 cannot be 'hi'")

```

Regular expressions can also be used to match multiple attributes at once.

```python

@core.watch((Foo, "foo[12]"))
def watch_values(inst, name: str, old, new):
print(f"watch_values: {inst}.{name}, {old} -> {new}")
if name == "foo1" and new == "hi":
raise ValueError("foo1 cannot be 'hi'")

```

### Name Mapping

`core.schema` can take an optional argument `mapping` which is a dictionary that maps the schema attribute names to the corresponding data attribute names. This can be useful when the schema attribute names do not match the data attribute names. Example:

```python

@core.schema(mapping={"foo1": "data_foo1", "foo2": "data_foo2"})
class Foo:
foo1: str
foo2: int

```

## Contributing

Contributions are welcome! Please submit a pull request or open an issue to discuss your ideas.

## License

This project is licensed under the MIT License.