Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8583cd6
Sleep for 30 seconds then test import
sbalian Nov 24, 2024
bd94d9e
Use GitHub environment
sbalian Nov 24, 2024
36473e4
Linting
sbalian Nov 24, 2024
7ee4bac
Linting
sbalian Nov 24, 2024
29e8e38
Update linters and type checker
sbalian Nov 24, 2024
d8ea684
Update analysis instructions
sbalian Nov 24, 2024
e4a6c22
Update README
sbalian Nov 24, 2024
16e4783
Use typer
sbalian Nov 24, 2024
3a6f1c1
Remove XDG
sbalian Nov 24, 2024
24282b2
Misc improvements for API key
sbalian Nov 24, 2024
5b31f31
Use absolute imports
sbalian Nov 24, 2024
56a4afd
Remove pyright ignore
sbalian Nov 24, 2024
136ea51
Minor cleanup
sbalian Nov 24, 2024
2d804a3
Update docstring for API key function
sbalian Nov 24, 2024
b2b4859
Better error handling for INI file
sbalian Nov 24, 2024
c3a2ea4
Improve error message.
sbalian Nov 24, 2024
292fe49
Update README
sbalian Nov 25, 2024
94dca99
Update actionS
sbalian Mar 15, 2026
e5912eb
Update dependencies
sbalian Mar 15, 2026
eca3603
Latest pyproject.toml
sbalian Mar 15, 2026
495cf32
ty, drop support for py310, misc updates
sbalian Mar 15, 2026
bc3f581
Test output
sbalian Mar 15, 2026
2690df8
Add back newline
sbalian Mar 15, 2026
290782f
Verbose pytest output
sbalian Mar 15, 2026
b65f8a5
Remove -vv from pytest
sbalian Mar 15, 2026
a13d304
Change shell
sbalian Mar 15, 2026
92b1015
Debug
sbalian Mar 15, 2026
58ee768
Debug
sbalian Mar 15, 2026
547826a
Debug
sbalian Mar 15, 2026
874daaf
Debug
sbalian Mar 15, 2026
977da1b
Fix CI
sbalian Mar 15, 2026
51d58e5
Fix Python version selection in CI via UV env vars
sbalian Mar 15, 2026
e3c5f09
Use color=False in all CLI test runners for consistent output
sbalian Mar 15, 2026
9b5eda1
Fix CLI tests and tidy up CI
sbalian Mar 15, 2026
7da2d38
Add CLAUDE.md
sbalian Mar 15, 2026
36cc0b9
Fix abort message ANSI codes in CLI test
sbalian Mar 15, 2026
b4baea6
Use strip_ansi() in CLI tests instead of color=False
sbalian Mar 15, 2026
ce04434
Add ipython
sbalian Mar 15, 2026
4cdd3c2
Fix typo in README
sbalian Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@ name: Publish
on:
push:
tags:
- '*'
- "*"

jobs:
release:
permissions:
id-token: write
name: Release
runs-on: ubuntu-latest
environment: release
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
with:
version: "0.5.4"
version: "0.10.10"
- name: Build
run: uv build
- name: Publish
run: uv publish
- name: Sleep
run: sleep 30s
shell: bash
- name: Test import
run: uv run --with quantum-random --no-project -- python -c "import qrandom"
40 changes: 19 additions & 21 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,41 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
env:
UV_PYTHON: ${{ matrix.python-version }}
UV_MANAGED_PYTHON: "1"
defaults:
run:
shell: bash
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
with:
version: "0.5.4"
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
version: "0.10.10"
- name: Install the project
run: uv sync --all-extras --dev
run: uv sync --all-extras --all-groups
- name: Run pytest
run: QRANDOM_API_KEY=key uv run pytest
- name: Run mypy
run: uv run mypy --install-types --non-interactive .
- name: Run ty
run: uv run ty check .

lint:
name: Lint
runs-on: ubuntu-latest
env:
UV_PYTHON: "3.12"
UV_MANAGED_PYTHON: "1"
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v7
with:
version: "0.5.4"
- name: Set up Python
run: uv python install
version: "0.10.10"
- name: Install the project
run: uv sync --all-extras --dev
- name: Run black
run: uv run black --check .
- name: Run isort
run: uv run isort --check .
- name: Run flake8
run: uv run flake8 --extend-exclude .venv
run: uv sync --all-extras --all-groups
- name: Check linting
run: uv run ruff check .
39 changes: 39 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# quantum-random

Python library for quantum random numbers, fetched from the [ANU Quantum Random Numbers Server](https://quantumnumbers.anu.edu.au).

## Commands

```bash
# Install with all extras and dev dependencies
uv sync --all-extras --all-groups

# Run tests
QRANDOM_API_KEY=key uv run pytest

# Type check
uv run ty check .

# Lint
uv run ruff check .
```

## Structure

- `src/qrandom/` — library source
- `_api.py` — HTTP client for the ANU API
- `_generator.py` — `random.Random` subclass backed by quantum randomness
- `_cli.py` — `qrandom-init` CLI for storing the API key
- `_util.py` — XDG config path utilities
- `numpy.py` — optional numpy/randomgen integration
- `tests/` — pytest tests
- `analysis/` — notebooks/scripts (not part of the package)

## CI

GitHub Actions runs tests across Python 3.10–3.14 on Linux, macOS, and Windows, plus a lint job pinned to 3.12. Python versions are managed via `UV_PYTHON` and `UV_MANAGED_PYTHON=1` env vars set at the job level.

## Notes

- API key is read from `QRANDOM_API_KEY` env var or a config file (`qrandom.ini`) in `QRANDOM_CONFIG_DIR` (defaults to XDG config home)
- Use `click.utils.strip_ansi()` on `result.output` in CLI test assertions to handle ANSI codes consistently across platforms
68 changes: 25 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
![License](https://img.shields.io/github/license/sbalian/quantum-random)

Use the [Python random module][pyrandom] with real quantum random numbers from
[ANU][anu]. The default pseudo-random generator is replaced by calls to
the ANU API.
[ANU Quantum Numbers][anu].

## Usage

Import `qrandom` and use it like the standard `random` module. For example:
Import `qrandom` and use it like the standard `random` module:

```python
>>> import qrandom
Expand All @@ -28,10 +27,11 @@ Import `qrandom` and use it like the standard `random` module. For example:
-0.8370871276247828
```

Alternatively, you can use the class `qrandom.QuantumRandom`. It has the same
You can also use the class `qrandom.QuantumRandom`. It has the same
interface as `random.Random`.

There is also a [NumPy][numpy] interface, although it is not fully tested:
There is also a [NumPy][numpy] interface (implemented using [RandomGen][randomgen])
but it is not fully tested:

```python
>>> from qrandom.numpy import quantum_rng
Expand All @@ -44,59 +44,45 @@ array([[0.37220278, 0.24337193, 0.67534826],
[0.35894084, 0.72219929, 0.55388594]])
```

NumPy is supported using [RandomGen][randomgen].

## Installation

The minimum supported Python version is 3.9. Install with `pip`:

```bash
pip install -U quantum-random
pip install quantum-random
```

If you want NumPy support:
The minimum supported version of Python is 3.9.

The NumPy interface is optional. To include it:

```bash
pip install -U 'quantum-random[numpy]'
pip install 'quantum-random[numpy]'
```

## First-time setup: setting your API key
## Setting the ANU Quantum Numbers API key

ANU requires you to use an API key. You can get a free trial or pay for a key
ANU Quantum Numbers requires an API key. You can get a free trial or pay for a key
[here][anupricing].

You can pass your key to `qrandom` in three ways:
You can set the key in order of precedence as follows:

1. By setting the environment variable `QRANDOM_API_KEY`.
2. By running the included command line utility `qrandom-init` to save your
key in `qrandom.ini` in a subdirectory of your home config directory
as specified by XDG, e.g., `/home/<your-username>/.config/qrandom/`.
3. By running `qrandom-init` to save your key in `qrandom.ini` in a directory
of your choice, and then specifying this directory by setting
`QRANDOM_CONFIG_DIR`.
1. Set the `QRANDOM_API_KEY` environment variable.
2. Write the key to `qrandom.ini` using the `qrandom-init` setup utility (included with
the package). By default, the INI file is saved in your home config directory
(e.g., `~/.config/` in Linux) and `qrandom` will find it without you having to set
any environment variables. If you choose to save to a different location, you
must set `QRANDOM_CONFIG_DIR`.

If `QRANDOM_API_KEY` is set, its value is used as the API key and the
config file is not read. Otherwise, `qrandom` will look for the key
in the config directory. The config directory defaults to the XDG home config
and can be changed by setting `QRANDOM_CONFIG_DIR`.
The config file is ignored if `QRANDOM_API_KEY` is set.

## Pre-fetching batches

Batches of quantum numbers are fetched from the API as needed.
Each batch contains 1024 numbers. Use `qrandom.fill(n)` to fetch `n` batches
if you need to pre-fetch at the start of your computation.

## Tests
Quantum numbers are fetched from the API in batches of 1024 as needed. Use
`qrandom.fill(n)` to pre-fetch `n` batches at the start of your computation.

The tests run for Python 3.9 - 3.12 on the latest Windows,
macOS and Ubuntu runner images.
## Implementation details

See [here](./analysis/uniform.md) for a visualisation and a Kolmogorov–Smirnov
test.

## Notes on implementation

The `qrandom` module exposes a class derived from `random.Random` with a
The default pseudo-random generator is replaced by calls to
the ANU API. The `qrandom` module exposes a class derived from `random.Random` with a
`random()` method that outputs quantum floats in the range [0, 1)
(converted from 64-bit integers). Overriding `random.Random.random`
is sufficient to make the `qrandom` module behave mostly like the
Expand All @@ -107,10 +93,6 @@ produce arbitrarily long sequences. Finally, the user is warned when `seed()`
is called because the quantum generator has no state. For the same reason,
`getstate()` and `setstate()` are not implemented.

## License

See [LICENCE](./LICENSE).

[anu]: https://quantumnumbers.anu.edu.au
[anupricing]: https://quantumnumbers.anu.edu.au/pricing
[pyrandom]: https://docs.python.org/3/library/random.html
Expand Down
9 changes: 5 additions & 4 deletions analysis/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Analysis

To run the analysis, first install the requirements (includes `quantum-random`):
To run the analysis, [install uv](https://docs.astral.sh/uv/getting-started/installation/), then:

```bash
pip install -r requirements.txt
cd analysis
uv sync --all-extras --all-groups
```

Then run the script to generate the plot in `uniform.md`:
Next, run the script to generate the plot in `uniform.md`:

```bash
python generate_plot.py
uv run generate_plot.py
```

The output is written to `random.png`.
3 changes: 1 addition & 2 deletions analysis/generate_plot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import random
from typing import List, Tuple

import matplotlib.pyplot as plt
from scipy import stats

import qrandom

DataType = Tuple[Tuple[List[float], str], Tuple[List[float], str]]
DataType = tuple[tuple[list[float], str], tuple[list[float], str]]


def generate_data() -> DataType:
Expand Down
3 changes: 0 additions & 3 deletions analysis/requirements.txt

This file was deleted.

50 changes: 23 additions & 27 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "quantum-random"
version = "1.4.2"
version = "1.5.0"
description = "Quantum random numbers"
readme = "README.md"
license = {text = "MIT License"}
Expand All @@ -24,29 +24,31 @@ classifiers = [
"Operating System :: MacOS",
"License :: OSI Approved :: MIT License",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"click>=8.1.7",
"requests>=2.32.3",
"xdg>=6.0.0",
"requests>=2.32.5",
"typer>=0.24.1",
]

[project.optional-dependencies]
numpy = [
"numpy>=2.0.2",
"randomgen>=2.1.1",
"numpy>=2.2.6",
"randomgen>=2.3.0",
]

[dependency-groups]
dev = [
"pytest-mock>=3.14.0",
"pytest>=8.3.3",
"responses>=0.25.3",
"mypy>=1.13.0",
"flake8>=7.1.1",
"isort>=5.13.2",
"black>=24.10.0",
"pip>=24.3.1",
"click>=8.3.1",
"ipython>=8.38.0",
"matplotlib>=3.10.8",
"numpy>=2.2.6",
"pytest>=9.0.2",
"pytest-mock>=3.15.1",
"randomgen>=2.3.0",
"responses>=0.26.0",
"ruff>=0.15.6",
"scipy>=1.15.3",
"ty>=0.0.23",
]

[project.scripts]
Expand All @@ -63,17 +65,11 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/qrandom"]

[tool.pytest.ini_options]
addopts = "--verbose"
testpaths = "tests"
[tool.ruff]
target-version = "py312"

[tool.mypy]
ignore_missing_imports = true
[tool.ruff.lint]
select = ["F", "E", "W", "I001"]

[tool.black]
line-length = 79

[tool.isort]
profile = "black"
line_length = 79
known_first_party = ["qrandom"]
[tool.ruff.lint.isort]
known-first-party = ["qrandom"]
Loading