Metadata-Version: 2.5
Name: krossdb
Version: 0.2.0
Summary: Enterprise-grade, multi-database unified interface & repository pattern library for Python
Project-URL: Homepage, https://github.com/ayal408/krossdb
Project-URL: Repository, https://github.com/ayal408/krossdb
Project-URL: Changelog, https://github.com/ayal408/krossdb/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/ayal408/krossdb/issues
Author: KrossDB Maintainers
License: MIT
License-File: LICENSE
Keywords: async,database,mongodb,mysql,postgresql,qdrant,repository-pattern,sqlalchemy,sqlite,unit-of-work,vector-database
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: anyio>=4.15
Requires-Dist: opentelemetry-api>=1.44.0
Requires-Dist: opentelemetry-sdk>=1.44.0
Requires-Dist: pydantic-settings<3.0,>=2.15.0
Requires-Dist: pydantic<3.0,>=2.13
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: rich>=14.2
Requires-Dist: sqlalchemy[asyncio]<3.0,>=2.0.54
Requires-Dist: structlog>=25.1
Requires-Dist: tenacity>=9.1
Requires-Dist: typer>=0.27.2
Provides-Extra: all
Requires-Dist: aiosqlite>=0.21; extra == 'all'
Requires-Dist: alembic>=1.16; extra == 'all'
Requires-Dist: asyncmy>=0.2.15; extra == 'all'
Requires-Dist: asyncpg>=0.31; extra == 'all'
Requires-Dist: motor>=3.7.1; extra == 'all'
Requires-Dist: opentelemetry-exporter-otlp>=1.44.0; extra == 'all'
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.65b0; extra == 'all'
Requires-Dist: pymongo>=4.18; extra == 'all'
Requires-Dist: qdrant-client>=1.19; extra == 'all'
Requires-Dist: redis[hiredis]>=8.1.0; extra == 'all'
Requires-Dist: sqlparse>=0.5.3; extra == 'all'
Provides-Extra: cache
Requires-Dist: redis[hiredis]>=8.1.0; extra == 'cache'
Provides-Extra: dev
Requires-Dist: aiosqlite>=0.21; extra == 'dev'
Requires-Dist: alembic>=1.16; extra == 'dev'
Requires-Dist: faker>=40.38.0; extra == 'dev'
Requires-Dist: httpx>=0.28.1; extra == 'dev'
Requires-Dist: motor>=3.7.1; extra == 'dev'
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: pre-commit>=4.0; extra == 'dev'
Requires-Dist: pymongo>=4.18; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.15; extra == 'dev'
Requires-Dist: pytest>=8.4; extra == 'dev'
Requires-Dist: qdrant-client>=1.19; extra == 'dev'
Requires-Dist: redis[hiredis]>=8.1.0; extra == 'dev'
Requires-Dist: ruff>=0.13; extra == 'dev'
Requires-Dist: sqlparse>=0.5.3; extra == 'dev'
Requires-Dist: testcontainers[mongodb,postgres,qdrant]>=4.13; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0.12.20260815; extra == 'dev'
Provides-Extra: migrations
Requires-Dist: alembic>=1.16; extra == 'migrations'
Requires-Dist: sqlparse>=0.5.3; extra == 'migrations'
Provides-Extra: mongodb
Requires-Dist: motor>=3.7.1; extra == 'mongodb'
Requires-Dist: pymongo>=4.18; extra == 'mongodb'
Provides-Extra: mysql
Requires-Dist: asyncmy>=0.2.15; extra == 'mysql'
Provides-Extra: otel-exporters
Requires-Dist: opentelemetry-exporter-otlp>=1.44.0; extra == 'otel-exporters'
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.65b0; extra == 'otel-exporters'
Provides-Extra: postgres
Requires-Dist: asyncpg>=0.31; extra == 'postgres'
Provides-Extra: sqlite
Requires-Dist: aiosqlite>=0.21; extra == 'sqlite'
Provides-Extra: vector
Requires-Dist: qdrant-client>=1.19; extra == 'vector'
Description-Content-Type: text/markdown

# krossdb

[![CI](https://github.com/ayal408/krossdb/actions/workflows/ci.yml/badge.svg)](https://github.com/ayal408/krossdb/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/krossdb.svg)](https://pypi.org/project/krossdb/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](pyproject.toml)

An enterprise-grade, multi-database Python library that provides a unified
interface and repository pattern over relational (PostgreSQL/MySQL/SQLite),
document (MongoDB), and vector (Qdrant) databases.

The Python package you import is `krossdb`.

## Why

Most projects that touch more than one kind of database end up with
per-backend, ad-hoc data access code, inconsistent error handling, and no
shared story for retries, caching, multi-tenancy, or observability. krossdb
gives every backend the same repository/Unit-of-Work surface, the same
exception hierarchy, and the same resilience and observability behavior, so
application code depends on `krossdb`'s abstractions instead of a specific
driver.

## Architecture at a glance

```
src/krossdb/
├── core/            settings, enums, the exception hierarchy, the driver
│                     exception mapper, structlog config, correlation/tenant
│                     context (contextvars)
├── interfaces/       ABCs: AbstractDatabaseAdapter, AbstractRepository,
│                     AbstractUnitOfWork, AbstractCache, domain events/outbox
├── adapters/         concrete backends, one package per DB family
│   ├── relational/    PostgreSQL / MySQL / SQLite via async SQLAlchemy,
│   │                  with master/replica read-write splitting
│   ├── document/      MongoDB via Motor
│   └── vector/        Qdrant
├── repositories/     backend-specific AbstractRepository implementations
├── uow/               Unit of Work implementations
├── cache/             Redis-backed cache-aside implementation
├── resilience/        circuit breaker + exponential-backoff retry
├── events/             in-process dispatcher + transactional outbox
├── observability/     metrics, tracing, audit trail
├── multitenancy/      tenant isolation helpers
├── factory/            DatabaseFactory: builds/owns every configured adapter
├── models/             shared Pydantic base models (Entity, AuditRecord)
├── migrations/         optional Alembic env.py wiring (see `migrations` extra)
└── cli/                `krossdb` console script (init, health, schema, ...)
```

Every domain entity is a Pydantic v2 model; every mutating repository method
supports an idempotency key; every raised error is a `KrossDBError` subclass
(driver exceptions are translated centrally in
`core/exception_mapper.py`, never leaked to callers).

## Installation

```bash
pip install krossdb             # from PyPI
pip install krossdb[postgres]   # + asyncpg
pip install krossdb[all]        # everything
```

For local development (editable install from a clone):

```bash
pip install -e ".[dev]"          # core + dev/test tooling
pip install -e ".[postgres]"     # + asyncpg
pip install -e ".[all]"          # everything
```

## Quickstart

```python
from krossdb import ConnectionConfig, DatabaseFactory, DatabaseKind, Entity, NodeConfig, RoutingRole
from krossdb.repositories.relational_repository import SQLAlchemyRepository

class Widget(Entity):
    name: str
    quantity: int = 0

config = ConnectionConfig(
    name="primary",
    kind=DatabaseKind.SQLITE,
    nodes=[NodeConfig(dsn="sqlite+aiosqlite:///:memory:", role=RoutingRole.MASTER)],
)

async with DatabaseFactory([config]) as factory:
    repo = SQLAlchemyRepository(factory.get("primary"), widgets_table, Widget)
    widget = await repo.create(Widget(name="gizmo", quantity=10))
```

See [`examples/quickstart.py`](examples/quickstart.py) for a complete, runnable
version (table creation, CRUD, exception handling, and a transactional Unit
of Work) — `python examples/quickstart.py` after `pip install -e ".[sqlite]"`.

## Declarative schema

Tables can also be described in YAML/JSON instead of SQLAlchemy Core code:

```yaml
# schema.yaml
tables:
  - name: widgets
    columns:
      - name: id
        type: uuid
        primary_key: true
      - name: name
        type: string
        required: true
        unique: true
      - name: quantity
        type: integer
        required: true
```

```bash
krossdb schema create --connection primary --schema schema.yaml --config krossdb.yaml
```

`krossdb.schema.build_metadata` turns this into a real `sqlalchemy.MetaData`, so the same
file produces correct DDL on Postgres, MySQL, and SQLite. See
[`examples/schema.yaml`](examples/schema.yaml) for a complete example with a foreign key
and an index; `--metadata module.path:attribute_name` still works as before for
code-defined `MetaData` objects — pass exactly one of `--schema` or `--metadata`.

## CLI

```bash
krossdb init                     # scaffold a starter krossdb.yaml
krossdb health                   # connect to every configured database and report status
krossdb schema create            # metadata.create_all() against a configured connection
krossdb schema init-migrations   # scaffold an Alembic project wired to krossdb.yaml
                                  # (requires `pip install krossdb[migrations]`)
```

`schema init-migrations` scaffolds `alembic.ini` + `alembic/` pre-wired to
read the database URL and target `MetaData` from `krossdb.yaml` instead of a
hardcoded `sqlalchemy.url`; day-to-day migration work still goes through
Alembic's own CLI (`alembic revision --autogenerate`, `alembic upgrade head`,
...). See `krossdb schema init-migrations --help` and
`krossdb.migrations.configure` for details.

## Versioned schema migrations

The optional migration layer compares a declarative schema with an existing
database, previews additive changes and runs project-owned numbered revisions:

```bash
pip install "krossdb[migrations,sqlite]"
krossdb schema plan --connection primary --schema schema.yaml
krossdb schema migrate --connection primary --manifest migrations.yaml --dry-run
krossdb schema migrate --connection primary --manifest migrations.yaml
```

PostgreSQL and SQLite apply pending changes and checksum-verified history in
one locked transaction. Renames, type changes and removals require explicit
migrations. MySQL continues to use Alembic because its DDL implicitly commits.
See [the migration guide](docs/schema-migrations.md) for guarantees, dialect
boundaries and async usage, and [a generic manifest](examples/migrations.yaml).

## Testing

```bash
pytest -m unit                   # fast, no external services required
pytest -m integration            # spins up real Postgres/MongoDB/Qdrant
                                  # containers via Testcontainers (needs Docker)
pytest                           # everything
```

Integration tests are automatically skipped when Docker isn't reachable, so
`pytest -m unit` (or plain `pytest` in a Docker-less environment) never fails
because of them.

## License

MIT — see [LICENSE](LICENSE).
