Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 46 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

# Release pipeline — fires on version tags (vX.Y.Z).
#
# Publishes the pure-Python root package admina-framework to PyPI and
# creates the GitHub Release. The Rust accelerator admina-core is built
# Publishes the pure-Python root package admina-framework to PyPI, then the
# alias package admina (packaging/admina: no code, it installs
# admina-framework at the same version), and creates the GitHub Release. The Rust accelerator admina-core is built
# and published by a sibling workflow (.github/workflows/release-core.yml)
# triggered by the same tag — they run in parallel, independently.
#
Expand All @@ -34,7 +35,9 @@
# Workflow name: release.yml
# Environment name: pypi
#
# The matching publisher for admina-core is configured for release-core.yml.
# The publisher of the alias project "admina" has the same values, with
# PyPI Project Name: admina. The matching publisher for admina-core is
# configured for release-core.yml.
# See https://docs.pypi.org/trusted-publishers/ for details.
#
# ── How to cut a release ───────────────────────────────────────────────────
Expand Down Expand Up @@ -102,6 +105,10 @@ jobs:
run: |
uv build --out-dir dist/

- name: Build the admina alias (sdist + wheel)
run: |
uv build packaging/admina --out-dir dist-alias/

- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand All @@ -110,6 +117,14 @@ jobs:
if-no-files-found: error
retention-days: 7

- name: Upload alias artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: admina-alias-dist
path: dist-alias/*
if-no-files-found: error
retention-days: 7

# ── 4. Publish root admina-framework to PyPI ────────────────────────────
publish-root:
name: Publish admina-framework to PyPI
Expand Down Expand Up @@ -138,7 +153,32 @@ jobs:
packages-dir: dist/
skip-existing: true

# ── 5. Create GitHub Release ────────────────────────────────────────────
# ── 5. Publish the admina alias to PyPI ─────────────────────────────────
publish-alias:
name: Publish admina (alias) to PyPI
# After admina-framework, so that the version the alias pins exists.
needs: [build-root, tests, publish-root]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/admina/
steps:
- name: Download alias artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: admina-alias-dist
path: dist

- name: List artifacts
run: ls -la dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: dist/
skip-existing: true

# ── 6. Create GitHub Release ────────────────────────────────────────────
github-release:
name: Create GitHub Release
needs: [publish-root]
Expand All @@ -148,11 +188,11 @@ jobs:
with:
fetch-depth: 0 # needed to extract the CHANGELOG section for this tag

- name: Download all artifacts
- name: Download admina-framework artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: admina-root-dist
path: artifacts
merge-multiple: true

- name: Extract changelog for this version
id: changelog
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ stability commitment. See [ROADMAP.md](ROADMAP.md) for planned milestones.

## [Unreleased]

### Added

- The PyPI package `admina`: `pip install admina` installs
`admina-framework` at the same version, and `admina[X]` installs
`admina-framework[X]` for every extra. It has no code of its own
(`packaging/admina`), and `release.yml` publishes it after
admina-framework from the same tag. `scripts/check-versions.py` checks its
version and its admina-framework pin.

### Notes

- `tests/test_iban_it.py::test_matching_time_on_long_runs` (a 50 ms budget
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ pip install admina-framework
> matters more than that extra coverage. See
> [Performance](#performance--hybrid-python--rust-engine) for the trade-off.

The PyPI package `admina` is an alias: `pip install admina` installs
`admina-framework` at the same version, and `admina[X]` installs
`admina-framework[X]` for every extra above. It has no code of its own, so
both names install the same `admina` module.

### Or install from source

```bash
Expand Down
15 changes: 15 additions & 0 deletions packaging/admina/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# admina

`pip install admina` installs [`admina-framework`](https://pypi.org/project/admina-framework/),
the Admina governed AI development framework, at the same version. This
package has no code of its own; `import admina` comes from `admina-framework`.

Every extra of `admina-framework` is available under the same name:

```bash
pip install admina # SDK
pip install "admina[proxy]" # SDK + proxy + dashboard
pip install "admina[full]" # proxy + NLP + telemetry
```

Documentation, changelog and source: https://github.com/admina-org/admina
69 changes: 69 additions & 0 deletions packaging/admina/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

# Copyright © 2025–2026 Stefano Noferi & Admina contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The PyPI project "admina": no code of its own, it installs admina-framework
# at the same version. Every extra of admina-framework has an extra of the same
# name here (admina[proxy] installs admina-framework[proxy]). The version and
# the pin are checked by scripts/check-versions.py, the extras by
# tests/test_admina_alias.py; release.yml publishes it after admina-framework.

[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

[project]
name = "admina"
version = "0.13.1"
description = "Admina — governed AI development framework (installs admina-framework)"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
authors = [
{name = "Stefano Noferi", email = "info@admina.org"},
]
maintainers = [
{name = "Stefano Noferi", email = "info@admina.org"},
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3",
]
dependencies = ["admina-framework==0.13.1"]

[project.urls]
Homepage = "https://admina.org"
Repository = "https://github.com/admina-org/admina"
Documentation = "https://github.com/admina-org/admina#readme"
Changelog = "https://github.com/admina-org/admina/blob/main/CHANGELOG.md"

[project.optional-dependencies]
proxy = ["admina-framework[proxy]==0.13.1"]
proxy-minimal = ["admina-framework[proxy-minimal]==0.13.1"]
nlp = ["admina-framework[nlp]==0.13.1"]
telemetry = ["admina-framework[telemetry]==0.13.1"]
rust = ["admina-framework[rust]==0.13.1"]
full = ["admina-framework[full]==0.13.1"]
openai = ["admina-framework[openai]==0.13.1"]
ollama = ["admina-framework[ollama]==0.13.1"]
anthropic = ["admina-framework[anthropic]==0.13.1"]
mistral = ["admina-framework[mistral]==0.13.1"]
gemini = ["admina-framework[gemini]==0.13.1"]
bedrock = ["admina-framework[bedrock]==0.13.1"]
adapters = ["admina-framework[adapters]==0.13.1"]
presidio = ["admina-framework[presidio]==0.13.1"]
all = ["admina-framework[all]==0.13.1"]

[tool.setuptools]
packages = []
15 changes: 15 additions & 0 deletions scripts/check-versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
6. uv.lock → resolved admina-framework entry
7. uv.lock → resolved admina-core entry ([rust] extra)
8. core-rust/uv.lock → resolved admina-core entry
9. packaging/admina/pyproject.toml → admina (PyPI alias)
10. packaging/admina/pyproject.toml → its admina-framework pin

Docker images and dashboard HTML derive their version dynamically from
pyproject.toml at build time, so they need no separate check here.
Expand Down Expand Up @@ -103,7 +105,18 @@ def _uv_lock_package(path: Path, pkg: str) -> str:
raise RuntimeError(f"{pkg!r} entry not found in {path}")


def _alias_framework_pin(path: Path) -> str:
"""The version of the ``admina-framework==X`` dependency of the alias."""
data = tomllib.loads(path.read_text(encoding="utf-8"))
for dep in data["project"].get("dependencies", []):
m = re.fullmatch(r"admina-framework==(\S+)", dep.strip())
if m:
return m.group(1)
raise RuntimeError(f"no admina-framework==X dependency in {path}")


def main(repo: Path = REPO) -> int:
alias = repo / "packaging" / "admina" / "pyproject.toml"
versions: dict[str, str] = {
"pyproject.toml": _toml_version(repo / "pyproject.toml"),
"admina/__init__.py": _python_dunder_version(repo / "admina" / "__init__.py"),
Expand All @@ -113,6 +126,8 @@ def main(repo: Path = REPO) -> int:
"core-rust/Cargo.toml": _toml_version(repo / "core-rust" / "Cargo.toml", table="package"),
"core-rust/Cargo.lock": _cargo_lock_admina_core(repo / "core-rust" / "Cargo.lock"),
"core-rust/uv.lock": _uv_lock_package(repo / "core-rust" / "uv.lock", "admina-core"),
"packaging/admina/pyproject.toml": _toml_version(alias),
"packaging/admina (admina-framework pin)": _alias_framework_pin(alias),
}

canonical = versions["pyproject.toml"]
Expand Down
56 changes: 56 additions & 0 deletions tests/test_admina_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright © 2025–2026 Stefano Noferi & Admina contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""The ``admina`` alias package (``packaging/admina``) mirrors admina-framework.

``pip install admina[X]`` installs ``admina-framework[X]`` at the same
version, for every extra X of admina-framework, and the alias ships no
module of its own (``import admina`` comes from admina-framework only).
"""

from __future__ import annotations

import tomllib
from pathlib import Path

REPO = Path(__file__).resolve().parent.parent


def _project(path: Path) -> dict:
return tomllib.loads(path.read_text(encoding="utf-8"))


ROOT = _project(REPO / "pyproject.toml")
ALIAS = _project(REPO / "packaging" / "admina" / "pyproject.toml")


def test_the_alias_installs_admina_framework_at_its_version():
version = ROOT["project"]["version"]
assert ALIAS["project"]["name"] == "admina"
assert ALIAS["project"]["version"] == version
assert ALIAS["project"]["dependencies"] == [f"admina-framework=={version}"]


def test_every_extra_of_admina_framework_has_an_alias_extra():
version = ROOT["project"]["version"]
expected = {
extra: [f"admina-framework[{extra}]=={version}"]
for extra in ROOT["project"]["optional-dependencies"]
}
assert ALIAS["project"]["optional-dependencies"] == expected


def test_the_alias_ships_no_module():
assert ALIAS["tool"]["setuptools"]["packages"] == []
assert ALIAS["project"]["requires-python"] == ROOT["project"]["requires-python"]
17 changes: 15 additions & 2 deletions tests/test_check_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,24 @@ def _write_repo(
cargo: str | None = None,
runtime: str | None = None,
locked_core: str | None = None,
alias: str | None = None,
alias_pin: str | None = None,
) -> Path:
"""Write the eight tracked manifests of a repository at ``version``.
"""Write the tracked manifests of a repository at ``version``.

``cargo`` is the crate version (Cargo.toml and Cargo.lock), ``runtime``
the ``__version__`` and ``locked_core`` the admina-core entry of uv.lock;
each defaults to ``version``.
each defaults to ``version``. ``alias`` and ``alias_pin`` are the
version of the ``admina`` alias and its admina-framework pin.
"""
alias = alias or version
alias_pin = alias_pin or version
(root / "packaging" / "admina").mkdir(parents=True)
(root / "packaging" / "admina" / "pyproject.toml").write_text(
f'[project]\nname = "admina"\nversion = "{alias}"\n'
f'dependencies = ["admina-framework=={alias_pin}"]\n',
encoding="utf-8",
)
cargo = cargo or version
runtime = runtime or version
locked_core = locked_core or version
Expand Down Expand Up @@ -128,6 +139,8 @@ def test_aligned_final_release_passes(tmp_path):
({"cargo": "0.13.0"}, "core-rust/Cargo.toml"),
({"runtime": "0.12.1"}, "admina/__init__.py"),
({"locked_core": "0.9.3"}, "uv.lock (admina-core)"),
({"alias": "0.12.2"}, "packaging/admina/pyproject.toml"),
({"alias_pin": "0.12.2"}, "packaging/admina (admina-framework pin)"),
],
)
def test_real_drift_fails(tmp_path, capsys, overrides, drifting):
Expand Down
Loading