Metadata-Version: 2.4
Name: f40-toolkit
Version: 1.1.0
Summary: Shared config, logging, cache and common tooling for 40F services.
Author-email: 40F ECSD <m.domenicale@40-factory.com>
License-Expression: MIT
Project-URL: Source, https://bitbucket.org/40factory/f40-toolkit/src/main/
Project-URL: Documentation, https://bitbucket.org/40factory/f40-toolkit/src/main/docs/
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: cache
Provides-Extra: docs
Requires-Dist: mkdocs; extra == "docs"
Requires-Dist: mkdocs-material; extra == "docs"
Requires-Dist: mkdocstrings[python]; extra == "docs"
Requires-Dist: mkdocs-autorefs; extra == "docs"
Provides-Extra: cache-redis
Requires-Dist: redis>=5.0; python_version >= "3.12" and extra == "cache-redis"
Provides-Extra: config
Requires-Dist: pyyaml>=6.0; extra == "config"
Provides-Extra: config-s3
Requires-Dist: boto3<2,>=1.34; extra == "config-s3"
Provides-Extra: all
Requires-Dist: redis>=5.0; python_version >= "3.12" and extra == "all"
Requires-Dist: pyyaml>=6.0; extra == "all"
Requires-Dist: boto3<2,>=1.34; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# f40-toolkit

Shared configuration, logging, caching, and common utilities for 40F services.

`f40-toolkit` is a dependency-light Python package intended to provide a stable set of building blocks that multiple services can share without duplicating glue code.

It currently focuses on four areas:

- **Config** (`f40_toolkit.config`) — local single-file and layered config loading, env overrides, canonical key aliases, and optional remote config overlays from HTTP or S3-compatible object storage.
- **Logging** (`f40_toolkit.logging`) — opinionated logging setup with console / rotating-file handlers and channel-based level overrides.
- **Cache** (`f40_toolkit.cache`) — backend-agnostic cache manager with memory, file, and optional Redis backends.
- **Common** (`f40_toolkit.common`) — shared utilities used across the modules above.

## Requirements

- Python **3.12+**

## Installation

Core package, with minimal dependencies:

```bash
pip install f40-toolkit
```

Optional extras:

```bash
# Marker extra for cache (memory/file backends; no extra dependency)
pip install f40-toolkit[cache]

# Redis backend support for cache
pip install f40-toolkit[cache-redis]

# YAML support for config loading
pip install f40-toolkit[config]

# S3-compatible remote config support
pip install f40-toolkit[config-s3]

# YAML + S3-compatible remote config support
pip install f40-toolkit[config,config-s3]

# Convenience install for all optional runtime features
pip install f40-toolkit[all]
```

## What is included

### `f40_toolkit.config`

The configuration subsystem is designed to let services use a homogeneous API while staying flexible about where configuration comes from.

Highlights:

- single-file mode via `<PREFIX>CONFIG_PATH`
- layered mode via `<PREFIX>CONFIG_DIR`, `<PREFIX>PROJECT`, and `<PREFIX>ENV`
- deep env var overrides with `__`
- canonical key aliases for migrations
- optional remote overlay support from:
  - HTTP/HTTPS
  - S3-compatible object storage (for example Scaleway object storage)
- optional mirrored remote layered loading

Typical usage:

```python
from f40_toolkit.config import get_config

cfg = get_config()
include_system = cfg.get("ops.include_system_in_health", False)
```

### `f40_toolkit.logging`

Logging is explicit and does not configure itself at import time.

Highlights:

- console + file logging configuration helpers
- channel-based level overrides
- session-aware loggers
- safe defaults for service startup

### `f40_toolkit.cache`

The cache module provides a common cache API that can be backed by different storage implementations.

Highlights:

- memory backend
- file backend
- optional Redis backend
- cache key helpers
- simple observability / health-check support
- `get_or_set(...)` convenience flow

### `f40_toolkit.common`

Shared helpers used by the rest of the toolkit, including:

- env parsing
- deep merge / dict helpers
- config redaction for logging
- small filesystem / path helpers

## Quickstart

### Configuration

Minimal usage:

```python
from f40_toolkit.config import configure_global_config, get_config

configure_global_config()
cfg = get_config()

port = cfg.get("server.port", 8080)
```

Using canonical keys during a migration:

```python
from f40_toolkit.config import configure_global_config, get_config

CANONICAL_KEYS = {
    "server.port": ["server.backend_port", "server_settings.port"],
}

configure_global_config(canonical_keys=CANONICAL_KEYS)

cfg = get_config()
port = cfg.get_canonical("server.port", default=8080)
```

### Logging

```python
from f40_toolkit.logging import configure_logging, get_logger

configure_logging()
log = get_logger("service.startup", channel="service")
log.info("Service booted")
```

### Cache

```python
from f40_toolkit.cache import create_cache_from_config, InvalidCacheValue

cfg = {
    "env": "prod",
    "service": {"name": "billing"},
    "customer": "acme",
    "cache": {
        "backend": "memory",      # "memory" | "file" | "redis"
        "default_timeout": 300,
        "serializer": "json",
    },
}

cache = create_cache_from_config(cfg)
cache.set("example", {"ok": True}, timeout=60)

try:
    value = cache.get("example")
except InvalidCacheValue:
    value = None

print(value)
```

Memoization helper:

```python
def expensive(a: int, b: int) -> int:
    return a + b

value = cache.get_or_set(
    key="sum:1:2",
    func=expensive,
    f_args=[1, 2],
    timeout=120,
)
```

## Configuration conventions

By default the config loader uses the `F40_` env prefix.

Important env vars:

- `F40_CONFIG_PATH` — enables single-file mode
- `F40_CONFIG_EXTRA` — optional extra files appended after local config
- `F40_CONFIG_DIR` — root directory for layered mode
- `F40_PROJECT` — project name for layered mode
- `F40_ENV` — environment / stage for layered mode

`F40_CONFIG_EXTRA` uses the platform path separator:

- Linux / macOS: `:`
- Windows: `;`

Deep env var overrides use `__` as a path separator:

```bash
export F40_DB__HOST="db.internal"
export F40_SERVER__PORT="8080"
export F40_FEATURES__0__NAME="beta"
```

Those can be read back with the same dotted path style:

```python
cfg.get("features.0.name")
```

## Remote config

Remote config is bootstrapped from local config and/or environment variables.

Supported remote providers:

- `http`
- `s3`

Supported remote modes:

- `overlay`
- `mirrored_layers`

Example HTTP overlay bootstrap:

```toml
[remote_config]
enabled = true
provider = "http"
mode = "overlay"
url = "https://example.internal/config/my-service.toml"
```

Example S3-compatible overlay bootstrap:

```toml
[remote_config]
enabled = true
provider = "s3"
mode = "overlay"
bucket_name = "service-config"
object_path = "my-service/prod.toml"
endpoint_url = "https://s3.fr-par.scw.cloud"
region_name = "fr-par"
access_key_env = "SCW_ACCESS_KEY"
secret_key_env = "SCW_SECRET_KEY"
```

Effective precedence is:

1. local main file or local layered files
2. local extra files from `<PREFIX>CONFIG_EXTRA`
3. remote overlay or remote mirrored layers
4. env var overrides

Env vars always win.

## Development

Editable install:

```bash
pip install -e .[dev]
```

Run checks:

```bash
pytest
ruff check .
mypy src
```

## Local documentation

The repository includes Markdown docs under `docs/`.

They can be:

- read directly in the repository
- served locally with MkDocs

To run a local docs site:

```bash
pip install -e .[docs]
mkdocs serve
```

## License

MIT. See `LICENSE`.
