Metadata-Version: 2.1
Name: pydbantic
Version: 0.0.3
Summary: 'db' within pydantic - A single model for shaping, creating, accessing, storing data within a Database
Home-page: https://github.com/codemation/pydbantic
Author: Joshua Jamison
Author-email: joshjamison1@gmail.com
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7, <4
Description-Content-Type: text/markdown
Requires-Dist: SQLAlchemy (==1.3.24)
Requires-Dist: databases (==0.4.3)
Requires-Dist: aioredis (==2.0.0)
Requires-Dist: pydantic (==1.8.2)
Requires-Dist: asyncpg (==0.24.0)
Requires-Dist: aiosqlite (==0.17.0)

# pydbantic
'db' within pydantic - A single model for shaping, creating, accessing, storing data within a Database

## Key Features
- Integrated Redis Caching Support
- Automatic Migration on Schema Changes
- Flexible Data Types
- One Model to Manage

## Basic Usage - Model

```python
from typing import List, Optional
from pydantic import BaseModel, Field
from pydbantic import DataBaseModel

class Department(DataBaseModel):
    id: str 
    name: str
    company: str
    is_sensitive: bool = False

class Positions(DataBaseModel):
    id: str
    name: str
    department: Department

class EmployeeInfo(DataBaseModel):
    ssn: str
    first_name: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]

class Employee(DataBaseModel):
    id: str
    employee_info: EmployeeInfo
    position: Positions
    salary: float
    is_employed: bool
    date_employed: Optional[str]
```

## Basic Usage - Connecting a Database to Models

```python
import asyncio
from pydbantic import Database
from models import Employee

async def main():
    db = await Database.create(
        'sqlite:///test.db',
        tables=[Employee]
    )

if __name__ == '__main__':
    asyncio.run(main())
```

## Model Usage

```python
from models import (
    Employee, 
    EmployeeInfo, 
    Position, 
    Department
)

async def main():
    # db creation is above

    # create department 
    hr_department = Department(
        id='d1234',
        name='hr'
        company='abc-company',
        is_sensitive=True,
    )

    # create a Position in Hr Department
    hr_manager = Position(
        id='p1234',
        name='manager',
        department=hr_department
    )

    # create information on an hr employee
    hr_emp_info = EmployeeInfo(
        ssn='123-456-789',
        first_name='john',
        last_name='doe',
        address='123 lane',
        city='snake city',
        zip=12345
    )

    # create an hr employee 
    hr_employee = Employee(
        id='e1234',
        employee_info=hr_emp_info,
        position=hr_manager,
        is_employed=True,
        date_employed='1970-01-01'
    )

```
Note: At this point only the models have been created, but nothing is saved in the database yet.

```python
    # save to database
    await hr_employee.save()
```

Save results in a new row created in `Employee` table as well as the related `EmployeeInfo`, `Position`, `Department` tables if non-existing.  

## What is pydbantic
`pydbantic` was built to solve some of the most common pain developers may face working with databases. 
- migrations 
- model creation / managment
- caching

`pydbantic` believes that related data should be stored together, in the shape the developer plans to use

`pydbantic` knows data is rarely flat and follows a set schema

`pydbantic` understand migrations are not fun, and does them for you

`pydbantic` speaks many `types` 


## Pillars
- [pydantic](https://pydantic-docs.helpmanual.io/) - Models, Type validation
- [databases](https://www.encode.io/databases/) - Database Connection Abstractions
- [sqlalchemy](https://www.sqlalchemy.org/) - Core Database Query and Database Model 

## Models
`pydbantic` most basic object is a `DataBaseModel`. This object may be comprised of almost any `pickle-able` python object, though you are encouraged to stay within the type-validation land by using `pydantic`'s `BaseModels` and validators.

### Primary Keys
`DataBaseModel` 's also have a priamry key, which is the first item defined in a model.

```python
class NotesBm(DataBaseModel):
    id: str              # required - primary key
    text: Optional[str]  # optional
    data: DataModel      # required 
    coridinates: tuple   # required
    items: list          # required
    nested: dict = {'nested': True} # Optional - w/ Default
```
### Model Types & Typing
`DataBaseModel` items are capable of being multiple layers deep following `pydantic` model validation
- Primary Key - First Item, must be unique
- Required - items without default values are assumed required
- Optional - marked explicitly with `typing.Optional` or with a default value
- Union - Accepts Either specified input type Union[str|int]
- List[item] - Lists of specified items

Input datatypes without a natural / built in serialization path are serialized using `pickle` and stored as bytes. More on this later. 

## Migrations
`pydbantic` handles migrations automatically in response to detected model changes: `New Field`, `Removed Field`, `Modified Field`, `Renamed Field` 

### Renaming an exiting column
Speical consideration is needed when renaming a field in a `DataBaseModel`, extra metadata `__renamed__` is needed to ensure existing data is migrated:

```python
# field `first_name` is renamed to `first_names`

class EmployeeInfo(DataBaseModel):
    __renamed__= [{'old_name': 'first_name', 'new_name': 'first_names'}]
    ssn: str
    first_names: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]
```


## Cache
Adding cache with Redis is easy with `pydbantic`, and is complete with built in `cache invalidation`. 

```python
    db = await Database.create(
        'sqlite:///test.db',
        tables=[Employee],
        cache_enabled=True,
        redis_url="redis://localhost"
    )
```

