Metadata-Version: 2.4
Name: pychrony
Version: 3.0.2
Summary: Python bindings for chrony NTP client
Author-email: arunderwood <arunderwood@users.noreply.github.com>
License-Expression: MIT AND LGPL-2.1-or-later
Project-URL: Homepage, https://github.com/arunderwood/pychrony
Project-URL: Documentation, https://pychrony.org/
Project-URL: Repository, https://github.com/arunderwood/pychrony.git
Project-URL: Issues, https://github.com/arunderwood/pychrony/issues
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
Classifier: Topic :: System :: Networking :: Time Synchronization
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/LGPL-2.1-or-later.txt
Requires-Dist: cffi>=1.15.1; python_version == "3.11"
Requires-Dist: cffi>=1.16.0; python_version == "3.12"
Requires-Dist: cffi>=1.17.0; python_version == "3.13"
Requires-Dist: cffi>=2.0.0; python_version == "3.14"
Requires-Dist: cffi>=2.1.0; python_version >= "3.15"
Dynamic: license-file

# PyChrony: Python bindings for chrony NTP client

[![PyPI version](https://img.shields.io/pypi/v/pychrony)](https://pypi.org/project/pychrony/)
[![Python versions](https://img.shields.io/pypi/pyversions/pychrony)](https://pypi.org/project/pychrony/)
[![CI](https://github.com/arunderwood/pychrony/actions/workflows/ci.yml/badge.svg)](https://github.com/arunderwood/pychrony/actions/workflows/ci.yml)
[![Documentation](https://img.shields.io/badge/docs-pychrony.org-blue)](https://pychrony.org/)
[![License](https://img.shields.io/pypi/l/pychrony)](https://github.com/arunderwood/pychrony/blob/main/LICENSE)

Query your system's time synchronization status from Python. PyChrony provides bindings for [chrony](https://chrony-project.org/), the default NTP (Network Time Protocol) client on most Linux distributions, letting you monitor clock accuracy, sync status, and time sources programmatically.

## Features

- **Read-only monitoring**: Access tracking (offset, drift, stratum), sources (NTP servers, peers), statistics (samples, deviation), and RTC data
- **Pythonic API**: Clean, typed interface following Python conventions
- **[CFFI](https://cffi.readthedocs.io/) binding**: Efficient interface to [libchrony](https://gitlab.com/chrony/libchrony)
- **Linux-first**: Optimized for Linux environments with libchrony
- **Type hints**: Full type annotation support for better IDE integration

## Installation

```bash
pip install pychrony
```

Pre-built wheels include libchrony—no system dependencies needed.

<details>
<summary><strong>Building from Source</strong></summary>

Installing from source (the sdist, or any platform without a pre-built wheel)
compiles the CFFI bindings, so it needs a C compiler, the libffi headers, and
[libchrony](https://gitlab.com/chrony/libchrony) with its headers. libchrony is
found through `pkg-config`; for a non-standard prefix, add
`<prefix>/lib/pkgconfig` to `PKG_CONFIG_PATH`. A missing `chrony.h` or library
stops the install with an error naming it.

To build from source even where a wheel exists:

```bash
pip install --no-binary pychrony pychrony
```

#### libchrony Package Availability

| Distribution | Available | Install Command |
|--------------|-----------|-----------------|
| Fedora 42+ | ✅ Yes | `sudo dnf install libchrony-devel` |
| RHEL/CentOS/Rocky/Alma 9 | ✅ Yes (EPEL) | `sudo dnf install epel-release && sudo dnf install libchrony-devel` |
| Debian/Ubuntu | ❌ No | Build from source (see below) |
| Alpine | ❌ No | Build from source (see below) |
| Arch Linux | ❌ No | Build from source (see below) |

#### Building libchrony from Source

```bash
# Install build dependencies
# Debian/Ubuntu:
sudo apt-get install build-essential libtool-bin libffi-dev python3-dev

# Alpine:
sudo apk add gcc make libtool libffi-dev musl-dev

# Arch:
sudo pacman -S base-devel libtool libffi

# Clone and build libchrony
git clone https://gitlab.com/chrony/libchrony.git
cd libchrony
make
sudo make install prefix=/usr
sudo ldconfig
```

#### Development Install

```bash
git clone https://github.com/arunderwood/pychrony.git
cd pychrony
git submodule update --init
uv sync --all-groups
```

`uv sync` and `uv pip install` from the checkout install pychrony **without**
the compiled bindings: `[tool.uv]` in `pyproject.toml` sets
`PYCHRONY_SKIP_CFFI=1`, so development and the unit and contract tests need no
libchrony. Connecting to chronyd from such an install raises
`ChronyLibraryError`. Integration tests build the bindings in Docker (see
[TESTING.md](TESTING.md)).

With libchrony installed, `PYCHRONY_BUILD_CFFI=1 uv sync` or `pip install -e .`
builds the bindings. With other installers, `PYCHRONY_SKIP_CFFI=1` skips them.

</details>

## Usage

```python
from pychrony import ChronyConnection

with ChronyConnection() as conn:
    status = conn.get_tracking()
    print(f"Reference: {status.reference_id_name}")
    print(f"Stratum: {status.stratum}")
    print(f"Offset: {status.offset:.9f} seconds")
    print(f"Synchronized: {status.is_synchronized()}")
```

With no argument, pychrony tries chronyd's Unix socket and then its localhost
command port, using the first that connects — the same candidates
[chronyc uses](https://chrony-project.org/doc/4.9/chronyc.html).

The two are not equivalent in privilege. The Unix socket is chronyd's control
channel, restricted to the root or `chrony` user; the command port serves
monitoring commands only, which is everything pychrony reads. Read-only
consumers should prefer the command port, and can ask for it explicitly:

```python
from pychrony import ChronyConnection

with ChronyConnection("127.0.0.1") as conn:
    status = conn.get_tracking()
```

Note that joining the `chrony` group does **not** grant access to the Unix
socket. See [Choosing a Transport](https://pychrony.org/#choosing-a-transport)
for details.

## Testing Your Code

`pychrony.testing` has factories that build pychrony objects with realistic
defaults. They work in any test framework and do not need pytest:

```python
from pychrony.testing import make_tracking

status = make_tracking(stratum=16, reference_id=0)
assert not status.is_synchronized()
```

pytest users can enable fixtures (`chrony_tracking_status`, `chrony_source`,
`chrony_source_stats`, `chrony_rtc_data`) in their top-level `conftest.py`:

```python
pytest_plugins = ["pychrony.pytest_plugin"]
```

The plugin is not loaded automatically, so installing pychrony adds no
fixtures to other projects' test runs. See
[TESTING.md](https://github.com/arunderwood/pychrony/blob/main/TESTING.md#pytest-fixtures)
for details.

## Compatibility

- **Python**: 3.11, 3.12, 3.13, 3.14, 3.15
- **Platform**: Linux (primary), other platforms where libchrony is available
- **chrony**: 4.x and later

## Resources

- [Documentation](https://pychrony.org/) — Full API reference and guides
- [GitHub Issues](https://github.com/arunderwood/pychrony/issues) — Report bugs or request features
- [chrony documentation](https://chrony-project.org/documentation.html) — Understanding NTP and chrony

## License

PyChrony is licensed under MIT. See [LICENSE](LICENSE).

Pre-built wheels bundle [libchrony](https://gitlab.com/chrony/libchrony) which is licensed under LGPL-2.1-or-later. See [LICENSES/LGPL-2.1-or-later.txt](LICENSES/LGPL-2.1-or-later.txt).
