Metadata-Version: 2.4
Name: infra-db
Version: 0.2.0a1
Summary: Typed, security-focused clients for Elasticsearch and native database protocols.
Author-email: Helios Quant <heliosquant@gmail.com>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://gitlab.com/nexus-packages1/infra-db
Project-URL: Source, https://gitlab.com/nexus-packages1/infra-db
Project-URL: Issues, https://gitlab.com/nexus-packages1/infra-db/-/issues
Keywords: database,elasticsearch,mongodb,postgresql,mysql,infra
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3,>=2.7
Requires-Dist: requests<3,>=2.32
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Dynamic: license-file

# infra-db

`infra-db` is a typed, synchronous Python database package with security-focused native
protocol implementations for MongoDB, PostgreSQL, and MySQL, plus an HTTP client for
Elasticsearch. It provides bounded I/O, verified TLS defaults, typed results, explicit
transactions, synchronous connection pools, and a small Spring Data-inspired relational
Repository.

> **Status: Alpha.** The package is suitable for evaluation and controlled production use,
> but its API may evolve before 1.0. Review the [current limitations](#current-limitations)
> before adopting it.

## Installation

```bash
pip install infra-db
```

Python 3.10 or newer is required. Runtime dependencies are Pydantic and `requests`.
`infra-db` does not depend on database-specific drivers, SDKs, ORMs, or migration tools.

## Supported backends

| Backend | Transport | Authentication |
| --- | --- | --- |
| Elasticsearch | HTTPS through `requests.Session` | Basic, bearer, or API key |
| MongoDB | Native BSON and OP_MSG over TCP/TLS | SCRAM-SHA-256 |
| PostgreSQL | Native PostgreSQL v3 protocol over TCP/TLS | SCRAM-SHA-256 |
| MySQL | Native MySQL protocol over TCP/TLS | `caching_sha2_password` (MySQL 8.4) |

## Elasticsearch

```python
from pathlib import Path

from infra_db import TLSOptions
from infra_db.elasticsearch import (
    BasicAuth,
    ElasticsearchClient,
    ElasticsearchSettings,
    RefreshPolicy,
)

settings = ElasticsearchSettings(
    base_url="https://localhost:9200",
    auth=BasicAuth(username="elastic", password="secret"),
    tls=TLSOptions(ca_cert_path=Path("certs/elasticsearch-ca.pem")),
)

with ElasticsearchClient(settings) as client:
    client.create_index("events", body={"mappings": {"properties": {"kind": {"type": "keyword"}}}})
    client.index_document(
        "events",
        {"kind": "startup"},
        doc_id="event-1",
        refresh=RefreshPolicy.WAIT_FOR,
    )
    result = client.search("events", {"query": {"term": {"kind": "startup"}}})
```

## MongoDB

```python
from pathlib import Path

from infra_db import TLSOptions
from infra_db.mongodb import MongoDBClient, MongoDBSettings

settings = MongoDBSettings(
    host="localhost",
    username="application",
    password="secret",
    auth_database="admin",
    tls=TLSOptions(ca_cert_path=Path("certs/mongodb-ca.pem")),
)

with MongoDBClient(settings) as client:
    events = client["application"]["events"]
    inserted = events.insert_one({"kind": "startup", "detail": None})
    document = events.find_one({"_id": inserted.inserted_id})
```

Use `MongoDBPool` when multiple threads or independently scoped operations need exclusive
connections:

```python
from infra_db import PoolOptions
from infra_db.mongodb import MongoDBPool

with MongoDBPool(settings, PoolOptions(min_size=1, max_size=8)) as pool:
    with pool.database("application") as database:
        database["events"].insert_one({"kind": "pooled"})
```

## PostgreSQL

PostgreSQL parameters use native `$1`, `$2`, ... placeholders. Values are always sent through
the extended query protocol.

```python
from pathlib import Path

from infra_db import TLSOptions
from infra_db.postgresql import PostgreSQLClient, PostgreSQLSettings

settings = PostgreSQLSettings(
    host="localhost",
    user="application",
    password="secret",
    database="application",
    tls=TLSOptions(ca_cert_path=Path("certs/postgresql-ca.pem")),
)

with PostgreSQLClient(settings) as client:
    result = client.execute(
        "SELECT $1::text AS name, $2::text AS optional_value",
        ("infra-db", None),
    )
    with client.transaction() as transaction:
        transaction.execute("INSERT INTO events(kind) VALUES ($1)", ("startup",))
```

## MySQL

MySQL parameterized queries use native prepared statements with `?` placeholders.

```python
from pathlib import Path

from infra_db import TLSOptions
from infra_db.mysql import MySQLClient, MySQLSettings

settings = MySQLSettings(
    host="localhost",
    user="application",
    password="secret",
    database="application",
    tls=TLSOptions(ca_cert_path=Path("certs/mysql-ca.pem")),
)

with MySQLClient(settings) as client:
    result = client.execute("SELECT ? AS name, ? AS optional_value", ("infra-db", None))
    with client.transaction() as transaction:
        transaction.execute("INSERT INTO events(kind) VALUES (?)", ("startup",))
```

## Relational Repository

PostgreSQL and MySQL expose the same safe Repository developer experience while retaining
separate dialects, placeholders, generated-key behavior, and protocol semantics. Tables must
already exist; the Repository never creates or alters schema.

```python
from pydantic import BaseModel

from infra_db import PoolOptions
from infra_db.postgresql import (
    PageRequest,
    PostgreSQLPool,
    Repository,
    Sort,
    SortDirection,
)

class User(BaseModel):
    id: int | None = None
    email: str
    display_name: str | None = None

class UserRepository(Repository[User, int]):
    model = User
    table = "users"
    primary_key = "id"

with PostgreSQLPool(settings, PoolOptions(max_size=8)) as pool:
    with pool.transaction() as transaction:
        users = UserRepository(transaction, batch_size=100)
        created = users.save(User(email="person@example.com", display_name=None))
        page = users.find_all(
            page=PageRequest(number=0, size=25),
            sort=Sort(field="email", direction=SortDirection.ASC),
        )
        active_users = users.find(where={"display_name": None})
        same_user = users.find_by_email("person@example.com")
```

The supported operations are `insert`, `insert_all`, `save`, `save_all`, `update`,
`find_by_id`, `find_all`, `find`, `exists_by_id`, `count`, `delete`, `delete_by_id`,
`delete_all`, and `delete_all_by_id`. Safe derived methods support `find_by_`, `exists_by_`,
`count_by_`, and `delete_by_`, including `_and_` combinations of declared model fields.

## TLS and security

- TLS with certificate and hostname verification is the default for every backend.
- Supply a private or test CA with `TLSOptions(ca_cert_path=...)`.
- Plaintext transport requires an explicit backend setting such as
  `allow_insecure_transport=True` or `allow_insecure_http=True`.
- Passwords and tokens use secret types and are excluded from settings, client, pool, and
  typed exception representations.
- MongoDB/PostgreSQL/MySQL frames and results are size-bounded; socket reads and writes are
  exact, partial-I/O aware, and timeout-bounded.
- Repository values are bound parameters. Identifiers come only from validated static model
  metadata; arbitrary SQL fragments are not accepted.

Disabling certificate verification is an explicit insecure choice. Do not use it as a
compatibility workaround.

## Error handling

Catch a backend exception for backend-specific handling, or a shared foundation exception for
cross-backend policy:

```python
from infra_db import InfraDBAuthenticationError, InfraDBPoolTimeoutError, InfraDBTimeoutError
from infra_db.postgresql import PostgreSQLQueryError

try:
    with pool.connection(timeout=1.0) as connection:
        connection.execute("SELECT $1::integer", (1,))
except InfraDBPoolTimeoutError:
    ...
except InfraDBAuthenticationError:
    ...
except InfraDBTimeoutError:
    ...
except PostgreSQLQueryError as error:
    print(error.sqlstate)  # safe structured diagnostics only
```

## Current limitations

- All APIs are synchronous; there is no async client or async pool.
- Native backends directly address one server. Topology discovery, replica-set/shard routing,
  failover, load balancing, and replication APIs are not implemented.
- MongoDB does not yet provide sessions, transactions, aggregation helpers, change streams,
  GridFS, compression, or document sequences.
- PostgreSQL does not provide COPY, LISTEN/NOTIFY, replication, or binary result formats.
- MySQL targets MySQL 8.4 with verified TLS and `caching_sha2_password`; MariaDB,
  `mysql_native_password`, multi-result workflows, replication, and `LOAD DATA` are not
  supported.
- Elasticsearch does not provide node sniffing, multi-node failover, or async transport.
- Pools validate on acquisition but do not yet provide background health checks, idle expiry,
  or connection lifetime rotation.
- The Repository is intentionally not an ORM: no relationships, lazy loading, migrations,
  automatic schema mutation, or arbitrary query grammar.

More detail is available in [pooling](docs/pooling.md), [Repository](docs/repository.md), and
[backend support](docs/backends.md).
