Metadata-Version: 2.4
Name: frequenz-core
Version: 1.4.0
Summary: Core utilities to complement Python's standard library
Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
License-Expression: MIT
Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-core-python/
Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-core-python/releases
Project-URL: Issues, https://github.com/frequenz-floss/frequenz-core-python/issues
Project-URL: Repository, https://github.com/frequenz-floss/frequenz-core-python
Project-URL: Support, https://github.com/frequenz-floss/frequenz-core-python/discussions/categories/support
Keywords: asyncio,collections,core,datetime,frequenz,lib,library,math,python,stdlib,typing
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: <4,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typing-extensions<5,>=4.13.0
Provides-Extra: dev-flake8
Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
Requires-Dist: flake8-datetimez==20.10.0; extra == "dev-flake8"
Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
Requires-Dist: pydoclint==0.9.1; extra == "dev-flake8"
Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
Provides-Extra: dev-formatting
Requires-Dist: black==26.5.1; extra == "dev-formatting"
Requires-Dist: isort==8.0.1; extra == "dev-formatting"
Provides-Extra: dev-mkdocs
Requires-Dist: Markdown==3.10.2; extra == "dev-mkdocs"
Requires-Dist: black==26.5.1; extra == "dev-mkdocs"
Requires-Dist: mike==2.2.0; extra == "dev-mkdocs"
Requires-Dist: mkdocs-gen-files==0.6.1; extra == "dev-mkdocs"
Requires-Dist: mkdocs-literate-nav==0.6.3; extra == "dev-mkdocs"
Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
Requires-Dist: mkdocs-material==9.7.7; extra == "dev-mkdocs"
Requires-Dist: mkdocstrings[python]==1.0.6; extra == "dev-mkdocs"
Requires-Dist: mkdocstrings-python==2.0.5; extra == "dev-mkdocs"
Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-mkdocs"
Provides-Extra: dev-mypy
Requires-Dist: mypy==2.3.0; extra == "dev-mypy"
Requires-Dist: types-Markdown==3.10.2.20260712; extra == "dev-mypy"
Requires-Dist: frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
Provides-Extra: dev-noxfile
Requires-Dist: nox==2026.7.11; extra == "dev-noxfile"
Requires-Dist: frequenz-repo-config[lib]==0.18.0; extra == "dev-noxfile"
Provides-Extra: dev-pylint
Requires-Dist: frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
Provides-Extra: dev-pytest
Requires-Dist: pytest==9.1.1; extra == "dev-pytest"
Requires-Dist: pylint==4.0.6; extra == "dev-pytest"
Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.18.0; extra == "dev-pytest"
Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
Requires-Dist: pytest-asyncio==1.4.0; extra == "dev-pytest"
Requires-Dist: async-solipsism==0.9; extra == "dev-pytest"
Requires-Dist: hypothesis==6.163.0; extra == "dev-pytest"
Provides-Extra: dev
Requires-Dist: frequenz-core[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
Dynamic: license-file

# Frequenz Core Library

[![Build Status](https://github.com/frequenz-floss/frequenz-core-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-core-python/actions/workflows/ci.yaml)
[![PyPI Package](https://img.shields.io/pypi/v/frequenz-core)](https://pypi.org/project/frequenz-core/)
[![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-core-python/)

## Introduction

Core utilities to complement Python's standard library. This library provides
essential building blocks for Python applications, including mathematical
utilities, datetime constants, typing helpers, strongly-typed identifiers, and
module introspection tools.

The `frequenz-core` library is designed to be lightweight, type-safe, and
follow modern Python best practices. It fills common gaps in the standard
library with utilities that are frequently needed across different projects.

## Supported Platforms

The following platforms are officially supported (tested):

- **Python:** 3.11
- **Operating System:** Ubuntu Linux 20.04
- **Architectures:** amd64, arm64

## Installation

You can install the library from PyPI using pip:

```bash
python -m pip install frequenz-core
```

Or add it to your project's dependencies in `pyproject.toml`:

```toml
[project]
dependencies = [
    "frequenz-core >= 1.0.2, < 2",
]
```

> [!NOTE]
> We recommend pinning the dependency to the latest version for programs,
> like `"frequenz-core == 1.0.2"`, and specifying a version range spanning
> one major version for libraries, like `"frequenz-core >= 1.0.2, < 2"`.
> We follow [semver](https://semver.org/).

## Quick Start

Here's a quick overview of the main functionality:

```python
from frequenz.core.math import is_close_to_zero, Interval
from frequenz.core.datetime import UNIX_EPOCH
from frequenz.core.module import get_public_module_name

# Math utilities
print(is_close_to_zero(1e-10))  # True - check if float is close to zero
interval = Interval(1, 10)
print(5 in interval)  # True - check if value is in range

# Datetime utilities
print(UNIX_EPOCH)  # 1970-01-01 00:00:00+00:00

# Module utilities
public_name = get_public_module_name("my.package._private.module")
print(public_name)  # "my.package"
```

## Code Examples

### Math Utilities

The math module provides utilities for floating-point comparisons and interval
checking:

```python
from frequenz.core.math import is_close_to_zero, Interval

# Robust floating-point zero comparison
assert is_close_to_zero(1e-10)  # True
assert not is_close_to_zero(0.1)  # False

# Interval checking with inclusive bounds
numbers = Interval(0, 100)
assert 50 in numbers  # True
assert not (150 in numbers)  # False - 150 is outside the interval

# Unbounded intervals
positive = Interval(0, None)  # [0, ∞]
assert 1000 in positive  # True
```

### `Enum` with deprecated members

Define enums with deprecated members that raise deprecation warnings when
accessed:

```python
from frequenz.core.enum import Enum, deprecated_member, unique

@unique
class TaskStatus(Enum):
   OPEN = 1
   IN_PROGRESS = 2
   # Duplicate values are fine with `@unique` as long as they are deprecated
   PENDING = deprecated_member(1, "PENDING is deprecated, use OPEN instead")
   DONE = deprecated_member(3, "DONE is deprecated, use FINISHED instead")
   FINISHED = 4

status1 = TaskStatus.PENDING  # Warns: "PENDING is deprecated, use OPEN instead"
assert status1 is TaskStatus.OPEN
```

### Typing Utilities

Disable class constructors to enforce factory pattern usage:

```python
from frequenz.core.typing import disable_init

@disable_init
class ApiClient:
    @classmethod
    def create(cls, api_key: str) -> "ApiClient":
        # Factory method with validation
        instance = cls.__new__(cls)
        # Custom initialization logic here
        return instance

# This will raise TypeError:
# client = ApiClient()  # ❌ TypeError

# Use factory method instead:
client = ApiClient.create("my-api-key")  # ✅ Works
```

Annotate floating point values honestly, as Python's numeric tower lets `int`
values through any `float` annotation:

```python
from typing import assert_never

from frequenz.core.typing import FloatInt

def describe(value: FloatInt | None) -> str:
    match value:
        case float() | int():
            return f"number {value}"
        case None:
            return "nothing"
        case unexpected:
            assert_never(unexpected)

assert describe(1) == "number 1"  # ✅ `case float():` alone would crash here
assert describe(1.5) == "number 1.5"
assert describe(None) == "nothing"
```

### Strongly-Typed IDs

Create type-safe identifiers for different entities:

```python
from frequenz.core.id import BaseId

class UserId(BaseId, str_prefix="USR"):
    pass

class OrderId(BaseId, str_prefix="ORD"):
    pass

user_id = UserId(123)
order_id = OrderId(456)

print(f"User: {user_id}")  # User: USR123
print(f"Order: {order_id}")  # Order: ORD456

# Type safety prevents mixing different ID types
def process_user(user_id: UserId) -> None:
    print(f"Processing user: {user_id}")

process_user(user_id)  # ✅ Works
# process_user(order_id)  # ❌ Type error
```

## Documentation

For information on how to use this library, please refer to the
[documentation](https://frequenz-floss.github.io/frequenz-core-python/).

## Contributing

If you want to know how to build this project and contribute to it, please
check out the [Contributing Guide](CONTRIBUTING.md).
