diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bf37feb5..f6c3a267 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -497,13 +497,14 @@ Options: ### Local service execution -If a package's `obs/_service` contains any service with `mode="manual"`, `sync` automatically runs all non-buildtime services locally before uploading. This is required for packages like Go services that use `go_modules` (mode=manual) to vendor dependencies. +If a package's `obs/_service` contains any service with `mode="manual"`, `sync` automatically runs all non-buildtime services locally before uploading. This is required for packages like Go services that use `go_modules` (mode=manual) or npm frontends that use `npm_lockfile` + `node_modules` (mode=manual) to vendor dependencies. **Execution order and file handling:** 1. All services with `mode` not in `{buildtime, serveronly, disabled}` are run in XML declaration order. 2. Each service binary is invoked from `/usr/lib/obs/service/` with its `` values and `--outdir`. 3. Service outputs are merged into a shared work directory so later services can consume earlier outputs (e.g. `go_modules` consuming `obs_scm` tarballs). 4. Only files produced by `mode="manual"` services are committed to OBS. Files produced by no-mode services (e.g. obs_scm source tarballs) are used locally but **not** uploaded — OBS regenerates those on its server. +5. `.obscpio` files produced by `mode="manual"` services (e.g. `node_modules.obscpio`) are kept and uploaded; only `obs_scm` archives are extracted locally. If a service binary is missing from `/usr/lib/obs/service/`, a warning is logged and the service is skipped. A non-zero service exit code aborts the entire `sync` run. @@ -541,6 +542,8 @@ Before invoking each `download_url` service binary, `percona-obs` computes `para **`--no-cache`**: pass to `sync` to bypass all cache levels unconditionally for that run. +**Drift-tolerant artifacts** (`services.drift_tolerant_patterns`): outputs whose bytes depend on an external registry's state at generation time — `cargo_vendor` → `vendor.tar.*`; `node_modules` → `node_modules.obscpio`, `node_modules.spec.inc`, `*package-lock.json` — are compared by presence only in the `--branch-from` content check (`cmd_sync._content_mismatches`). A file missing on either side is still a mismatch. + When targeting a specific package (`sync `), the ancestor project chain is only walked if the target project does not yet exist on OBS (fast path avoids redundant GET calls otherwise). Project names use colon notation matching the directory hierarchy (e.g. `ppg:staging:17`). @@ -959,6 +962,8 @@ When branching is involved, always confirm which OBS instance is being queried: | Third-party infrastructure service | `ppg/staging/17/etcd/` | | OBS aggregate (mirrors another OBS project) | `obs-service-tar_scm/` | | Root project config | `root/project.yaml` | +| Repo-owned OBS service (npm lockfile generation) | `tools/obs-services/npm_lockfile` | +| Version-independent devel project (pgAdmin) | `root/ppg/devel/pgadmin/project.yaml` | | Management script | `percona-obs` (commands: `sync push`, `sync delete`, `sync promote`, `build trigger`, `build status`, `build dependency`, `profile create`, `profile list`, `project verify`) | --- diff --git a/.gitignore b/.gitignore index cbbef08d..2161cc17 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ __pycache__/ tmp/ docs/plans/ .percona-obs/ +.superpowers/ diff --git a/CLAUDE.md b/CLAUDE.md index d4c949af..8d8a0f7d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,7 @@ Always use `git commit -s` (signed-off-by). Do not add `Co-Authored-By: Claude` | `percona_obs/targets.py` | Target resolution helpers | | `percona_obs/services.py` | Local OBS service execution + caching | | `percona_obs/git_utils.py` | Git helpers | +| `tools/obs-services/` | Repo-owned OBS source services run locally by `sync` (e.g. `npm_lockfile`); baked into the obs-tools image | | `requirements.txt` | Runtime + dev dependencies (`osc`, `pyyaml`, `black`, `pyright`) | | `pyrightconfig.json` | Pyright config pointing at `venv/` and `percona_obs/` | | `root/` | All packaging content (project hierarchy mirrors OBS) | diff --git a/docs/PACKAGING_HOWTO.md b/docs/PACKAGING_HOWTO.md index 571d1227..71d0e5d6 100644 --- a/docs/PACKAGING_HOWTO.md +++ b/docs/PACKAGING_HOWTO.md @@ -396,6 +396,123 @@ After fixing packaging files, trigger a rebuild (the `_service` file has not cha --- +## Python 3.12 packages (pyproject builds) + +RPM-only Python libraries for RHEL-family targets are built for `/usr/bin/python3.12` +and named `python3.12-` (`%{python3_pkgprefix}-`; plain `python3-` on +openSUSE). The directory is `python3-`. Examples: +everything under `root/ppg/devel/pgadmin/python3-*` and the build-backend stack in +`root/ppg/common/deps/` (`python3-flit-core`, `python3-packaging`, `python3-pathspec`, +`python3-trove-classifiers`, `python3-hatchling`). + +**Reuse before you build.** RHEL 9 already ships `python3.12-{cffi,cryptography,idna, +pycparser,urllib3,setuptools,pip,wheel}` (AppStream) and `python3.12-{packaging 23.2, +pluggy,pytest,setuptools-rust,flit-core 3.9,Cython 0.29 (RPM name `python3.12-Cython`, +capital C)}` (CRB); `ppg:common:deps` has +`six`, `dateutil`, `psutil`, `click`, `dns`. Depend on those by their RPM name — without a +version floor, since RHEL's versions are often older than upstream's declared minimum but +work — and only package what is missing. RHEL's *build backends* are too old for current +PEP 639 metadata; use ours from `ppg:common:deps` (see the conditional below). + +**Source:** the PyPI sdist via `download_url`, literal version: + +```xml + + + https://files.pythonhosted.org/packages/source/f/flask/flask-3.1.3.tar.gz + + +``` + +**Spec header** (identical in every package; copy from `root/ppg/devel/pgadmin/python3-flask/rpm/python3-flask.spec`): + +```rpmspec +%global debug_package %{nil} # noarch only; drop for C/Rust extensions +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") +# extensions: python3_sitearch with 'platlib' instead + +Name: %{python3_pkgprefix}-flask +Version: 3.1.3 +Release: 1%{?dist} +Source0: https://files.pythonhosted.org/packages/source/f/flask/flask-3.1.3.tar.gz +BuildArch: noarch +Epoch: 1 +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +``` + +Then the backend the sdist's `[build-system]` names — `python%{python3_buildversion}-flit-core`, +`%{python3_pkgprefix}-poetry-core`, `%{python3_pkgprefix}-pdm-backend`, `%{python3_pkgprefix}-setuptools_scm` +(EPEL) when `setuptools_scm` is listed — and for hatchling the distro/ours switch: + +```rpmspec +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling # ours, ppg:common:deps +%else +BuildRequires: python3-hatchling # EL10 CRB / openSUSE +%endif +``` + +Every runtime `Requires:` is also a `BuildRequires:` so `%check` can import the module. + +**Build/install — pyproject packages** (setuptools, flit, hatchling, poetry, pdm): + +```rpmspec +%prep +%autosetup -p1 -n flask-%{version} + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask" + +%files +%{python3_sitelib}/* +%{_bindir}/flask +``` + +The `-P` flag stops Python from putting the source directory first on `sys.path`; +without it the smoke test imports the unpacked source tree instead of the installed +package (and fails for C extensions). + +Self-hosting backends (hatchling, poetry-core, pdm-backend, flit-core) add +`export PYTHONPATH=$PWD/src` (flit-core: `$PWD/.`) before `pip wheel`. Rust extensions +(bcrypt) add a `cargo_vendor` service (`cargotoml` pointing at the crate's `Cargo.toml`), +`Source1: vendor.tar.gz`, `%autosetup -a1`, `CARGO_NET_OFFLINE=true`. + +**PEP 639 licence metadata with setuptools 68.** Sdists whose `pyproject.toml` has +`license = "SPDX-ID"` (a string) and/or `license-files = [...]` fail on RHEL's setuptools 68 +(`project.license must be valid exactly by one definition`). Add to `%prep`, after +`%autosetup`: `sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml` +(see `root/ppg/devel/pgadmin/python3-greenlet/rpm/python3-greenlet.spec`). + +**Build/install — legacy `setup.py`-only packages** (no `pyproject.toml`): keep the +`%{__ospython} setup.py build` / `setup.py install --single-version-externally-managed -O1 +--root=%{buildroot} --record=INSTALLED_FILES` recipe and `%files -f INSTALLED_FILES`, as in +`root/ppg/common/deps/python3-click`. + +**Bumping:** edit the sdist URL in `obs/_service`, `Version:` in the spec, add a +`%changelog` entry. Nothing is generated. + +--- + ## Adding a Package as an Aggregate in a Subproject When a package is built in one subproject (e.g. `common:deps:runtime`) and consumed by @@ -545,6 +662,7 @@ version before assuming this. | `debian/patches/` | if needed | Quilt patches applied to upstream source | | `debian/*.install` | if needed | File installation lists for binary packages | | `rpm/.spec` | yes | `Version: 1.0.0`; `Release: 1%{?dist}`; no `Source0` file in `rpm/` | +| `rpm/python3-.spec` | Python 3.12 libs | Template in "Python 3.12 packages"; `Version:` literal; runtime deps also `BuildRequires` | | `rpm/` | if needed | Only files referenced as `Source1`, `Source2`, … in the spec | | `obs/_service` | yes | Use env-var template for packaging services; customise only the upstream service | | `obs/_aggregate` | aggregates only | Replaces `_service`; references pre-built binaries from another project | diff --git a/docs/PERCONA_OBS_TOOL.md b/docs/PERCONA_OBS_TOOL.md index d451978f..c388d317 100644 --- a/docs/PERCONA_OBS_TOOL.md +++ b/docs/PERCONA_OBS_TOOL.md @@ -17,6 +17,11 @@ dependency vendoring): | `obs_scm` | `obs-service-obs-scm` | `obs-service-obs_scm` | | `go_modules` | `obs-service-go_modules` | `obs-service-go_modules` | | `download_url` | `obs-service-download_url` | `obs-service-download_url` | +| `node_modules` | `obs-service-node-modules` (openSUSE:Tools repo) | `obs-service-node_modules` (openSUSE:Tools repo) | +| `npm_lockfile` | this repo — `tools/obs-services/` (see its README) | same | + +`node_modules` and `npm_lockfile` additionally need `npm` (Node.js ≥ 18; the CI image +uses 22) and `cpio` on `PATH`. Binaries are expected at `/usr/lib/obs/service/`. @@ -121,6 +126,64 @@ see [root/README.md](../root/README.md) — not covered further here.) ./percona-obs -P local sync push ppg:staging:17 percona-pg-telemetry ``` +### Vendoring npm dependencies (`npm_lockfile` + `node_modules`) + +Packages with a webpack/npm frontend (pgAdmin 4) vendor their npm dependencies for +offline OBS builds with two `mode="manual"` services, run locally by `sync` in this order: + +```xml + + *percona-pgadmin4*.obscpio + web + + + node_modules.obscpio + node_modules.spec.inc + 10000 + +``` + +`npm_lockfile` (repo-owned, `tools/obs-services/`) generates `package-lock.json` with +`npm install --package-lock-only`; `node_modules` (openSUSE) reads it and downloads every +tarball into `node_modules.obscpio`, writing `SourceNNNNN:` lines to `node_modules.spec.inc` +(`%include %{_sourcedir}/node_modules.spec.inc` in the spec; `BuildRequires: local-npm-registry` +serves the tarballs to `npm` at build time). All three files are uploaded to OBS and cached +under `.cache/services//` — the lockfile is a function of the upstream commit +plus registry state, so `--no-cache` is the way to deliberately refresh the vendored set. +Each new upstream commit of an npm-vendored package adds a cache entry of roughly the +`.obscpio`'s size (~220 MB for pgAdmin); this is bounded locally by `prune_cache`, which +removes entries unused for 7 days, and in CI additionally by the `actions/cache` size limits. +`npm_lockfile` also accepts an optional `npm-flags` param (default `--legacy-peer-deps --ignore-scripts`; a given value replaces the default set) for upstreams that need different npm resolution flags. + +`root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service` is the reference user of this chain: +`obs_scm` (tag `REL-9_17`, `versionrewrite-pattern REL-(\d+)_(\d+)` → `9.17`) → +`npm_lockfile` (`subdir web`) → `node_modules` (`source-offset 10000`) → `tar`/`recompress`/ +`set_version` at build time. A `sync push --dry-run` of that package runs the whole chain +locally and takes several minutes (it downloads every npm tarball once; later runs hit the +service cache). + +Two rules in `percona-obs` make this work: + +- `.obscpio` files produced by **manual** services are uploaded as-is (OBS unpacks them into + `SOURCES` at build time); only `obs_scm` archives are extracted locally. +- **Drift-tolerant artifacts**: outputs whose bytes depend on registry state at generation + time are compared by presence only in the `--branch-from` content check, so re-resolution + never promotes a package by itself (absence on either side is still a change): + + | Service | Tolerated files | + |---|---| + | `cargo_vendor` | `vendor.tar.*` | + | `node_modules` | `node_modules.obscpio`, `node_modules.spec.inc`, `*package-lock.json` (names follow the `cpio`/`output`/`input` params) | + + Drift tolerance only affects the `--branch-from` content check's changed/unchanged + decision — it does not affect what gets uploaded. A sync that is not skipped (no + matching `--skip-unchanged` manifest entry, or run with `--force`) still uploads + whatever the local service run produced, so a regenerated drift-tolerant artifact + (for pgAdmin, a ~220 MB `node_modules.obscpio`) is re-PUT to OBS and commits a new + revision, triggering a rebuild even though nothing meaningful changed. `--skip-unchanged` + avoids this in the normal CI path because tag-pinned packages are skipped outright before + any service reruns; `--no-cache` is the deliberate way to force that refresh. + ### Sync all packages under a subproject ```sh diff --git a/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md b/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md new file mode 100644 index 00000000..537282a2 --- /dev/null +++ b/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md @@ -0,0 +1,1649 @@ +# pgAdmin 4 Tooling (sub-project 1) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make `percona-obs` (and the obs-tools CI image) able to vendor npm dependencies through the OBS `node_modules` service — generating `package-lock.json` itself — and create the empty `ppg:devel:pgadmin` OBS project that will hold every pgAdmin package. + +**Architecture:** A repo-owned OBS-style service `tools/obs-services/npm_lockfile` runs `npm install --package-lock-only` inside the unpacked upstream archive and emits `package-lock.json`; declared `mode="manual"` ahead of `node_modules`, it needs no bespoke plumbing because `services.py` already runs manual services in XML order into a shared workdir. Two generic fixes in `services.py`/`cmd_sync.py` let a manual service emit an `.obscpio` (kept, not extracted) and let the branch content check tolerate registry-driven byte drift (generalising the existing `vendor.tar.*` special case). The obs-tools image gains `obs-service-node-modules`, Node.js 22 + npm, `cpio` and our service; a new `project.yaml` declares the UBI_9-only `ppg:devel:pgadmin` project. + +**Tech Stack:** Python 3 (stdlib only for the service; `percona_obs` package + pytest for the tool), OBS source services (`obs_scm`, `node_modules`), npm ≥ 7, Docker (Ubuntu 24.04 image), OBS project config. + +**Spec:** `docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md` + +## Global Constraints + +- Every commit: `git commit -s` (Signed-off-by). **No** `Co-Authored-By: Claude` lines. Never `git push`; never `gh pr create` — the user does that. +- After every change under `percona_obs/`: `venv/bin/black percona_obs/` then `venv/bin/pyright` — both must be clean before the task's commit. The service script is formatted with `venv/bin/black tools/obs-services/npm_lockfile` (explicit path; it has no `.py` extension so pyright does not see it — that is intended, it is exercised by tests). +- Tests: `venv/bin/pytest tests/ -q` must stay green after every task. +- OBS: only the `dev` profile (`-P dev`, http://192.168.1.103:3000) may be written to. `-P isv` is production: **never** without `--dry-run`. +- Service naming/contract: binaries live at `/usr/lib/obs/service/`, receive `-- ` pairs plus `--outdir DIR`, write outputs only into `--outdir`, exit non-zero on failure (that is what `services._run_one` relies on). +- The three npm artifacts are exactly `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc` (openSUSE's `node_modules` params `cpio=node_modules.obscpio`, `output=node_modules.spec.inc`, `source-offset=10000`). +- `project.yaml` for `ppg:devel:pgadmin` declares **only** the `UBI_9` repository; its UBI_9 path list mirrors `root/ppg/staging/18/project.yaml`'s UBI_9 entry plus `ExpandFlags: module:nodejs:22`. +- No `pgadmin`/`node_modules` string may enter `services.py` control flow except in the drift-tolerant artifact table. + +**User decisions (already made):** +- Python deps: full `python3.12-*` stack ported from openSUSE:Factory (not a bundled venv) — SP3, out of scope here. +- New OBS project `ppg:devel:pgadmin` (`root/ppg/devel/pgadmin/`) holds all pgAdmin packages; UBI-9 only. +- Package name `percona-pgadmin4` (`Provides: pgadmin4`) — SP4, out of scope here. +- `package-lock.json` is generated by percona-obs at sync time (not committed) via the `npm_lockfile` service. +- `local-npm-registry` lives in `root/common/deps/build/` — SP2, out of scope here. +- Node.js major 22 in the image and `module:nodejs:22` on UBI_9. + +--- + +## File structure + +| Path | Responsibility | +|---|---| +| `tools/obs-services/npm_lockfile` (new, executable, no extension) | The service: resolve archive → unpack → `npm install --package-lock-only` → validate → copy `package-lock.json` to `--outdir`. Exposes `main(argv) -> int`. | +| `tools/obs-services/npm_lockfile.service` (new) | OBS service descriptor (XML) listing the parameters. | +| `tools/obs-services/README.md` (new) | How to install the service locally and what it does. | +| `tests/test_npm_lockfile.py` (new) | Unit tests for the service with a fake `npm` on `PATH`; no network. | +| `percona_obs/services.py` (modify) | Keep manual-service `.obscpio` outputs; `drift_tolerant_patterns()` replaces `_has_cargo_vendor_service()`. | +| `percona_obs/cmd_sync.py` (modify) | `_content_mismatches()` takes the pattern list; call site updated; `_VENDOR_TAR_RE` removed. | +| `tests/test_run_local_services.py` (new) | `_run_local_services` with fake service binaries: manual `.obscpio` survives (miss + cache hit). | +| `tests/test_vendor_content_check.py` (modify) | Rewritten against `drift_tolerant_patterns` / new `_content_mismatches` signature. | +| `.github/docker/obs-tools/Dockerfile` (modify) | `cpio`, `obs-service-node-modules`, Node.js 22, `COPY` of the service. | +| `.github/workflows/build-obs-image.yml` (modify) | Trigger on `tools/obs-services/**`. | +| `root/ppg/devel/pgadmin/project.yaml` (new) | The `ppg:devel:pgadmin` project, UBI_9 only. | +| `root/README.md` (modify) | Document `devel/pgadmin/` as a version-independent devel project. | +| `docs/PERCONA_OBS_TOOL.md`, `.github/copilot-instructions.md`, `CLAUDE.md` (modify) | Prerequisites table, manual-`.obscpio` rule, drift-tolerant table, key-files entries. | + +--- + +### Task 1: `npm_lockfile` service with tests + +**Goal:** A stdlib-only OBS-style service that turns an upstream source archive into a `package-lock.json` in `--outdir`, with unit tests that use a fake `npm`. + +**Files:** +- Create: `tools/obs-services/npm_lockfile` +- Create: `tools/obs-services/npm_lockfile.service` +- Create: `tools/obs-services/README.md` +- Test: `tests/test_npm_lockfile.py` + +**Acceptance Criteria:** +- [ ] `--archive` glob matching 0 or >1 files exits 1 with a one-line `npm_lockfile: …` message on stderr. +- [ ] `.obscpio` (via `cpio`) and `.tar.gz` archives unpack; an archive with ≠1 top-level directory exits 1. +- [ ] Missing `package.json` in `/` exits 1. +- [ ] `npm` is invoked as `npm install --package-lock-only --legacy-peer-deps --ignore-scripts` (default flags) with cwd `/` and env `npm_config_cache=`, `npm_config_update_notifier=false`, `npm_config_fund=false`, `npm_config_audit=false`; `--npm-flags` replaces the default flag set. +- [ ] The produced lockfile is copied to `/package-lock.json`; `lockfileVersion` < 2 exits 1; npm exit ≠ 0 exits 1; `npm` missing from `PATH` exits 1. +- [ ] All temp dirs removed on success and failure. + +**Verify:** `venv/bin/pytest tests/test_npm_lockfile.py -v` → all tests PASS; `venv/bin/black --check tools/obs-services/npm_lockfile` → unchanged. + +**Steps:** + +- [ ] **Step 1: Write the failing tests** + +Create `tests/test_npm_lockfile.py`: + +```python +"""Unit tests for tools/obs-services/npm_lockfile. + +The service generates package-lock.json from an upstream source archive so +the OBS node_modules service can vendor npm dependencies. npm itself is +replaced by a fake executable put first on PATH; it records its argv, cwd and +npm_config_* environment, writes a lockfile, and exits with a configurable +status. cpio is used for real when available (skipped otherwise). +""" + +import importlib.machinery +import importlib.util +import json +import os +import shutil +import stat +import subprocess +import tarfile +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[1] +SERVICE_PATH = REPO_ROOT / "tools" / "obs-services" / "npm_lockfile" + + +def _load_service(): + loader = importlib.machinery.SourceFileLoader("npm_lockfile", str(SERVICE_PATH)) + spec = importlib.util.spec_from_loader("npm_lockfile", loader) + assert spec is not None + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + +svc = _load_service() + +FAKE_NPM = """#!/bin/sh +printf '%s\\n' "$@" > "$NPM_FAKE_LOG" +pwd >> "$NPM_FAKE_LOG" +env | grep '^npm_config_' | sort >> "$NPM_FAKE_LOG" +printf '{"name":"web","lockfileVersion":%s,"packages":{}}' "${NPM_FAKE_LOCKVER:-3}" > package-lock.json +exit "${NPM_FAKE_EXIT:-0}" +""" + + +@pytest.fixture +def fake_npm(tmp_path, monkeypatch): + bindir = tmp_path / "fakebin" + bindir.mkdir() + npm = bindir / "npm" + npm.write_text(FAKE_NPM) + npm.chmod(npm.stat().st_mode | stat.S_IXUSR) + log = tmp_path / "npm.log" + monkeypatch.setenv("PATH", f"{bindir}{os.pathsep}{os.environ['PATH']}") + monkeypatch.setenv("NPM_FAKE_LOG", str(log)) + monkeypatch.delenv("NPM_FAKE_EXIT", raising=False) + monkeypatch.delenv("NPM_FAKE_LOCKVER", raising=False) + return log + + +def _source_tree(root, top="pgadmin4-9.9", subdir="web", with_package_json=True): + pkg = root / top / subdir + pkg.mkdir(parents=True) + if with_package_json: + (pkg / "package.json").write_text('{"name": "web", "version": "1.0.0"}') + (root / top / "README.md").write_text("upstream\n") + return root / top + + +def _tar_gz(src_root, top, dest): + with tarfile.open(dest, "w:gz") as tar: + tar.add(src_root / top, arcname=top) + return dest + + +def _obscpio(src_root, top, dest): + if shutil.which("cpio") is None: + pytest.skip("cpio not installed") + files = subprocess.run( + ["find", top, "-depth", "-print"], + cwd=src_root, + capture_output=True, + text=True, + check=True, + ).stdout + with open(dest, "wb") as fh: + subprocess.run( + ["cpio", "-o", "-H", "newc", "--quiet"], + cwd=src_root, + input=files.encode(), + stdout=fh, + check=True, + ) + return dest + + +def _run(workdir, outdir, *extra): + argv = ["--archive", "*.tar.gz", "--subdir", "web", "--outdir", str(outdir)] + argv += list(extra) + cwd = os.getcwd() + os.chdir(workdir) + try: + return svc.main(argv) + finally: + os.chdir(cwd) + + +def test_tar_gz_archive_produces_lockfile_in_outdir(tmp_path, fake_npm): + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + + assert _run(work, outdir) == 0 + + lock = json.loads((outdir / "package-lock.json").read_text()) + assert lock["lockfileVersion"] == 3 + assert sorted(p.name for p in outdir.iterdir()) == ["package-lock.json"] + + +def test_npm_invocation_argv_cwd_and_env(tmp_path, fake_npm): + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + + _run(work, outdir) + + lines = fake_npm.read_text().splitlines() + assert lines[:4] == [ + "install", + "--package-lock-only", + "--legacy-peer-deps", + "--ignore-scripts", + ] + cwd_line = lines[4] + assert cwd_line.endswith(os.path.join("pgadmin4-9.9", "web")) + env_lines = lines[5:] + assert "npm_config_audit=false" in env_lines + assert "npm_config_fund=false" in env_lines + assert "npm_config_update_notifier=false" in env_lines + cache_line = next(l for l in env_lines if l.startswith("npm_config_cache=")) + cache_dir = cache_line.split("=", 1)[1] + assert cache_dir != str(Path.home() / ".npm") + assert not Path(cache_dir).exists(), "temporary npm cache must be removed" + + +def test_npm_flags_param_replaces_defaults(tmp_path, fake_npm): + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + + _run(work, outdir, "--npm-flags", "--ignore-scripts --foo") + + lines = fake_npm.read_text().splitlines() + assert lines[:4] == ["install", "--package-lock-only", "--ignore-scripts", "--foo"] + + +def test_obscpio_archive_is_unpacked_with_cpio(tmp_path, fake_npm): + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _obscpio(src, "pgadmin4-9.9", work / "percona-pgadmin4.obscpio") + outdir = tmp_path / "out" + outdir.mkdir() + + cwd = os.getcwd() + os.chdir(work) + try: + rc = svc.main( + ["--archive", "*.obscpio", "--subdir", "web", "--outdir", str(outdir)] + ) + finally: + os.chdir(cwd) + + assert rc == 0 + assert (outdir / "package-lock.json").is_file() + + +def test_archive_glob_zero_matches_fails(tmp_path, fake_npm, capsys): + work = tmp_path / "work" + work.mkdir() + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "no file matches" in capsys.readouterr().err + + +def test_archive_glob_multiple_matches_fails(tmp_path, fake_npm, capsys): + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "a-9.9.tar.gz") + _tar_gz(src, "pgadmin4-9.9", work / "b-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "matches 2 files" in capsys.readouterr().err + + +def test_archive_without_single_top_dir_fails(tmp_path, fake_npm, capsys): + src = tmp_path / "src" + _source_tree(src, top="one") + _source_tree(src, top="two") + work = tmp_path / "work" + work.mkdir() + with tarfile.open(work / "flat-1.0.tar.gz", "w:gz") as tar: + tar.add(src / "one", arcname="one") + tar.add(src / "two", arcname="two") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "exactly one top-level directory" in capsys.readouterr().err + + +def test_missing_package_json_fails(tmp_path, fake_npm, capsys): + src = tmp_path / "src" + _source_tree(src, with_package_json=False) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "package.json" in capsys.readouterr().err + + +def test_npm_failure_propagates(tmp_path, fake_npm, monkeypatch, capsys): + monkeypatch.setenv("NPM_FAKE_EXIT", "7") + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "npm exited with 7" in capsys.readouterr().err + assert not (outdir / "package-lock.json").exists() + + +def test_old_lockfile_version_rejected(tmp_path, fake_npm, monkeypatch, capsys): + monkeypatch.setenv("NPM_FAKE_LOCKVER", "1") + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "lockfileVersion" in capsys.readouterr().err + + +def test_npm_missing_from_path_fails(tmp_path, monkeypatch, capsys): + monkeypatch.setenv("PATH", str(tmp_path / "empty-bin")) + (tmp_path / "empty-bin").mkdir() + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + with pytest.raises(SystemExit) as exc: + _run(work, outdir) + assert exc.value.code == 1 + assert "npm not found" in capsys.readouterr().err + + +def test_temp_dirs_are_removed(tmp_path, fake_npm, monkeypatch): + scratch = tmp_path / "tmproot" + scratch.mkdir() + monkeypatch.setenv("TMPDIR", str(scratch)) + import tempfile + + tempfile.tempdir = None # make tempfile re-read TMPDIR + src = tmp_path / "src" + _source_tree(src) + work = tmp_path / "work" + work.mkdir() + _tar_gz(src, "pgadmin4-9.9", work / "percona-pgadmin4-9.9.tar.gz") + outdir = tmp_path / "out" + outdir.mkdir() + try: + assert _run(work, outdir) == 0 + assert list(scratch.iterdir()) == [] + finally: + tempfile.tempdir = None +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `venv/bin/pytest tests/test_npm_lockfile.py -q` +Expected: collection error — `FileNotFoundError` for `tools/obs-services/npm_lockfile` (the module does not exist yet). + +- [ ] **Step 3: Write the service** + +Create `tools/obs-services/npm_lockfile` (mode 755): + +```python +#!/usr/bin/env python3 +"""OBS source service: generate ``package-lock.json`` from an upstream archive. + +Upstream projects that use Yarn (e.g. pgAdmin 4) ship no npm lockfile, but the +OBS ``node_modules`` service needs one (lockfileVersion >= 2) to vendor the +frontend dependencies for offline builds. This service unpacks the source +archive produced by ``obs_scm``/``tar_scm``, runs +``npm install --package-lock-only`` in the directory holding ``package.json`` +and writes the resulting ``package-lock.json`` to ``--outdir``. + +Declared ``mode="manual"`` ahead of ``node_modules`` in ``_service``:: + + + *percona-pgadmin4*.obscpio + web + + +percona-obs runs manual services in XML order into one shared work directory, +so ``node_modules`` (which globs ``*package-lock.json`` in its cwd) picks the +file up in the next step. Nothing here is pgAdmin-specific. + +Only the Python standard library is used; ``npm`` and ``cpio`` are external. +""" + +import argparse +import glob +import json +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile +from pathlib import Path + +DEFAULT_NPM_FLAGS = "--legacy-peer-deps --ignore-scripts" +CPIO_SUFFIX = ".obscpio" +LOCKFILE = "package-lock.json" +MIN_LOCKFILE_VERSION = 2 + + +def die(message: str) -> None: + """Print a one-line cause on stderr and exit 1 (the OBS service contract).""" + print(f"npm_lockfile: {message}", file=sys.stderr) + sys.exit(1) + + +def resolve_archive(pattern: str) -> Path: + """Return the single file matching *pattern* (relative to cwd).""" + matches = sorted(p for p in glob.glob(pattern) if os.path.isfile(p)) + if not matches: + die(f"no file matches --archive {pattern!r} in {os.getcwd()}") + if len(matches) > 1: + die(f"--archive {pattern!r} matches {len(matches)} files: {matches}") + return Path(matches[0]) + + +def unpack(archive: Path, dest: Path) -> Path: + """Unpack *archive* into *dest*; return its single top-level directory.""" + if archive.name.endswith(CPIO_SUFFIX): + with archive.open("rb") as fh: + result = subprocess.run( + ["cpio", "--extract", "--make-directories", "--unconditional", "--quiet"], + stdin=fh, + cwd=dest, + stderr=subprocess.PIPE, + text=True, + ) + if result.returncode != 0: + die(f"cpio extract of {archive.name} failed: {result.stderr.strip()}") + else: + try: + with tarfile.open(archive) as tar: + # The 'data' filter (PEP 706) refuses absolute paths and + # traversal; available on every Python we run on (>= 3.11.4). + kwargs = {"filter": "data"} if hasattr(tarfile, "data_filter") else {} + tar.extractall(dest, **kwargs) + except (tarfile.TarError, OSError) as exc: + die(f"cannot unpack {archive.name}: {exc}") + + entries = list(dest.iterdir()) + if len(entries) != 1 or not entries[0].is_dir(): + names = sorted(e.name for e in entries) + die(f"{archive.name}: expected exactly one top-level directory, found {names}") + return entries[0] + + +def run_npm(pkg_dir: Path, npm_flags: list[str], npm_cache: Path) -> None: + """Run ``npm install --package-lock-only`` in *pkg_dir*. + + A private ``npm_config_cache`` keeps the maintainer's ``~/.npm`` out of the + resolution; notifier/fund/audit are silenced so stderr only carries real + diagnostics. npm's stderr is passed through for diagnosability. + """ + env = dict(os.environ) + env.update( + npm_config_cache=str(npm_cache), + npm_config_update_notifier="false", + npm_config_fund="false", + npm_config_audit="false", + ) + cmd = ["npm", "install", "--package-lock-only", *npm_flags] + try: + result = subprocess.run( + cmd, cwd=pkg_dir, env=env, stdout=subprocess.PIPE, text=True + ) + except FileNotFoundError: + die("npm not found in PATH (install nodejs/npm)") + if result.returncode != 0: + die(f"npm exited with {result.returncode} (command: {' '.join(cmd)})") + + +def validate_lockfile(lockfile: Path) -> None: + """Require an npm lockfile with lockfileVersion >= 2 (node_modules' minimum).""" + if not lockfile.is_file(): + die(f"npm did not produce {lockfile.name}") + try: + version = json.loads(lockfile.read_text("utf-8")).get("lockfileVersion") + except (ValueError, OSError) as exc: + die(f"{lockfile.name} is not valid JSON: {exc}") + if not isinstance(version, int) or version < MIN_LOCKFILE_VERSION: + die( + f"{lockfile.name} has lockfileVersion={version!r}; " + f"node_modules needs >= {MIN_LOCKFILE_VERSION} (use npm >= 7)" + ) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="npm_lockfile", + description="Generate package-lock.json from an upstream source archive.", + ) + parser.add_argument( + "--archive", + required=True, + help="glob matching the source archive (.obscpio, .tar, .tar.gz/.tgz, " + ".tar.bz2, .tar.xz); exactly one file must match", + ) + parser.add_argument( + "--subdir", + default=".", + help="directory inside the unpacked source that holds package.json", + ) + parser.add_argument( + "--npm-flags", + default=DEFAULT_NPM_FLAGS, + help="flags appended to 'npm install --package-lock-only' " + f"(default: {DEFAULT_NPM_FLAGS!r}; a given value replaces the default)", + ) + parser.add_argument("--outdir", required=True, help="output directory (OBS)") + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + outdir = Path(args.outdir) + if not outdir.is_dir(): + die(f"--outdir {args.outdir!r} is not a directory") + + archive = resolve_archive(args.archive) + workdir = Path(tempfile.mkdtemp(prefix="npm_lockfile-src-")) + npm_cache = Path(tempfile.mkdtemp(prefix="npm_lockfile-cache-")) + try: + top = unpack(archive, workdir) + pkg_dir = top / args.subdir + if not (pkg_dir / "package.json").is_file(): + die(f"no package.json in {top.name}/{args.subdir}") + run_npm(pkg_dir, args.npm_flags.split(), npm_cache) + lockfile = pkg_dir / LOCKFILE + validate_lockfile(lockfile) + shutil.copyfile(lockfile, outdir / LOCKFILE) + finally: + shutil.rmtree(workdir, ignore_errors=True) + shutil.rmtree(npm_cache, ignore_errors=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) +``` + +Create `tools/obs-services/npm_lockfile.service`: + +```xml + + Generate package-lock.json from an upstream source archive + + Unpacks the obs_scm/tar_scm source archive, runs + "npm install --package-lock-only" in the directory holding package.json + and writes package-lock.json to the output directory, so that the + node_modules service can vendor npm dependencies for projects that ship + no npm lockfile (e.g. Yarn-based upstreams). Run with mode="manual" + ahead of node_modules. + + + Glob matching the source archive (.obscpio, .tar, .tar.gz/.tgz, .tar.bz2, .tar.xz). Exactly one file must match. + + + + Directory inside the unpacked source that holds package.json (default: "."). + + + Flags appended to "npm install --package-lock-only". Default: "--legacy-peer-deps --ignore-scripts". A given value replaces the default set. + + +``` + +Create `tools/obs-services/README.md`: + +```markdown +# Repo-owned OBS source services + +Services here follow the OBS source-service contract (binary at +`/usr/lib/obs/service/`, `-- ` pairs, `--outdir DIR`, +non-zero exit on failure) and are run **locally** by `percona-obs sync` for +services declared `mode="manual"` in a package's `obs/_service`. They never run +on the OBS server. + +| Service | Purpose | +|---|---| +| `npm_lockfile` | Generate `package-lock.json` from an upstream source archive so the OBS `node_modules` service can vendor npm dependencies (upstreams that ship only `yarn.lock`, e.g. pgAdmin 4). Needs `npm` (Node.js ≥ 18) and `cpio` on the machine. | + +## Installing locally + +```sh +sudo install -m 755 tools/obs-services/npm_lockfile /usr/lib/obs/service/npm_lockfile +sudo install -m 644 tools/obs-services/npm_lockfile.service /usr/lib/obs/service/npm_lockfile.service +``` + +The obs-tools CI image (`.github/docker/obs-tools/Dockerfile`) installs them the +same way; changes under `tools/obs-services/` trigger an image rebuild. + +## Testing + +`venv/bin/pytest tests/test_npm_lockfile.py` — uses a fake `npm`, no network. +Format with `venv/bin/black tools/obs-services/npm_lockfile`. +``` + +Then: `chmod 755 tools/obs-services/npm_lockfile`. + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `venv/bin/pytest tests/test_npm_lockfile.py -v` +Expected: all 12 tests PASS (the `.obscpio` test is SKIPPED only where `cpio` is absent). + +- [ ] **Step 5: Format and commit** + +```bash +venv/bin/black tools/obs-services/npm_lockfile tests/test_npm_lockfile.py +venv/bin/pytest tests/test_npm_lockfile.py -q +git add tools/obs-services/ tests/test_npm_lockfile.py +git commit -s -m "tools: add npm_lockfile OBS service (generate package-lock.json for node_modules)" +``` + +--- + +### Task 2: Manual-service `.obscpio` artifacts survive `_run_local_services` + +**Goal:** `.obscpio` files produced by `mode="manual"` services (e.g. `node_modules.obscpio`) are neither extracted nor deleted, while `obs_scm` `.obscpio` archives are still extracted and dropped — on both cache miss and level-2 cache hit. + +**Files:** +- Modify: `percona_obs/services.py:981-1000` (extraction loop) and `percona_obs/services.py:1060-1064` (cleanup loop) +- Test: `tests/test_run_local_services.py` + +**Acceptance Criteria:** +- [ ] After `_run_local_services`, a manual-service output `bar.obscpio` exists in the returned workdir with unchanged bytes. +- [ ] A Phase-1 `obs_scm` output `foo.obscpio` is extracted (its `foo-1.0/…` tree is consumed) and is absent from the returned workdir; no directories remain in the workdir. +- [ ] On a level-2 cache hit restoring `bar.obscpio`, the manual service binary is **not** executed and `bar.obscpio` is still present in the returned workdir. +- [ ] `venv/bin/black percona_obs/` and `venv/bin/pyright` clean. + +**Verify:** `venv/bin/pytest tests/test_run_local_services.py -v` → PASS; `venv/bin/pytest tests/ -q` → all PASS. + +**Steps:** + +- [ ] **Step 1: Write the failing tests** + +Create `tests/test_run_local_services.py`: + +```python +"""Unit tests for services._run_local_services with fake service binaries. + +Reproduces the pgAdmin node_modules requirement: a mode="manual" service may +legitimately emit an .obscpio archive (node_modules.obscpio) that OBS unpacks +itself at build time. _run_local_services used to extract and delete every +*.obscpio in the workdir, which would have exploded that archive into ~1400 +loose tarballs and dropped it from the upload. Only Phase-1 (obs_scm) +archives may be extracted. +""" + +import shutil +import stat +import subprocess +from pathlib import Path + +import pytest + +import percona_obs.services as services +from percona_obs.services import _run_local_services + +SERVICE_XML = """ + + https://example.invalid/foo.git + git + v1.0 + foo + + + bar.obscpio + + +""" + +# Fake obs_scm: writes foo.obsinfo (with a commit) and a real cpio archive +# containing foo-1.0/hello.txt into --outdir. +FAKE_OBS_SCM = """#!/bin/sh +set -e +outdir="" +while [ $# -gt 0 ]; do + if [ "$1" = "--outdir" ]; then outdir="$2"; shift; fi + shift +done +printf 'name: foo\\nversion: 1.0\\nmtime: 0\\ncommit: %s\\n' "$FAKE_COMMIT" > "$outdir/foo.obsinfo" +tmp=$(mktemp -d) +mkdir -p "$tmp/foo-1.0" +echo hello > "$tmp/foo-1.0/hello.txt" +(cd "$tmp" && find foo-1.0 -depth -print | cpio -o -H newc --quiet) > "$outdir/foo.obscpio" +rm -rf "$tmp" +""" + +# Fake manual service: writes bar.obscpio (arbitrary bytes are fine — the +# tool must not open it) and a companion text file into --outdir. +FAKE_MANUAL = """#!/bin/sh +set -e +outdir="" +while [ $# -gt 0 ]; do + if [ "$1" = "--outdir" ]; then outdir="$2"; shift; fi + shift +done +printf 'MANUAL-CPIO-BYTES' > "$outdir/bar.obscpio" +printf 'Source10000: x\\n' > "$outdir/bar.spec.inc" +""" + +FAIL_IF_RUN = """#!/bin/sh +echo "manual service must not run on a cache hit" >&2 +exit 99 +""" + + +def _install(bindir: Path, name: str, body: str) -> None: + path = bindir / name + path.write_text(body) + path.chmod(path.stat().st_mode | stat.S_IXUSR) + + +@pytest.fixture +def fake_services(tmp_path, monkeypatch): + if shutil.which("cpio") is None: + pytest.skip("cpio not installed") + bindir = tmp_path / "obs-service" + bindir.mkdir() + _install(bindir, "obs_scm", FAKE_OBS_SCM) + _install(bindir, "fake_manual", FAKE_MANUAL) + monkeypatch.setattr(services, "_OBS_SERVICE_DIR", bindir) + monkeypatch.setenv("FAKE_COMMIT", "0123456789abcdef0123456789abcdef01234567") + # Point every cache tree at tmp so nothing touches the repo's .cache/. + cache_dir = tmp_path / ".cache" + for attr, sub in [ + ("_OBS_SCM_CACHE_DIR", "obs_scm"), + ("_SVC_CACHE_DIR", "services"), + ("_DOWNLOAD_URL_CACHE_DIR", "download_url"), + ("_CARGO_VENDOR_CACHE_DIR", "cargo_vendor"), + ]: + monkeypatch.setattr(services, attr, cache_dir / sub) + # No network: the obs_scm cache key lookup must fall through to _run_one. + monkeypatch.setattr(services, "_git_head_sha", lambda url, rev: None) + pkg = tmp_path / "pkg" + obs_dir = pkg / "obs" + obs_dir.mkdir(parents=True) + (obs_dir / "_service").write_text(SERVICE_XML) + return bindir, obs_dir, cache_dir + + +def test_manual_obscpio_kept_and_obs_scm_obscpio_extracted(fake_services): + _, obs_dir, _ = fake_services + workdir = _run_local_services(obs_dir, cache=False) + try: + names = sorted(p.name for p in workdir.iterdir()) + assert "bar.obscpio" in names + assert (workdir / "bar.obscpio").read_bytes() == b"MANUAL-CPIO-BYTES" + assert "bar.spec.inc" in names + assert "foo.obscpio" not in names + assert "foo.obsinfo" in names + assert not any(p.is_dir() for p in workdir.iterdir()) + finally: + shutil.rmtree(workdir, ignore_errors=True) + + +def test_manual_obscpio_kept_on_cache_hit(fake_services): + bindir, obs_dir, cache_dir = fake_services + commit = "0123456789abcdef0123456789abcdef01234567" + entry = cache_dir / "services" / commit + entry.mkdir(parents=True) + (entry / "bar.obscpio").write_bytes(b"CACHED-CPIO-BYTES") + (entry / "bar.spec.inc").write_text("Source10000: cached\n") + _install(bindir, "fake_manual", FAIL_IF_RUN) + + workdir = _run_local_services(obs_dir, cache=True) + try: + assert (workdir / "bar.obscpio").read_bytes() == b"CACHED-CPIO-BYTES" + assert (workdir / "bar.spec.inc").read_text() == "Source10000: cached\n" + assert not (workdir / "foo.obscpio").exists() + finally: + shutil.rmtree(workdir, ignore_errors=True) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `venv/bin/pytest tests/test_run_local_services.py -v` +Expected: both FAIL — `assert 'bar.obscpio' in names` (the file was extracted/deleted) and the cache-hit test either the same assertion or `cpio extract of bar.obscpio failed` (arbitrary bytes are not a cpio archive). + +- [ ] **Step 3: Guard the extraction and cleanup loops** + +In `percona_obs/services.py`, change the extraction loop (currently line 985): + +```python + for obscpio in sorted(workdir.glob("*.obscpio")): + if obscpio.name in manual_artifacts: + # Produced by a mode="manual" service (e.g. node_modules.obscpio). + # It is a source artifact in its own right — OBS unpacks it into + # SOURCES at build time — so it must reach the upload set intact. + logger.debug(f"keeping manual artifact {obscpio.name}") + continue + logger.debug(f"extracting {obscpio.name}") +``` + +and the cleanup loop (currently lines 1060-1064): + +```python + for child in list(workdir.iterdir()): + if child.is_dir(): + shutil.rmtree(child, ignore_errors=True) + elif child.suffix == ".obscpio" and child.name not in manual_artifacts: + child.unlink() +``` + +Update the function docstring's "Extraction" line to: + +``` + Extraction — ``.obscpio`` archives produced by Phase 1 are extracted via + ``cpio``; ``.obscpio`` files produced by manual services + (e.g. node_modules.obscpio) are kept for upload. +``` + +- [ ] **Step 4: Run tests, format, type-check** + +```bash +venv/bin/pytest tests/test_run_local_services.py -v # expected: 2 PASS +venv/bin/black percona_obs/ +venv/bin/pyright # expected: 0 errors +venv/bin/pytest tests/ -q # expected: all PASS +``` + +- [ ] **Step 5: Commit** + +```bash +git add percona_obs/services.py tests/test_run_local_services.py +git commit -s -m "services: keep .obscpio artifacts produced by manual services" +``` + +--- + +### Task 3: Drift-tolerant artifact patterns replace `ignore_vendor` + +**Goal:** The branch content check tolerates byte drift for the outputs of registry-driven services (`cargo_vendor` → `vendor.tar.*`; `node_modules` → its cpio, spec.inc and `package-lock.json`) through one table-driven helper, preserving today's `vendor.tar.*` behaviour exactly. + +**Files:** +- Modify: `percona_obs/services.py:79-85` (replace `_has_cargo_vendor_service`) +- Modify: `percona_obs/cmd_sync.py:93` (import), `:124` (drop `_VENDOR_TAR_RE`), `:442-467` (`_content_mismatches`), `:553` (call site) +- Test: `tests/test_vendor_content_check.py` (rewrite) + +**Acceptance Criteria:** +- [ ] `drift_tolerant_patterns(service_file)` returns `[]` for a missing/unparsable file or a `_service` with neither service; `vendor.tar.*` patterns for `cargo_vendor`; for `node_modules`, patterns for the `cpio`/`output`/`input` param values (defaults `node_modules.obscpio`, `node_modules.spec.inc`, `package-lock.json`). +- [ ] `_content_mismatches(local, obs, tolerant)`: a tolerated file present on both sides with different md5 is not reported; missing on either side is reported; any non-tolerated difference is reported; empty `tolerant` → plain comparison. +- [ ] `_has_cargo_vendor_service` and `_VENDOR_TAR_RE` no longer exist; `grep -rn '_has_cargo_vendor_service\|_VENDOR_TAR_RE\|ignore_vendor' percona_obs tests` → no matches. +- [ ] `venv/bin/black percona_obs/` and `venv/bin/pyright` clean. + +**Verify:** `venv/bin/pytest tests/test_vendor_content_check.py -v` → PASS; `venv/bin/pytest tests/ -q` → all PASS. + +**Steps:** + +- [ ] **Step 1: Rewrite the tests (failing)** + +Replace the content of `tests/test_vendor_content_check.py` with: + +```python +"""Unit tests for drift-tolerant content comparison (services + cmd_sync). + +Some service outputs are functions of an external registry's state at +generation time rather than of any input under git: + +* cargo_vendor's vendor.tar.* — percona/obs-packaging PR #5: pgvectorscale + ships no Cargo.lock, so the crate graph is re-resolved on every run and the + bytes drift between machines and dates. +* node_modules' node_modules.obscpio / node_modules.spec.inc and the + package-lock.json generated by npm_lockfile — npm re-resolves ranges. + +A branch-from content check comparing raw md5s would report those files as +"differs" and promote the package on unrelated PRs. Such files are compared +by presence only; absence on either side is still a mismatch. +""" + +import re + +from percona_obs.cmd_sync import _content_mismatches +from percona_obs.services import drift_tolerant_patterns + +SPEC = "percona-pgvectorscale.spec" +SRC = "percona-pgvectorscale_16-0.9.0.tar.gz" +VENDOR = "vendor.tar.gz" +LOCK = "package-lock.json" +CPIO = "node_modules.obscpio" +INC = "node_modules.spec.inc" + + +def _service_file(tmp_path, body): + path = tmp_path / "obs" / "_service" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(f"{body}") + return path + + +def _matches(patterns, name): + return any(p.search(name) for p in patterns) + + +def _cargo(tmp_path): + return drift_tolerant_patterns( + _service_file( + tmp_path, + '' + 'gz', + ) + ) + + +def _npm(tmp_path, params=""): + return drift_tolerant_patterns( + _service_file( + tmp_path, + f'{params}', + ) + ) + + +# ── drift_tolerant_patterns ────────────────────────────────────────────── + + +def test_no_patterns_without_registry_services(tmp_path): + path = _service_file(tmp_path, '') + assert drift_tolerant_patterns(path) == [] + + +def test_no_patterns_for_missing_or_broken_service_file(tmp_path): + assert drift_tolerant_patterns(tmp_path / "missing" / "_service") == [] + broken = tmp_path / "broken" / "_service" + broken.parent.mkdir() + broken.write_text("deps.obscpio' + 'deps.inc' + 'web-lock.json', + ) + for name in ("deps.obscpio", "deps.inc", "web-lock.json"): + assert _matches(pats, name) + assert not _matches(pats, CPIO) + assert not _matches(pats, INC) + + +def test_both_services_combine(tmp_path): + pats = drift_tolerant_patterns( + _service_file( + tmp_path, + '' + '', + ) + ) + assert _matches(pats, VENDOR) + assert _matches(pats, CPIO) + + +# ── _content_mismatches ────────────────────────────────────────────────── + + +def test_vendor_only_drift_ignored_for_cargo_packages(tmp_path): + local = {SPEC: "a", SRC: "b", VENDOR: "local-bytes"} + obs = {SPEC: "a", SRC: "b", VENDOR: "obs-bytes"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [] + + +def test_vendor_drift_reported_without_tolerant_patterns(): + local = {SPEC: "a", VENDOR: "local-bytes"} + obs = {SPEC: "a", VENDOR: "obs-bytes"} + assert _content_mismatches(local, obs, []) == [VENDOR] + + +def test_real_change_still_reported_alongside_vendor_drift(tmp_path): + local = {SPEC: "changed", SRC: "b", VENDOR: "local-bytes"} + obs = {SPEC: "a", SRC: "b", VENDOR: "obs-bytes"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [SPEC] + + +def test_vendor_missing_on_obs_is_a_mismatch(tmp_path): + local = {SPEC: "a", VENDOR: "local-bytes"} + obs = {SPEC: "a"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [VENDOR] + + +def test_vendor_missing_locally_is_a_mismatch(tmp_path): + local = {SPEC: "a"} + obs = {SPEC: "a", VENDOR: "obs-bytes"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [VENDOR] + + +def test_npm_artifact_drift_ignored_for_node_modules_packages(tmp_path): + local = {SPEC: "a", LOCK: "l1", CPIO: "c1", INC: "i1"} + obs = {SPEC: "a", LOCK: "l2", CPIO: "c2", INC: "i2"} + assert _content_mismatches(local, obs, _npm(tmp_path)) == [] + + +def test_npm_artifact_missing_on_obs_is_a_mismatch(tmp_path): + local = {SPEC: "a", LOCK: "l1", CPIO: "c1", INC: "i1"} + obs = {SPEC: "a", LOCK: "l2", INC: "i2"} + assert _content_mismatches(local, obs, _npm(tmp_path)) == [CPIO] + + +def test_npm_drift_reported_for_packages_without_node_modules(tmp_path): + local = {SPEC: "a", CPIO: "c1"} + obs = {SPEC: "a", CPIO: "c2"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [CPIO] + + +def test_identical_content_has_no_mismatches(): + local = {SPEC: "a", SRC: "b"} + obs = {SPEC: "a", SRC: "b"} + assert _content_mismatches(local, obs, []) == [] + + +def test_patterns_are_compiled_regexes(tmp_path): + assert all(isinstance(p, re.Pattern) for p in _cargo(tmp_path)) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `venv/bin/pytest tests/test_vendor_content_check.py -q` +Expected: `ImportError: cannot import name 'drift_tolerant_patterns' from 'percona_obs.services'`. + +- [ ] **Step 3: Implement `drift_tolerant_patterns` in `services.py`** + +Replace `_has_cargo_vendor_service` (lines 79-85) with: + +```python +def _cargo_vendor_patterns(svc: ET.Element) -> list[re.Pattern[str]]: + """vendor.tar. — the crate tree cargo_vendor downloads.""" + return [re.compile(r"^vendor\.tar\.[a-z0-9]+$")] + + +def _node_modules_patterns(svc: ET.Element) -> list[re.Pattern[str]]: + """The npm tarball cpio, its Source-line include, and the npm lockfile. + + Names follow the service's own params (openSUSE convention: + cpio=node_modules.obscpio, output=node_modules.spec.inc, + input=package-lock.json). The lockfile is matched by suffix because the + node_modules service itself globs ``*``. + """ + params = {p.get("name", ""): (p.text or "").strip() for p in svc.findall("param")} + cpio = params.get("cpio") or "node_modules.obscpio" + output = params.get("output") or "node_modules.spec.inc" + lockfile = params.get("input") or "package-lock.json" + return [ + re.compile("^" + re.escape(cpio) + "$"), + re.compile("^" + re.escape(output) + "$"), + re.compile(re.escape(lockfile) + "$"), + ] + + +# Services whose outputs are a function of an external registry's state at +# generation time (crates.io, registry.npmjs.org) rather than of any input +# under git. The branch content check compares those outputs by presence +# only: a byte difference must not flip a decision to "promote", but a file +# missing on either side still must. Keyed by service name. +_DRIFT_TOLERANT_SERVICES: dict[str, Callable[[ET.Element], list[re.Pattern[str]]]] = { + "cargo_vendor": _cargo_vendor_patterns, + "node_modules": _node_modules_patterns, +} + + +def drift_tolerant_patterns(service_file: Path) -> list[re.Pattern[str]]: + """Return filename regexes for drift-tolerant outputs declared in *service_file*. + + Returns an empty list when the file is missing or unparsable, or when it + declares none of the services in _DRIFT_TOLERANT_SERVICES. + """ + try: + root = ET.parse(service_file).getroot() + except (ET.ParseError, OSError): + return [] + patterns: list[re.Pattern[str]] = [] + for svc in root.findall("service"): + factory = _DRIFT_TOLERANT_SERVICES.get(svc.get("name", "")) + if factory is not None: + patterns.extend(factory(svc)) + return patterns +``` + +Add `from typing import Callable` to the imports at the top of `services.py` (keep the import block sorted: stdlib `typing` goes after `time`, before `xml.etree.ElementTree`). + +- [ ] **Step 4: Update `cmd_sync.py`** + +In the `from .services import (…)` block (line 93) replace `_has_cargo_vendor_service,` with `drift_tolerant_patterns,` (keep the block alphabetical: it goes after `_run_local_services,` and before `upstream_scm_refs,`). + +Delete lines 123-124: + +```python +# Matches the vendored-crates archive produced by the cargo_vendor service. +_VENDOR_TAR_RE = re.compile(r"^vendor\.tar\.[a-z0-9]+$") +``` + +Replace `_content_mismatches` (lines 442-467) with: + +```python +def _content_mismatches( + local_md5s: dict[str, str], + obs_md5s: dict[str, str], + tolerant: list[re.Pattern[str]], +) -> list[str]: + """Return the file names whose content differs between local and OBS. + + Files matching a *tolerant* pattern (see services.drift_tolerant_patterns) + and present on both sides are compared by presence only, never by bytes: + their content is a function of an external registry's state at generation + time — cargo_vendor re-resolving crates for a project without Cargo.lock, + npm re-resolving ranges for node_modules — so byte drift between machines + and dates must not flip a branch decision to promote. A tolerant file + missing from either side is still a mismatch. + """ + mismatches = [name for name, md5 in local_md5s.items() if obs_md5s.get(name) != md5] + mismatches += [name for name in obs_md5s if name not in local_md5s] + if tolerant: + both = set(local_md5s) & set(obs_md5s) + mismatches = [ + name + for name in mismatches + if not (name in both and any(p.search(name) for p in tolerant)) + ] + return sorted(mismatches) +``` + +Replace the call site (line 553): + +```python + mismatches = _content_mismatches( + local_md5s, + obs_md5s, + tolerant=drift_tolerant_patterns(service_file) if run_services else [], + ) +``` + +Check `re` is still imported in `cmd_sync.py` (it is used by `_SYNC_MSG_RE` etc. — keep it). + +- [ ] **Step 5: Run tests, format, type-check** + +```bash +venv/bin/pytest tests/test_vendor_content_check.py -v # expected: all PASS +venv/bin/black percona_obs/ +venv/bin/pyright # expected: 0 errors +venv/bin/pytest tests/ -q # expected: all PASS +grep -rn '_has_cargo_vendor_service\|_VENDOR_TAR_RE\|ignore_vendor' percona_obs tests # expected: no output +``` + +- [ ] **Step 6: Commit** + +```bash +git add percona_obs/services.py percona_obs/cmd_sync.py tests/test_vendor_content_check.py +git commit -s -m "sync: generalise vendor drift tolerance to node_modules artifacts" +``` + +--- + +### Task 4: obs-tools image — `node_modules` service, Node.js 22, `cpio`, `npm_lockfile` + +**Goal:** The CI image provides `/usr/lib/obs/service/node_modules`, `/usr/lib/obs/service/npm_lockfile`, `npm` (Node 22) and `cpio`, and rebuilds when the service source changes. + +**Files:** +- Modify: `.github/docker/obs-tools/Dockerfile:37-40` (base apt line), `:84-88` (openSUSE:Tools line), after `:94` (new Node.js + service blocks) +- Modify: `.github/workflows/build-obs-image.yml:18-21` (`paths:`) + +**Acceptance Criteria:** +- [ ] `docker build` of the image succeeds locally (or, if Docker is unavailable on the dev box, the `build-obs-image.yml` run after merge succeeds — record which). +- [ ] Inside the image: `test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && test -f /usr/lib/obs/service/npm_lockfile.service && command -v cpio && node --version | grep -q '^v22\.' && npm --version` exits 0. +- [ ] `build-obs-image.yml` lists `tools/obs-services/**` under `on.push.paths`. + +**Verify:** `docker build --build-arg GO_VERSION=$(sed -n 's/^- *GOLANG_VERSION: *//p' root/macros.yaml | head -1 | tr -d '"'"' ") -f .github/docker/obs-tools/Dockerfile -t obs-tools:local .` → exit 0, then `docker run --rm obs-tools:local sh -c 'test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && command -v cpio && node --version && npm --version'` → prints `v22.x.y` and an npm version. + +**Steps:** + +- [ ] **Step 1: Base tools line — add `cpio`** + +In the final-image `RUN apt-get … install` (lines 36-42) change + +```dockerfile + ca-certificates curl gnupg git jq sudo \ +``` +to +```dockerfile + ca-certificates curl gnupg git jq sudo cpio \ +``` + +and extend the comment block above it with: + +```dockerfile +# cpio: percona-obs extracts obs_scm .obscpio archives locally, and the +# npm_lockfile service unpacks them before running npm +``` + +- [ ] **Step 2: openSUSE:Tools line — add `obs-service-node-modules`** + +Change lines 85-88 to: + +```dockerfile + apt-get install -y --no-install-recommends \ + obs-service-tar-scm obs-service-recompress \ + obs-service-set-version obs-service-download-url \ + obs-service-node-modules python3-lxml \ + && rm -rf /var/lib/apt/lists/* +``` + +and add to the comment above that RUN: + +```dockerfile +# obs-service-node-modules: vendors npm dependencies (node_modules.obscpio + +# node_modules.spec.inc) from a package-lock.json; runs under /usr/bin/python3 +# and needs python3-lxml. +``` + +- [ ] **Step 3: Node.js 22 + `npm_lockfile` blocks** + +Insert after the `obs-service-go_modules` RUN (after line 94): + +```dockerfile +# Node.js 22 + npm — required by the repo-owned npm_lockfile service +# (tools/obs-services), which generates package-lock.json for packages whose +# frontend dependencies are vendored with node_modules (pgAdmin 4). Major 22 +# matches the nodejs:22 module stream those packages build against on UBI_9 +# (ExpandFlags in root/ppg/devel/pgadmin/project.yaml) — bump both together. +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ + | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \ + > /etc/apt/sources.list.d/nodesource.list && \ + apt-get update -qq && \ + apt-get install -y --no-install-recommends nodejs && \ + rm -rf /var/lib/apt/lists/* && \ + node --version && npm --version + +# npm_lockfile — repo-owned OBS-style service (tools/obs-services/README.md). +# Stdlib-only Python; its #!/usr/bin/env python3 resolves to the venv below. +COPY --chmod=755 tools/obs-services/npm_lockfile /usr/lib/obs/service/npm_lockfile +COPY --chmod=644 tools/obs-services/npm_lockfile.service /usr/lib/obs/service/npm_lockfile.service +``` + +- [ ] **Step 4: Workflow trigger** + +In `.github/workflows/build-obs-image.yml` change the `paths:` list to: + +```yaml + paths: + - '.github/docker/obs-tools/Dockerfile' + - 'requirements.txt' + - 'root/macros.yaml' + - 'tools/obs-services/**' +``` + +and extend the header comment with the line: `# tools/obs-services/ holds repo-owned OBS services baked into the image.` + +- [ ] **Step 5: Build and check the image** + +```bash +GO_VERSION=$(sed -n 's/^- *GOLANG_VERSION: *//p' root/macros.yaml | head -1 | tr -d '"'"' ') +docker build --build-arg GO_VERSION="$GO_VERSION" -f .github/docker/obs-tools/Dockerfile -t obs-tools:local . +docker run --rm obs-tools:local sh -c 'test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && test -f /usr/lib/obs/service/npm_lockfile.service && command -v cpio && node --version && npm --version && /usr/lib/obs/service/npm_lockfile --help | head -1' +``` +Expected: build exits 0; the run prints the cpio path, `v22.*`, an npm version and `usage: npm_lockfile …`. If Docker is not available locally (`podman build` is an acceptable substitute with the same arguments), note it in the commit message body and rely on the post-merge workflow run. + +- [ ] **Step 6: Commit** + +```bash +git add .github/docker/obs-tools/Dockerfile .github/workflows/build-obs-image.yml +git commit -s -m "obs-tools image: add node_modules service, Node.js 22, cpio and npm_lockfile" +``` + +--- + +### Task 5: `ppg:devel:pgadmin` project definition + +**Goal:** `root/ppg/devel/pgadmin/project.yaml` defines the UBI_9-only `ppg:devel:pgadmin` project and `root/README.md` documents the version-independent devel project. + +**Files:** +- Create: `root/ppg/devel/pgadmin/project.yaml` +- Modify: `root/README.md:172-175` (append a paragraph after the "Graduation" paragraph of the devel-tier section) + +**Acceptance Criteria:** +- [ ] `venv/bin/python -m percona_obs -P dev project verify ppg:devel:pgadmin` exits 0 (all `${VAR}` tokens defined). +- [ ] `venv/bin/python -m percona_obs -P dev sync push --project-only --dry-run ppg:devel:pgadmin` prints a `+` line for project `home:Admin:percona:ppg:devel:pgadmin` with exactly one repository, `UBI_9`, and a project config containing `ExpandFlags: module:nodejs:22`. +- [ ] `root/README.md` explains `devel/pgadmin/`. + +**Verify:** the two commands above → exit 0 with the described output. + +**Steps:** + +- [ ] **Step 1: Write `project.yaml`** + +Create `root/ppg/devel/pgadmin/project.yaml`: + +```yaml +title: pgAdmin 4 for Percona Distribution for PostgreSQL +description: | + pgAdmin 4 (percona-pgadmin4) and its Python 3.12 / Node.js build and runtime + dependency stack, built for UBI 9 only. Independent of the PostgreSQL major + version, hence a version-less devel project rather than a ppg:devel: entry. + Design: docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md +repositories: + - name: UBI_9 + paths: + # python3.12-six / -dateutil / -psutil / -click already built there + - subproject: ppg:common:deps + repository: UBI_9 + # local-npm-registry (npm offline registry used at build time) + - subproject: common:deps:build + repository: UBI_9 + - project: ${REMOTE_OBS_ORG_INTERCONNECT}Fedora:EPEL:9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RedHat:UBI-9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RockyLinux:9 + repository: devel + archs: [x86_64, aarch64] +project-config: | + %if "%_repository" == "UBI_9" + ExpandFlags: module:llvm-toolset-rhel9 + # pgAdmin's webpack build and local-npm-registry need Node.js >= 20; the + # obs-tools image pins the same major for npm_lockfile — bump both together. + ExpandFlags: module:nodejs:22 + # hdf-libs (EPEL split package) and hdf both provide libdf.so.0 and libmfhdf.so.0 + Prefer: hdf-libs + %endif +``` + +- [ ] **Step 2: Document in `root/README.md`** + +After the paragraph starting `Graduation: when a development cycle concludes …` (ends line 175), insert: + +```markdown +### `devel/pgadmin/` — a version-independent devel project + +`ppg:devel:pgadmin` is the one devel project that is not a PG-major subset: it holds +`percona-pgadmin4` and its Python 3.12 / Node.js dependency stack, which do not depend on +the PostgreSQL major version. Its `project.yaml` declares a single `UBI_9` repository +(pgAdmin is built for UBI 9 only), so no per-package `build:` flags are needed, and adds +`ExpandFlags: module:nodejs:22` for the frontend build. Packages there follow the +normal `rpm/` + `obs/_service` layout; the npm dependencies are vendored at sync time by +the `npm_lockfile` → `node_modules` service pair (see `docs/PERCONA_OBS_TOOL.md`). +``` + +- [ ] **Step 3: Verify against the dev profile** + +```bash +venv/bin/python -m percona_obs -P dev project verify ppg:devel:pgadmin +venv/bin/python -m percona_obs -P dev sync push --project-only --dry-run ppg:devel:pgadmin +``` +Expected: `project verify` → `✔`; the dry-run lists `+ project home:Admin:percona:ppg:devel:pgadmin` (and `=` for the existing ancestors `home:Admin:percona:ppg:devel`, `…:ppg`) with `(dry run)` on the final `✔` line. Inspect with `--verbose` that the generated repository meta has only `UBI_9` and the ancestor-injected paths. + +- [ ] **Step 4: Commit** + +```bash +git add root/ppg/devel/pgadmin/project.yaml root/README.md +git commit -s -m "ppg:devel:pgadmin: add UBI_9-only project for pgAdmin 4 packages" +``` + +--- + +### Task 6: Documentation of the npm vendoring flow + +**Goal:** Tool docs and the AI/project reference describe the `npm_lockfile` → `node_modules` flow, the manual-`.obscpio` rule, the drift-tolerant artifact table and the local prerequisites. + +**Files:** +- Modify: `docs/PERCONA_OBS_TOOL.md:9-25` (System packages table) and a new subsection after `### Sync a single PostgreSQL extension` (line 114-123) +- Modify: `.github/copilot-instructions.md:493-503` (Local service execution), `:531-535` (cache levels — add a sentence), `:947-956` (Key Files by Pattern) +- Modify: `CLAUDE.md` Key Files table (add `tools/obs-services/`) + +**Acceptance Criteria:** +- [ ] `docs/PERCONA_OBS_TOOL.md` System packages table lists `node_modules` and `npm_lockfile` with install sources, and `npm`/`cpio` as external tools. +- [ ] `docs/PERCONA_OBS_TOOL.md` has a subsection "Vendoring npm dependencies (`npm_lockfile` + `node_modules`)" with the `_service` snippet, the three artifacts, the drift-tolerance rule and `--no-cache` as the refresh knob. +- [ ] `.github/copilot-instructions.md` "Local service execution" bullets state that manual-service `.obscpio` outputs are kept, and a "Drift-tolerant artifacts" paragraph lists the table. +- [ ] `CLAUDE.md` Key Files table has a `tools/obs-services/` row. + +**Verify:** `grep -n 'npm_lockfile' docs/PERCONA_OBS_TOOL.md .github/copilot-instructions.md CLAUDE.md` → at least one hit per file. + +**Steps:** + +- [ ] **Step 1: `docs/PERCONA_OBS_TOOL.md` — prerequisites** + +Replace the System packages table (lines 15-19) with: + +```markdown +| Binary | Package (Debian/Ubuntu) | Package (RPM) | +|---|---|---| +| `obs_scm` | `obs-service-obs-scm` | `obs-service-obs_scm` | +| `go_modules` | `obs-service-go_modules` | `obs-service-go_modules` | +| `download_url` | `obs-service-download_url` | `obs-service-download_url` | +| `node_modules` | `obs-service-node-modules` (openSUSE:Tools repo) | `obs-service-node_modules` (openSUSE:Tools repo) | +| `npm_lockfile` | this repo — `tools/obs-services/` (see its README) | same | + +`node_modules` and `npm_lockfile` additionally need `npm` (Node.js ≥ 18; the CI image +uses 22) and `cpio` on `PATH`. +``` + +- [ ] **Step 2: `docs/PERCONA_OBS_TOOL.md` — new subsection** + +Insert after the `### Sync a single PostgreSQL extension` example (before `### Sync all packages under a subproject`, line 124): + +```markdown +### Vendoring npm dependencies (`npm_lockfile` + `node_modules`) + +Packages with a webpack/npm frontend (pgAdmin 4) vendor their npm dependencies for +offline OBS builds with two `mode="manual"` services, run locally by `sync` in this order: + +```xml + + *percona-pgadmin4*.obscpio + web + + + node_modules.obscpio + node_modules.spec.inc + 10000 + +``` + +`npm_lockfile` (repo-owned, `tools/obs-services/`) generates `package-lock.json` with +`npm install --package-lock-only`; `node_modules` (openSUSE) reads it and downloads every +tarball into `node_modules.obscpio`, writing `SourceNNNNN:` lines to `node_modules.spec.inc` +(`%include %{_sourcedir}/node_modules.spec.inc` in the spec; `BuildRequires: local-npm-registry` +serves the tarballs to `npm` at build time). All three files are uploaded to OBS and cached +under `.cache/services//` — the lockfile is a function of the upstream commit +plus registry state, so `--no-cache` is the way to deliberately refresh the vendored set. + +Two rules in `percona-obs` make this work: + +- `.obscpio` files produced by **manual** services are uploaded as-is (OBS unpacks them into + `SOURCES` at build time); only `obs_scm` archives are extracted locally. +- **Drift-tolerant artifacts**: outputs whose bytes depend on registry state at generation + time are compared by presence only in the `--branch-from` content check, so re-resolution + never promotes a package by itself (absence on either side is still a change): + + | Service | Tolerated files | + |---|---| + | `cargo_vendor` | `vendor.tar.*` | + | `node_modules` | `node_modules.obscpio`, `node_modules.spec.inc`, `*package-lock.json` (names follow the `cpio`/`output`/`input` params) | +``` + +- [ ] **Step 3: `.github/copilot-instructions.md`** + +In "Local service execution" (lines 495-503): change the first paragraph's example to `… like Go services that use `go_modules` (mode=manual) or npm frontends that use `npm_lockfile` + `node_modules` (mode=manual) to vendor dependencies.` and append a bullet 5: + +```markdown +5. `.obscpio` files produced by `mode="manual"` services (e.g. `node_modules.obscpio`) are kept and uploaded; only `obs_scm` archives are extracted locally. +``` + +After the `--no-cache` paragraph (line 535) add: + +```markdown +**Drift-tolerant artifacts** (`services.drift_tolerant_patterns`): outputs whose bytes depend on an external registry's state at generation time — `cargo_vendor` → `vendor.tar.*`; `node_modules` → `node_modules.obscpio`, `node_modules.spec.inc`, `*package-lock.json` — are compared by presence only in the `--branch-from` content check (`cmd_sync._content_mismatches`). A file missing on either side is still a mismatch. +``` + +In "Key Files by Pattern" (line 948-956) add rows: + +```markdown +| Repo-owned OBS service (npm lockfile generation) | `tools/obs-services/npm_lockfile` | +| Version-independent devel project (pgAdmin) | `root/ppg/devel/pgadmin/project.yaml` | +``` + +- [ ] **Step 4: `CLAUDE.md`** + +Add to the Key Files table, after the `percona_obs/git_utils.py` row: + +```markdown +| `tools/obs-services/` | Repo-owned OBS source services run locally by `sync` (e.g. `npm_lockfile`); baked into the obs-tools image | +``` + +- [ ] **Step 5: Commit** + +```bash +grep -n 'npm_lockfile' docs/PERCONA_OBS_TOOL.md .github/copilot-instructions.md CLAUDE.md +git add docs/PERCONA_OBS_TOOL.md .github/copilot-instructions.md CLAUDE.md +git commit -s -m "docs: describe npm vendoring flow (npm_lockfile + node_modules) and drift-tolerant artifacts" +``` + +--- + +### Task 7: End-to-end dry run against pgAdmin `REL-9_9` on the dev profile + +**Goal:** Prove the whole local chain on the real upstream — `obs_scm` → `npm_lockfile` → `node_modules` — with a scratch package under the new project, capturing the answers to the spec's open verification items (Yarn-lock hint, `tar` service behaviour, artifact sizes, and the ~220 MB upload). + +**Files:** +- Create (temporary, **not committed**): `root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service` +- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md` §10 (fill in the verification results) + +**Acceptance Criteria:** +- [ ] Local prerequisites installed: `/usr/lib/obs/service/node_modules`, `/usr/lib/obs/service/npm_lockfile`, `npm --version` ≥ 7, `cpio`. +- [ ] `sync push --dry-run -P dev ppg:devel:pgadmin percona-pgadmin4` lists `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc` (plus `percona-pgadmin4.obsinfo`/tarball outputs) as `+`, with no `error:` lines; a second run shows `service npm_lockfile … (cached)` and `service node_modules … (cached)`. +- [ ] The generated `package-lock.json` (from `.cache/services//`) has `lockfileVersion` ≥ 2 and its `packages` count is recorded; whether its resolved versions match `web/yarn.lock` for a sample of 5 packages is recorded (Yarn-lock hint verification). +- [ ] A real `sync push -P dev ppg:devel:pgadmin percona-pgadmin4` uploads all files (the OBS package file list shows `node_modules.obscpio` with the same MD5 as the local file) — records elapsed time; any timeout is fixed in `_upload_obs_files` and re-run. +- [ ] Spec §10 table updated with the outcomes; scratch package removed locally and from the dev OBS. + +**Verify:** `venv/bin/python -m percona_obs -P dev sync push --dry-run ppg:devel:pgadmin percona-pgadmin4` → `✔ … (dry run)` with the three artifacts listed as `+`; `venv/bin/osc -A http://192.168.1.103:3000 ls -l home:Admin:percona:ppg:devel:pgadmin percona-pgadmin4` (after the real push) → lists `node_modules.obscpio`. + +**Steps:** + +- [ ] **Step 1: Install local prerequisites (dev box, Fedora)** + +```bash +sudo dnf install -y obs-service-node_modules nodejs npm cpio # obs-service-node_modules comes from the openSUSE:Tools Fedora repo already used for obs-service-tar_scm +sudo install -m 755 tools/obs-services/npm_lockfile /usr/lib/obs/service/npm_lockfile +sudo install -m 644 tools/obs-services/npm_lockfile.service /usr/lib/obs/service/npm_lockfile.service +ls -l /usr/lib/obs/service/node_modules /usr/lib/obs/service/npm_lockfile && npm --version && cpio --version | head -1 +``` +Expected: both binaries listed; npm ≥ 7. + +- [ ] **Step 2: Create the scratch `_service`** + +```bash +mkdir -p root/ppg/devel/pgadmin/percona-pgadmin4/obs +cat > root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service <<'EOF' + + + https://github.com/pgadmin-org/pgadmin4.git + git + REL-9_9 + @PARENT_TAG@ + REL-(\d+)_(\d+) + \1.\2 + percona-pgadmin4 + + + percona-pgadmin4*.obscpio + web + + + node_modules.obscpio + node_modules.spec.inc + 10000 + + + + *.tar + gz + + + +EOF +``` + +- [ ] **Step 3: Dry run twice** + +```bash +time venv/bin/python -m percona_obs -P dev --verbose sync push --dry-run ppg:devel:pgadmin percona-pgadmin4 2>&1 | tee /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/b716b217-b467-4b8e-8b0a-034c834afd47/scratchpad/pgadmin-dryrun-1.log +venv/bin/python -m percona_obs -P dev sync push --dry-run ppg:devel:pgadmin percona-pgadmin4 2>&1 | tee /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/b716b217-b467-4b8e-8b0a-034c834afd47/scratchpad/pgadmin-dryrun-2.log +``` +Expected run 1: `> service obs_scm`, `> service npm_lockfile`, `> service node_modules` (this one downloads ~1 400 tarballs — several minutes), then `+` lines for `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc`, `percona-pgadmin4.obsinfo`, `percona-pgadmin4-9.9.tar.gz`; no `error:`. Run 2: `= service npm_lockfile … (cached)`, `= service node_modules … (cached)`. +If `node_modules` fails on a tarball URL (registry hiccup) rerun; if `npm_lockfile` fails with an npm resolution error, capture the stderr — that is a finding for SP4 (may need `--npm-flags`). + +- [ ] **Step 4: Inspect the artifacts** + +```bash +COMMIT=$(ls .cache/services | head -1) # or pick the entry matching `git ls-remote https://github.com/pgadmin-org/pgadmin4.git REL-9_9^{}` +ls -la .cache/services/$COMMIT/ +python3 -c "import json,sys; d=json.load(open('.cache/services/$COMMIT/package-lock.json')); print('lockfileVersion', d['lockfileVersion'], 'packages', len(d['packages']))" +grep -c '^Source' .cache/services/$COMMIT/node_modules.spec.inc +``` +Yarn-lock hint check: `git clone --depth 1 -b REL-9_9 https://github.com/pgadmin-org/pgadmin4.git /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/b716b217-b467-4b8e-8b0a-034c834afd47/scratchpad/pgadmin4-src` and compare the versions of `webpack`, `react`, `@babel/core`, `eslint`, `axios` between `web/yarn.lock` and the generated `package-lock.json` (`packages["node_modules/"].version`). Record "matches yarn.lock: yes/no" in spec §10. + +- [ ] **Step 5: Real push to the dev OBS (upload-size check)** + +```bash +time venv/bin/python -m percona_obs -P dev sync push ppg:devel:pgadmin percona-pgadmin4 +venv/bin/osc -A http://192.168.1.103:3000 ls -l home:Admin:percona:ppg:devel:pgadmin percona-pgadmin4 +md5sum .cache/services/$COMMIT/node_modules.obscpio +``` +Expected: the push completes (record elapsed seconds); the OBS listing shows `node_modules.obscpio` with a size ≈ the local file and the same MD5. If the PUT times out, raise the timeout in `percona_obs/obs_api.py::_upload_obs_files` for that call (commit as `obs_api: allow long uploads of large service artifacts`), rerun, and note it in the spec. + +- [ ] **Step 6: Record results and clean up** + +Edit spec §10 ("Risks and open verification items") turning each row's "Where verified" into the observed outcome (e.g. `npm honours yarn.lock: yes — 5/5 sampled versions match`, `tar service ignored node_modules.obscpio: yes`, `220 MB PUT: 47 s, no timeout change needed`). Then remove the scratch package: + +```bash +rm -rf root/ppg/devel/pgadmin/percona-pgadmin4 +venv/bin/osc -A http://192.168.1.103:3000 rdelete -m "scratch package from SP1 verification" home:Admin:percona:ppg:devel:pgadmin percona-pgadmin4 +git status --short # expected: only the spec modified +git add docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md +git commit -s -m "docs: record SP1 verification results for the pgAdmin npm vendoring chain" +``` + +The dev-OBS project `home:Admin:percona:ppg:devel:pgadmin` itself stays (Task 5 owns it; SP2–SP4 populate it). + +--- + +## Self-review + +**Spec coverage** — §5 architecture → Tasks 1–5; §6 service → Task 1 (`.tar.zst` dropped: stdlib `tarfile` has no zstd before 3.14 and `obs_scm` emits `.obscpio` anyway — documented in the service's `--archive` help); §7.1 → Task 2; §7.2 → Task 3; §7.3 → no code (documented in Task 6); §7.4 → Task 7 step 5; §7.5 → Task 6; §8.1 → Task 4; §8.2 → Task 5; §9.1 → Tasks 1–3; §9.2/§9.3 → Tasks 4, 5, 7; §10 → Task 7 step 6. + +**Placeholder scan** — no TBD/TODO/"similar to"/"add validation" patterns; every code step carries its full code. + +**Type consistency** — `drift_tolerant_patterns(service_file: Path) -> list[re.Pattern[str]]` (Task 3 services.py) matches the `tolerant: list[re.Pattern[str]]` parameter of `_content_mismatches` and the tests' `_matches(patterns, name)` usage; `manual_artifacts: list[str]` (existing) is what Task 2 guards on; `npm_lockfile.main(argv: list[str]) -> int` matches `svc.main([...]) == 0` in the tests. diff --git a/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md.tasks.json b/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md.tasks.json new file mode 100644 index 00000000..f4d0b5e2 --- /dev/null +++ b/docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md.tasks.json @@ -0,0 +1,65 @@ +{ + "planPath": "docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md", + "tasks": [ + { + "id": 1, + "subject": "Task 1: npm_lockfile service + tests", + "status": "completed", + "description": "**Goal:** A stdlib-only OBS-style service that turns an upstream source archive into a `package-lock.json` in `--outdir`, with unit tests that use a fake `npm`.\n\n**Files:**\n- Create: `tools/obs-services/npm_lockfile`\n- Create: `tools/obs-services/npm_lockfile.service`\n- Create: `tools/obs-services/README.md`\n- Test: `tests/test_npm_lockfile.py`\n\n**Acceptance Criteria:**\n- [ ] `--archive` glob matching 0 or >1 files exits 1 with a one-line `npm_lockfile: \u2026` message on stderr.\n- [ ] `.obscpio` (via `cpio`) and `.tar.gz` archives unpack; an archive with \u22601 top-level directory exits 1.\n- [ ] Missing `package.json` in `/` exits 1.\n- [ ] `npm` is invoked as `npm install --package-lock-only --legacy-peer-deps --ignore-scripts` (default flags) with cwd `/` and env `npm_config_cache=`, `npm_config_update_notifier=false`, `npm_config_fund=false`, `npm_config_audit=false`; `--npm-flags` replaces the default flag set.\n- [ ] The produced lockfile is copied to `/package-lock.json`; `lockfileVersion` < 2 exits 1; npm exit \u2260 0 exits 1; `npm` missing from `PATH` exits 1.\n- [ ] All temp dirs removed on success and failure.\n\n**Verify:** `venv/bin/pytest tests/test_npm_lockfile.py -v` \u2192 all tests PASS; `venv/bin/black --check tools/obs-services/npm_lockfile` \u2192 unchanged.\n\n```json:metadata\n{\"files\": [\"tools/obs-services/npm_lockfile\", \"tools/obs-services/npm_lockfile.service\", \"tools/obs-services/README.md\", \"tests/test_npm_lockfile.py\"], \"verifyCommand\": \"venv/bin/pytest tests/test_npm_lockfile.py -v && venv/bin/black --check tools/obs-services/npm_lockfile\", \"modelTier\": \"standard\", \"acceptanceCriteria\": [\"`--archive` glob matching 0 or >1 files exits 1 with a one-line `npm_lockfile: \\u2026` message on stderr.\", \"`.obscpio` (via `cpio`) and `.tar.gz` archives unpack; an archive with \\u22601 top-level directory exits 1.\", \"Missing `package.json` in `/` exits 1.\", \"`npm` is invoked as `npm install --package-lock-only --legacy-peer-deps --ignore-scripts` (default flags) with cwd `/` and env `npm_config_cache=`, `npm_config_update_notifier=false`, `npm_config_fund=false`, `npm_config_audit=false`; `--npm-flags` replaces the default flag set.\", \"The produced lockfile is copied to `/package-lock.json`; `lockfileVersion` < 2 exits 1; npm exit \\u2260 0 exits 1; `npm` missing from `PATH` exits 1.\", \"All temp dirs removed on success and failure.\"]}\n```" + }, + { + "id": 2, + "subject": "Task 2: keep manual .obscpio in _run_local_services", + "status": "completed", + "description": "**Goal:** `.obscpio` files produced by `mode=\"manual\"` services (e.g. `node_modules.obscpio`) are neither extracted nor deleted, while `obs_scm` `.obscpio` archives are still extracted and dropped \u2014 on both cache miss and level-2 cache hit.\n\n**Files:**\n- Modify: `percona_obs/services.py:981-1000` (extraction loop) and `percona_obs/services.py:1060-1064` (cleanup loop)\n- Test: `tests/test_run_local_services.py`\n\n**Acceptance Criteria:**\n- [ ] After `_run_local_services`, a manual-service output `bar.obscpio` exists in the returned workdir with unchanged bytes.\n- [ ] A Phase-1 `obs_scm` output `foo.obscpio` is extracted (its `foo-1.0/\u2026` tree is consumed) and is absent from the returned workdir; no directories remain in the workdir.\n- [ ] On a level-2 cache hit restoring `bar.obscpio`, the manual service binary is **not** executed and `bar.obscpio` is still present in the returned workdir.\n- [ ] `venv/bin/black percona_obs/` and `venv/bin/pyright` clean.\n\n**Verify:** `venv/bin/pytest tests/test_run_local_services.py -v` \u2192 PASS; `venv/bin/pytest tests/ -q` \u2192 all PASS.\n\n```json:metadata\n{\"files\": [\"percona_obs/services.py\", \"tests/test_run_local_services.py\"], \"verifyCommand\": \"venv/bin/pytest tests/test_run_local_services.py -v && venv/bin/black --check percona_obs/ && venv/bin/pyright && venv/bin/pytest tests/ -q\", \"modelTier\": \"standard\", \"acceptanceCriteria\": [\"After `_run_local_services`, a manual-service output `bar.obscpio` exists in the returned workdir with unchanged bytes.\", \"A Phase-1 `obs_scm` output `foo.obscpio` is extracted (its `foo-1.0/\\u2026` tree is consumed) and is absent from the returned workdir; no directories remain in the workdir.\", \"On a level-2 cache hit restoring `bar.obscpio`, the manual service binary is **not** executed and `bar.obscpio` is still present in the returned workdir.\", \"`venv/bin/black percona_obs/` and `venv/bin/pyright` clean.\"]}\n```" + }, + { + "id": 3, + "subject": "Task 3: drift-tolerant patterns replace ignore_vendor", + "status": "completed", + "description": "**Goal:** The branch content check tolerates byte drift for the outputs of registry-driven services (`cargo_vendor` \u2192 `vendor.tar.*`; `node_modules` \u2192 its cpio, spec.inc and `package-lock.json`) through one table-driven helper, preserving today's `vendor.tar.*` behaviour exactly.\n\n**Files:**\n- Modify: `percona_obs/services.py:79-85` (replace `_has_cargo_vendor_service`)\n- Modify: `percona_obs/cmd_sync.py:93` (import), `:124` (drop `_VENDOR_TAR_RE`), `:442-467` (`_content_mismatches`), `:553` (call site)\n- Test: `tests/test_vendor_content_check.py` (rewrite)\n\n**Acceptance Criteria:**\n- [ ] `drift_tolerant_patterns(service_file)` returns `[]` for a missing/unparsable file or a `_service` with neither service; `vendor.tar.*` patterns for `cargo_vendor`; for `node_modules`, patterns for the `cpio`/`output`/`input` param values (defaults `node_modules.obscpio`, `node_modules.spec.inc`, `package-lock.json`).\n- [ ] `_content_mismatches(local, obs, tolerant)`: a tolerated file present on both sides with different md5 is not reported; missing on either side is reported; any non-tolerated difference is reported; empty `tolerant` \u2192 plain comparison.\n- [ ] `_has_cargo_vendor_service` and `_VENDOR_TAR_RE` no longer exist; `grep -rn '_has_cargo_vendor_service\\|_VENDOR_TAR_RE\\|ignore_vendor' percona_obs tests` \u2192 no matches.\n- [ ] `venv/bin/black percona_obs/` and `venv/bin/pyright` clean.\n\n**Verify:** `venv/bin/pytest tests/test_vendor_content_check.py -v` \u2192 PASS; `venv/bin/pytest tests/ -q` \u2192 all PASS.\n\n```json:metadata\n{\"files\": [\"percona_obs/services.py\", \"percona_obs/cmd_sync.py\", \"tests/test_vendor_content_check.py\"], \"verifyCommand\": \"venv/bin/pytest tests/test_vendor_content_check.py -v && venv/bin/black --check percona_obs/ && venv/bin/pyright && venv/bin/pytest tests/ -q\", \"modelTier\": \"standard\", \"acceptanceCriteria\": [\"`drift_tolerant_patterns(service_file)` returns `[]` for a missing/unparsable file or a `_service` with neither service; `vendor.tar.*` patterns for `cargo_vendor`; for `node_modules`, patterns for the `cpio`/`output`/`input` param values (defaults `node_modules.obscpio`, `node_modules.spec.inc`, `package-lock.json`).\", \"`_content_mismatches(local, obs, tolerant)`: a tolerated file present on both sides with different md5 is not reported; missing on either side is reported; any non-tolerated difference is reported; empty `tolerant` \\u2192 plain comparison.\", \"`_has_cargo_vendor_service` and `_VENDOR_TAR_RE` no longer exist; `grep -rn '_has_cargo_vendor_service\\\\|_VENDOR_TAR_RE\\\\|ignore_vendor' percona_obs tests` \\u2192 no matches.\", \"`venv/bin/black percona_obs/` and `venv/bin/pyright` clean.\"]}\n```", + "blockedBy": [ + 2 + ] + }, + { + "id": 4, + "subject": "Task 4: obs-tools image: node_modules, Node 22, cpio", + "status": "completed", + "description": "**Goal:** The CI image provides `/usr/lib/obs/service/node_modules`, `/usr/lib/obs/service/npm_lockfile`, `npm` (Node 22) and `cpio`, and rebuilds when the service source changes.\n\n**Files:**\n- Modify: `.github/docker/obs-tools/Dockerfile:37-40` (base apt line), `:84-88` (openSUSE:Tools line), after `:94` (new Node.js + service blocks)\n- Modify: `.github/workflows/build-obs-image.yml:18-21` (`paths:`)\n\n**Acceptance Criteria:**\n- [ ] `docker build` of the image succeeds locally (or, if Docker is unavailable on the dev box, the `build-obs-image.yml` run after merge succeeds \u2014 record which).\n- [ ] Inside the image: `test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && test -f /usr/lib/obs/service/npm_lockfile.service && command -v cpio && node --version | grep -q '^v22\\.' && npm --version` exits 0.\n- [ ] `build-obs-image.yml` lists `tools/obs-services/**` under `on.push.paths`.\n\n**Verify:** `docker build --build-arg GO_VERSION=$(sed -n 's/^- *GOLANG_VERSION: *//p' root/macros.yaml | head -1 | tr -d '\"'\"' \") -f .github/docker/obs-tools/Dockerfile -t obs-tools:local .` \u2192 exit 0, then `docker run --rm obs-tools:local sh -c 'test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && command -v cpio && node --version && npm --version'` \u2192 prints `v22.x.y` and an npm version.\n\n```json:metadata\n{\"files\": [\".github/docker/obs-tools/Dockerfile\", \".github/workflows/build-obs-image.yml\"], \"verifyCommand\": \"docker build --build-arg GO_VERSION=$(sed -n 's/^- *GOLANG_VERSION: *//p' root/macros.yaml | head -1 | tr -d '\\\"'\\\"' ') -f .github/docker/obs-tools/Dockerfile -t obs-tools:local . && docker run --rm obs-tools:local sh -c 'test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && command -v cpio && node --version && npm --version'\", \"modelTier\": \"standard\", \"acceptanceCriteria\": [\"`docker build` of the image succeeds locally (or, if Docker is unavailable on the dev box, the `build-obs-image.yml` run after merge succeeds \\u2014 record which).\", \"Inside the image: `test -x /usr/lib/obs/service/node_modules && test -x /usr/lib/obs/service/npm_lockfile && test -f /usr/lib/obs/service/npm_lockfile.service && command -v cpio && node --version | grep -q '^v22\\\\.' && npm --version` exits 0.\", \"`build-obs-image.yml` lists `tools/obs-services/**` under `on.push.paths`.\"]}\n```", + "blockedBy": [ + 1 + ] + }, + { + "id": 5, + "subject": "Task 5: ppg:devel:pgadmin project.yaml + README", + "status": "completed", + "description": "**Goal:** `root/ppg/devel/pgadmin/project.yaml` defines the UBI_9-only `ppg:devel:pgadmin` project and `root/README.md` documents the version-independent devel project.\n\n**Files:**\n- Create: `root/ppg/devel/pgadmin/project.yaml`\n- Modify: `root/README.md:172-175` (append a paragraph after the \"Graduation\" paragraph of the devel-tier section)\n\n**Acceptance Criteria:**\n- [ ] `venv/bin/python -m percona_obs -P dev project verify ppg:devel:pgadmin` exits 0 (all `${VAR}` tokens defined).\n- [ ] `venv/bin/python -m percona_obs -P dev sync push --project-only --dry-run ppg:devel:pgadmin` prints a `+` line for project `home:Admin:percona:ppg:devel:pgadmin` with exactly one repository, `UBI_9`, and a project config containing `ExpandFlags: module:nodejs:22`.\n- [ ] `root/README.md` explains `devel/pgadmin/`.\n\n**Verify:** the two commands above \u2192 exit 0 with the described output.\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin/project.yaml\", \"root/README.md\"], \"verifyCommand\": \"venv/bin/python -m percona_obs -P dev project verify ppg:devel:pgadmin && venv/bin/python -m percona_obs -P dev sync push --project-only --dry-run ppg:devel:pgadmin\", \"modelTier\": \"mechanical\", \"acceptanceCriteria\": [\"`venv/bin/python -m percona_obs -P dev project verify ppg:devel:pgadmin` exits 0 (all `${VAR}` tokens defined).\", \"`venv/bin/python -m percona_obs -P dev sync push --project-only --dry-run ppg:devel:pgadmin` prints a `+` line for project `home:Admin:percona:ppg:devel:pgadmin` with exactly one repository, `UBI_9`, and a project config containing `ExpandFlags: module:nodejs:22`.\", \"`root/README.md` explains `devel/pgadmin/`.\"]}\n```" + }, + { + "id": 6, + "subject": "Task 6: docs for npm vendoring flow", + "status": "completed", + "description": "**Goal:** Tool docs and the AI/project reference describe the `npm_lockfile` \u2192 `node_modules` flow, the manual-`.obscpio` rule, the drift-tolerant artifact table and the local prerequisites.\n\n**Files:**\n- Modify: `docs/PERCONA_OBS_TOOL.md:9-25` (System packages table) and a new subsection after `### Sync a single PostgreSQL extension` (line 114-123)\n- Modify: `.github/copilot-instructions.md:493-503` (Local service execution), `:531-535` (cache levels \u2014 add a sentence), `:947-956` (Key Files by Pattern)\n- Modify: `CLAUDE.md` Key Files table (add `tools/obs-services/`)\n\n**Acceptance Criteria:**\n- [ ] `docs/PERCONA_OBS_TOOL.md` System packages table lists `node_modules` and `npm_lockfile` with install sources, and `npm`/`cpio` as external tools.\n- [ ] `docs/PERCONA_OBS_TOOL.md` has a subsection \"Vendoring npm dependencies (`npm_lockfile` + `node_modules`)\" with the `_service` snippet, the three artifacts, the drift-tolerance rule and `--no-cache` as the refresh knob.\n- [ ] `.github/copilot-instructions.md` \"Local service execution\" bullets state that manual-service `.obscpio` outputs are kept, and a \"Drift-tolerant artifacts\" paragraph lists the table.\n- [ ] `CLAUDE.md` Key Files table has a `tools/obs-services/` row.\n\n**Verify:** `grep -n 'npm_lockfile' docs/PERCONA_OBS_TOOL.md .github/copilot-instructions.md CLAUDE.md` \u2192 at least one hit per file.\n\n```json:metadata\n{\"files\": [\"docs/PERCONA_OBS_TOOL.md\", \".github/copilot-instructions.md\", \"CLAUDE.md\"], \"verifyCommand\": \"grep -n 'npm_lockfile' docs/PERCONA_OBS_TOOL.md .github/copilot-instructions.md CLAUDE.md\", \"modelTier\": \"mechanical\", \"acceptanceCriteria\": [\"`docs/PERCONA_OBS_TOOL.md` System packages table lists `node_modules` and `npm_lockfile` with install sources, and `npm`/`cpio` as external tools.\", \"`docs/PERCONA_OBS_TOOL.md` has a subsection \\\"Vendoring npm dependencies (`npm_lockfile` + `node_modules`)\\\" with the `_service` snippet, the three artifacts, the drift-tolerance rule and `--no-cache` as the refresh knob.\", \"`.github/copilot-instructions.md` \\\"Local service execution\\\" bullets state that manual-service `.obscpio` outputs are kept, and a \\\"Drift-tolerant artifacts\\\" paragraph lists the table.\", \"`CLAUDE.md` Key Files table has a `tools/obs-services/` row.\"]}\n```", + "blockedBy": [ + 1, + 2, + 3 + ] + }, + { + "id": 7, + "subject": "Task 7: end-to-end dry run on pgAdmin REL-9_9 (dev)", + "status": "completed", + "description": "**Goal:** Prove the whole local chain on the real upstream \u2014 `obs_scm` \u2192 `npm_lockfile` \u2192 `node_modules` \u2014 with a scratch package under the new project, capturing the answers to the spec's open verification items (Yarn-lock hint, `tar` service behaviour, artifact sizes, and the ~220 MB upload).\n\n**Files:**\n- Create (temporary, **not committed**): `root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service`\n- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md` \u00a710 (fill in the verification results)\n\n**Acceptance Criteria:**\n- [ ] Local prerequisites installed: `/usr/lib/obs/service/node_modules`, `/usr/lib/obs/service/npm_lockfile`, `npm --version` \u2265 7, `cpio`.\n- [ ] `sync push --dry-run -P dev ppg:devel:pgadmin percona-pgadmin4` lists `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc` (plus `percona-pgadmin4.obsinfo`/tarball outputs) as `+`, with no `error:` lines; a second run shows `service npm_lockfile \u2026 (cached)` and `service node_modules \u2026 (cached)`.\n- [ ] The generated `package-lock.json` (from `.cache/services//`) has `lockfileVersion` \u2265 2 and its `packages` count is recorded; whether its resolved versions match `web/yarn.lock` for a sample of 5 packages is recorded (Yarn-lock hint verification).\n- [ ] A real `sync push -P dev ppg:devel:pgadmin percona-pgadmin4` uploads all files (the OBS package file list shows `node_modules.obscpio` with the same MD5 as the local file) \u2014 records elapsed time; any timeout is fixed in `_upload_obs_files` and re-run.\n- [ ] Spec \u00a710 table updated with the outcomes; scratch package removed locally and from the dev OBS.\n\n**Verify:** `venv/bin/python -m percona_obs -P dev sync push --dry-run ppg:devel:pgadmin percona-pgadmin4` \u2192 `\u2714 \u2026 (dry run)` with the three artifacts listed as `+`; `venv/bin/osc -A http://192.168.1.103:3000 ls -l home:Admin:percona:ppg:devel:pgadmin percona-pgadmin4` (after the real push) \u2192 lists `node_modules.obscpio`.\n\n```json:metadata\n{\"files\": [\"docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md\"], \"verifyCommand\": \"venv/bin/python -m percona_obs -P dev sync push --dry-run ppg:devel:pgadmin percona-pgadmin4\", \"modelTier\": \"standard\", \"acceptanceCriteria\": [\"Local prerequisites installed: `/usr/lib/obs/service/node_modules`, `/usr/lib/obs/service/npm_lockfile`, `npm --version` \\u2265 7, `cpio`.\", \"`sync push --dry-run -P dev ppg:devel:pgadmin percona-pgadmin4` lists `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc` (plus `percona-pgadmin4.obsinfo`/tarball outputs) as `+`, with no `error:` lines; a second run shows `service npm_lockfile \\u2026 (cached)` and `service node_modules \\u2026 (cached)`.\", \"The generated `package-lock.json` (from `.cache/services//`) has `lockfileVersion` \\u2265 2 and its `packages` count is recorded; whether its resolved versions match `web/yarn.lock` for a sample of 5 packages is recorded (Yarn-lock hint verification).\", \"A real `sync push -P dev ppg:devel:pgadmin percona-pgadmin4` uploads all files (the OBS package file list shows `node_modules.obscpio` with the same MD5 as the local file) \\u2014 records elapsed time; any timeout is fixed in `_upload_obs_files` and re-run.\", \"Spec \\u00a710 table updated with the outcomes; scratch package removed locally and from the dev OBS.\"]}\n```", + "blockedBy": [ + 1, + 2, + 3, + 5 + ] + } + ], + "lastUpdated": "2026-08-26T13:22:36.955482+00:00" +} \ No newline at end of file diff --git a/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md b/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md new file mode 100644 index 00000000..614aa687 --- /dev/null +++ b/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md @@ -0,0 +1,2549 @@ +# pgAdmin 4 Python 3.12 Stack (sub-project 3) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add the 77 `python3-*` package directories (71 in `root/ppg/devel/pgadmin/`, 6 in `root/ppg/common/deps/` including the `python3-dns` bump) that give `percona-pgadmin4` its complete Python 3.12 runtime closure on UBI 9, and get them all building green in PR #12's OBS project. + +**Architecture:** Every package is an ordinary repo package (`package.yaml`, `obs/_service` with a PyPI-sdist `download_url`, `rpm/python3-.spec`) following one EL spec template with three build families (pyproject via `pip wheel`/`pip install`, legacy `setup.py`, self-hosting backends). Because RHEL 9's build backends cannot build current PEP 639 metadata, `ppg:common:deps` gains a shared backend stack (flit-core, packaging, pathspec, trove-classifiers, hatchling) for EL8/EL9/UBI, while EL10/openSUSE use their distro backends through a spec conditional. The 77 directories are written once by a throwaway render script (Appendix A) from a fixed data file (Appendix B) — both live only in the scratchpad, never in the repo — and are hand-maintained afterwards like every other package. + +**Tech Stack:** RPM spec files (EL8/EL9/EL10/openSUSE conditionals), OBS `download_url` + `cargo_vendor` services, PyPI sdists, `percona-obs` (`sync push --dry-run`), OBS public API (read-only build monitoring), Python 3 (throwaway render script only). + +**Spec:** `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` + +## Global Constraints + +- Work happens in the worktree `/home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1` on branch `pgadmin-sp1` (PR #12). Run every command from there. +- Every commit: `git commit -s` (Signed-off-by). **No** `Co-Authored-By: Claude` lines. +- **Pushing is authorised only to `percona pgadmin-sp1`** (the user designated PR #12 as the test vehicle for SP2–4: "use the same PR to test the following SP"). Never push anywhere else; never open a PR; never add the `obs-sync` label (the user manages labels). +- OBS: `-P isv-pr` and `-P isv` are **production** (api.opensuse.org) — only ever with `--dry-run`. Build results and logs are read through the public API (`https://api.opensuse.org/public/build/...`), never through `osc` writes. No local `rpmbuild`/mock builds (OBS-first validation). +- The render script and its data file live in the scratchpad `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/` and are **never committed**. Only the rendered package directories and the docs changes go into git. After rendering, the specs are hand-maintained: a build fix is an edit to that package's spec (and, if you like, a re-render is *not* required). +- Directory names are `python3-`; the one exception is `python3-dns` (dnspython), whose name is kept because `python3-etcd` and `percona-patroni` require `%{python3_pkgprefix}-dns`. +- Versions are exactly those in Appendix B (spec §3/§4). Do not "upgrade" a package while fixing a build — pins were chosen to fit RHEL's setuptools 68. +- `percona_obs/` is not touched. Still run `venv/bin/black percona_obs/`, `venv/bin/pyright`, `venv/bin/pytest -q` once before the first push to prove nothing moved (expected: unchanged / 0 errors / 147 passed). +- Root-level `macros.yaml`, `project.yaml` files and `root/ppg/devel/pgadmin/project.yaml` are **not** modified by this plan. + +**User decisions (already made):** +- Reuse existing packages where the libraries' real minimums are met (RHEL 9: cffi, cryptography 41.0.7, idna, pycparser, urllib3 1.26, setuptools 68; ppg:common:deps: six, dateutil, psutil 6.1.1, click 8.1.7) — no maturin/Rust for cryptography, no replacement of RHEL runtime packages. +- Bump `python3-dns` in `ppg:common:deps` to dnspython 2.8.0 for everyone (name kept) and add the shared build-backend stack to `ppg:common:deps`. +- Plain package directories, **no generator or manifest committed** to the repo; render once with a throwaway script, then hand-maintain. +- PR #12 stays open as the test vehicle for SP2–4; the user does not merge it yet. +- `%check` is an import smoke test only (no pytest). + +--- + +## File structure + +| Path | Responsibility | +|---|---| +| `root/ppg/common/deps/python3-dns/{package.yaml,obs/_service,rpm/python3-dns.spec}` (modify) | dnspython 1.15.0 → 2.8.0, hatchling build, `download_url` source; `package.yaml` is new (the directory had none). | +| `root/ppg/common/deps/python3-{flit-core,packaging,pathspec,trove-classifiers,hatchling}/` (new) | Shared build-backend stack; `build:` flags disable RockyLinux_10 and both openSUSE repos. | +| `root/ppg/devel/pgadmin/python3-/` ×71 (new) | 68 runtime libraries + build tools `python3-cython`, `python3-poetry-core`, `python3-pdm-backend`. | +| `docs/PACKAGING_HOWTO.md` (modify) | New section "Python 3.12 packages (pyproject builds)" documenting the template; checklist row. | +| `root/README.md` (modify) | `devel/pgadmin/` paragraph lists the Python stack; `common/deps/` mentions the build-backend stack. | +| scratchpad `render_stack.py` + `stack.json` (not committed) | Appendix A and B; produce the 77 directories. | + +Batches (each its own commit, in this order, so an OBS failure localises): + +- **Batch A — `ppg:common:deps` (6):** `python3-flit-core`, `python3-packaging`, `python3-pathspec`, `python3-trove-classifiers`, `python3-hatchling`, `python3-dns`. +- **Batch B — pgadmin build tools + leaves (36):** `python3-cython`, `python3-poetry-core`, `python3-pdm-backend`; leaves (no dependency on another pgadmin package): `authlib babel backports-zstd bcrypt bidict blinker brotli decorator email-validator flask-principal greenlet h11 importlib-resources itsdangerous jaraco-context jeepney jsonformatter libgravatar markupsafe mdurl more-itertools passlib pillow psycopg-c pyasn1 pygments pynacl pyotp pytz shellingham sqlparse typing-extensions ua-parser` (each prefixed `python3-`). +- **Batch C — pgadmin dependents (35):** `alembic flask flask-babel flask-compress flask-login flask-mail flask-migrate flask-paranoid flask-security-too flask-socketio flask-sqlalchemy flask-wtf gssapi jaraco-classes jaraco-functools jinja2 keyring ldap3 mako markdown-it-py paramiko psycopg python-engineio python-socketio qrcode rich secretstorage simple-websocket sqlalchemy sshtunnel typer user-agents werkzeug wsproto wtforms` (each prefixed `python3-`). + +--- + +### Task 1: Render all 77 package directories and commit batch A (`ppg:common:deps`) + +**Goal:** The render script and data are materialised in the scratchpad, all 77 directories exist in the worktree exactly as the template prescribes, every spec parses, a sample dry-run sync resolves the sources, and the six `ppg:common:deps` packages are committed. + +**Files:** +- Create (scratchpad, not committed): `.../scratchpad/render_stack.py` (Appendix A), `.../scratchpad/stack.json` (Appendix B), `.../scratchpad/check_render.py` (Step 3) +- Create: `root/ppg/common/deps/python3-{flit-core,packaging,pathspec,trove-classifiers,hatchling}/{package.yaml,obs/_service,rpm/python3-.spec}` +- Modify: `root/ppg/common/deps/python3-dns/obs/_service`, `root/ppg/common/deps/python3-dns/rpm/python3-dns.spec`; Create: `root/ppg/common/deps/python3-dns/package.yaml` +- Create (left uncommitted for Tasks 2–3): the 71 `root/ppg/devel/pgadmin/python3-*/` directories + +**Acceptance Criteria:** +- [ ] `find root/ppg/devel/pgadmin -name '*.spec' | wc -l` prints `71`; `ls -d root/ppg/common/deps/python3-{flit-core,packaging,pathspec,trove-classifiers,hatchling,dns}` lists 6 directories +- [ ] `check_render.py` reports `specs: 77 parsed OK: 77 failures: 0` and `non-SPDX-looking: []` +- [ ] `sync push --dry-run -P isv-pr ppg:common:deps python3-hatchling` and `... python3-trove-classifiers` each print `✔ service download_url`, `~ 2 files` (spec + sdist) and `✔ sync successful (dry run)` +- [ ] `git show --stat HEAD` lists exactly 17 files: 6 × (`package.yaml`, `obs/_service`, `rpm/*.spec`) minus the pre-existing `python3-dns/obs/_service` + `rpm/python3-dns.spec` counted as modified (i.e. 15 new + 2 modified) +- [ ] `git status --short` afterwards shows only the 71 untracked `root/ppg/devel/pgadmin/python3-*/` directories (plus the pre-existing untracked `.profile`, `venv`) + +**Verify:** `venv/bin/python /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/check_render.py root` → `specs: 77 parsed OK: 77 failures: 0` + +**Steps:** + +- [ ] **Step 1: Materialise the render inputs in the scratchpad** + +Write Appendix A verbatim to `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/render_stack.py` and Appendix B verbatim to `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/stack.json` (the script reads `stack.json` from its own directory). Write the checker below to `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/check_render.py`: + +```python +"""Parse every rendered spec with rpmspec and audit licence strings. Usage: check_render.py """ + +import collections +import pathlib +import subprocess +import sys + +root = pathlib.Path(sys.argv[1]) +specs = sorted(p for p in root.rglob("python3-*/rpm/*.spec")) +bad = 0 +licences = collections.Counter() +for spec in specs: + r = subprocess.run( + ["rpmspec", "-q", "--qf", "%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n", str(spec)], + capture_output=True, + text=True, + ) + if r.returncode: + bad += 1 + print("FAIL", spec.name, r.stderr.strip()[:300]) + for line in spec.read_text().splitlines(): + if line.startswith("License:"): + licences[line.split(":", 1)[1].strip()] += 1 + break +print(f"specs: {len(specs)} parsed OK: {len(specs) - bad} failures: {bad}") +print("licences:", dict(licences)) +odd = [l for l in licences if "see upstream" in l or " " in l.replace(" AND ", "")] +print("non-SPDX-looking:", odd) +``` + +Note: `rpmspec` runs locally on Fedora where `%rhel` is undefined, so the specs' SUSE/`python3` branch is what gets parsed — a syntax check, not an EL rendering. That is intended. + +- [ ] **Step 2: Render into the worktree** + +```bash +cd /home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1 +venv/bin/python /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/render_stack.py . | wc -l +``` +Expected: `77`. Then `git status --short | grep -c '^?? root/ppg/devel/pgadmin/python3-'` → `71`, and `git status --short root/ppg/common/deps` → 5 `??` directories plus ` M root/ppg/common/deps/python3-dns/obs/_service`, ` M root/ppg/common/deps/python3-dns/rpm/python3-dns.spec`, `?? root/ppg/common/deps/python3-dns/package.yaml`. + +- [ ] **Step 3: Parse every spec** + +```bash +venv/bin/python /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/check_render.py root +``` +Expected: `specs: 77 parsed OK: 77 failures: 0`, `non-SPDX-looking: []`. Any FAIL is a render-script bug: fix it in the scratchpad script, re-run Step 2, repeat. + +- [ ] **Step 4: Dry-run the two batch-A packages that exercise special paths** + +```bash +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:common:deps python3-hatchling 2>&1 | tail -8 +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:common:deps python3-trove-classifiers 2>&1 | tail -8 +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:common:deps python3-dns 2>&1 | tail -8 +``` +Expected for each: `✔ service download_url ...`, `✔ packaging rpm/ ... (1 files)`, `~ 2 files` with `|_ + python3-.spec` and `|_ + .tar.gz` (for `python3-dns`: `~ 3 files` with `|_ - python3-dns-1.15.0.tar.gz` or the obs_scm artefacts removed, `|_ + dnspython-2.8.0.tar.gz`, `|_ ~ python3-dns.spec`), `✔ sync successful (dry run)`. A 404 from `download_url` means a wrong sdist URL in Appendix B — fix the `sdist_url` there, re-render. + +- [ ] **Step 5: Commit batch A** + +```bash +git add root/ppg/common/deps/python3-flit-core root/ppg/common/deps/python3-packaging \ + root/ppg/common/deps/python3-pathspec root/ppg/common/deps/python3-trove-classifiers \ + root/ppg/common/deps/python3-hatchling root/ppg/common/deps/python3-dns +git commit -s -F - <<'EOF' +ppg:common:deps: Python 3.12 build-backend stack; bump python3-dns to dnspython 2.8.0 + +Add python3-flit-core 3.12.0, python3-packaging 25.0, python3-pathspec 0.12.1, +python3-trove-classifiers 2025.9.11.17 and python3-hatchling 1.28.0 for +EL8/EL9/UBI (EL10 and openSUSE use their distro backends; disabled via +package.yaml build flags). RHEL's flit-core 3.9 / hatchling / packaging 23.2 +cannot build current PEP 639 metadata (Flask, Werkzeug, Pygments, ...). + +Bump python3-dns from dnspython 1.15.0 to 2.8.0 (email-validator, needed by +Flask-Security-Too for pgAdmin 4, requires >= 2.0). Package/RPM name kept so +python3-etcd and percona-patroni need no change; source moves from obs_scm +to the PyPI sdist via download_url; built with hatchling. + +Design: docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md +EOF +git show --stat HEAD | tail -20 +``` +Expected: 15 new files + 2 modified (17 in the stat). + +--- + +### Task 2: Commit batch B (pgadmin build tools + leaf libraries) + +**Goal:** The 36 pgadmin packages that depend on nothing else in the pgadmin project (three build tools + 33 leaves) are committed, after a dry-run proves the `cargo_vendor` path for bcrypt and the plain path for a native leaf. + +**Files:** +- Create: `root/ppg/devel/pgadmin/python3-{cython,poetry-core,pdm-backend}/` and the 33 leaf directories listed under "Batch B" above (each `package.yaml`, `obs/_service`, `rpm/python3-.spec`) + +**Acceptance Criteria:** +- [ ] `sync push --dry-run -P isv-pr ppg:devel:pgadmin python3-bcrypt` prints `✔ service download_url`, `✔ service cargo_vendor` and a would-be upload of 3 files (`python3-bcrypt.spec`, `bcrypt-5.0.0.tar.gz`, `vendor.tar.gz`) +- [ ] `sync push --dry-run -P isv-pr ppg:devel:pgadmin python3-psycopg-c` prints `~ 2 files` and `✔ sync successful (dry run)` +- [ ] `git show --stat HEAD | tail -1` reports `108 files changed` (36 × 3) +- [ ] `git status --short | grep -c '^?? root/ppg/devel/pgadmin/'` → `35` + +**Verify:** `git show --stat HEAD | tail -1` → `108 files changed, ... insertions(+)` + +**Steps:** + +- [ ] **Step 1: Dry-run bcrypt (cargo_vendor) and one native leaf** + +```bash +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-bcrypt 2>&1 | tail -12 +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-psycopg-c 2>&1 | tail -8 +``` +Expected (bcrypt): `✔ service download_url`, `✔ service cargo_vendor`, `~ 3 files` listing `+ python3-bcrypt.spec`, `+ bcrypt-5.0.0.tar.gz`, `+ vendor.tar.gz`, `✔ sync successful (dry run)`. If `cargo_vendor` fails with "no Cargo.toml", the `cargotoml` param path is wrong — check `tar tzf` of the sdist for the crate path (`bcrypt-5.0.0/src/_bcrypt/Cargo.toml`) and fix `obs/_service` by hand (this is the only hand edit expected in this task; keep the render script in sync only if you re-render). + +- [ ] **Step 2: Commit batch B** + +```bash +cd root/ppg/devel/pgadmin +git add python3-cython python3-poetry-core python3-pdm-backend \ + python3-authlib python3-babel python3-backports-zstd python3-bcrypt python3-bidict python3-blinker \ + python3-brotli python3-decorator python3-email-validator python3-flask-principal python3-greenlet \ + python3-h11 python3-importlib-resources python3-itsdangerous python3-jaraco-context python3-jeepney \ + python3-jsonformatter python3-libgravatar python3-markupsafe python3-mdurl python3-more-itertools \ + python3-passlib python3-pillow python3-psycopg-c python3-pyasn1 python3-pygments python3-pynacl \ + python3-pyotp python3-pytz python3-shellingham python3-sqlparse python3-typing-extensions python3-ua-parser +cd - +git commit -s -F - <<'EOF' +ppg:devel:pgadmin: Python 3.12 stack, part 1 — build tools and leaf libraries + +Build tools: python3-cython 3.1.3 (gssapi, SQLAlchemy speedups), +python3-poetry-core 2.2.1 and python3-pdm-backend 2.4.5 (self-hosting). + +33 leaf libraries of pgAdmin 4's Python 3.12 closure that depend only on +RHEL 9 / ppg:common:deps packages: PyPI sdists via download_url, one EL +spec template (pip wheel / pip install, or setup.py for legacy packages), +import smoke test in %check. bcrypt vendors its Rust crate with +cargo_vendor (cargotoml=src/_bcrypt/Cargo.toml). + +Versions follow the spec: pinned below latest where PEP 639 metadata would +need setuptools >= 77 (importlib-resources 6.5.2, jaraco.context 6.0.1, +Pillow 11.1.0), bidict 0.23.1 (no uv_build), pyotp 2.9.0 (no hatch-vcs), +ua-parser 0.18.0 (self-contained). + +Design: docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md +EOF +git show --stat HEAD | tail -1 +``` +Expected: `108 files changed`. + +--- + +### Task 3: Commit batch C (pgadmin dependents) + +**Goal:** The remaining 35 pgadmin packages, which depend on batch A/B packages, are committed after a dry-run of one package per remaining build family. + +**Files:** +- Create: the 35 directories listed under "Batch C" above + +**Acceptance Criteria:** +- [ ] Dry-runs of `python3-flask` (flit), `python3-wtforms` (hatchling), `python3-paramiko` (setup.py), `python3-typer` (pdm), `python3-rich` (poetry) each end with `✔ sync successful (dry run)` and `~ 2 files` +- [ ] `git show --stat HEAD | tail -1` reports `105 files changed` (35 × 3) +- [ ] `git status --short | grep -c '^?? root/'` → `0` + +**Verify:** `git status --short | grep -c '^?? root/'` → `0` + +**Steps:** + +- [ ] **Step 1: Dry-run one package per build family** + +```bash +for p in python3-flask python3-wtforms python3-paramiko python3-typer python3-rich; do + venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin $p 2>&1 | tail -4 +done +``` +Expected: five times `~ 2 files` and `✔ sync successful (dry run)`. + +- [ ] **Step 2: Commit batch C** + +```bash +cd root/ppg/devel/pgadmin +git add python3-alembic python3-flask python3-flask-babel python3-flask-compress python3-flask-login \ + python3-flask-mail python3-flask-migrate python3-flask-paranoid python3-flask-security-too \ + python3-flask-socketio python3-flask-sqlalchemy python3-flask-wtf python3-gssapi python3-jaraco-classes \ + python3-jaraco-functools python3-jinja2 python3-keyring python3-ldap3 python3-mako python3-markdown-it-py \ + python3-paramiko python3-psycopg python3-python-engineio python3-python-socketio python3-qrcode python3-rich \ + python3-secretstorage python3-simple-websocket python3-sqlalchemy python3-sshtunnel python3-typer \ + python3-user-agents python3-werkzeug python3-wsproto python3-wtforms +cd - +git commit -s -F - <<'EOF' +ppg:devel:pgadmin: Python 3.12 stack, part 2 — Flask, SQLAlchemy, keyring and friends + +The 35 remaining libraries of pgAdmin 4's Python 3.12 closure, which +depend on part 1 / ppg:common:deps packages: Flask 3.1.3 and its +extensions (Flask-Security-Too 5.6.2, Flask-SocketIO, Flask-Migrate, ...), +SQLAlchemy 2.0.52 + alembic, Werkzeug/Jinja2, keyring 25.2.1 stack, +paramiko/sshtunnel, psycopg 3.2.10, typer/rich, gssapi 1.10.1 (Cython 3), +python-socketio/engineio, WTForms. + +Runtime dependencies are also BuildRequires so the %check import smoke +test runs against the buildroot; OBS orders the builds accordingly. + +Design: docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md +EOF +git show --stat HEAD | tail -1 +``` +Expected: `105 files changed`. + +--- + +### Task 4: Document the template and the new package sets + +**Goal:** `docs/PACKAGING_HOWTO.md` explains how a Python 3.12 pyproject-based package is written here (so the next one follows the same pattern), and `root/README.md` says what `devel/pgadmin/` and `common/deps/` now contain. + +**Files:** +- Modify: `docs/PACKAGING_HOWTO.md` (insert a new section before `## Adding a Package as an Aggregate in a Subproject`; add one checklist row) +- Modify: `root/README.md` (the `common/deps/` bullet and the `### devel/pgadmin/` paragraph) + +**Acceptance Criteria:** +- [ ] `grep -n "^## Python 3.12 packages" docs/PACKAGING_HOWTO.md` finds the section, positioned before `## Adding a Package as an Aggregate in a Subproject` +- [ ] The section contains the header block, the `pip wheel`/`pip install` recipe, the `setup.py` legacy recipe, the hatchling `%if 0%{?rhel} == 8 || 0%{?rhel} == 9` conditional, the `%check` line and the reuse policy +- [ ] `grep -c "python3-hatchling\|build-backend stack" root/README.md` ≥ 2 +- [ ] `venv/bin/pytest -q` → `147 passed`; `venv/bin/black percona_obs/` → unchanged; `venv/bin/pyright` → `0 errors` + +**Verify:** `grep -n "^## Python 3.12 packages\|^## Adding a Package as an Aggregate" docs/PACKAGING_HOWTO.md` → the Python section's line number is smaller + +**Steps:** + +- [ ] **Step 1: Insert the how-to section** + +Insert the following immediately before the line `## Adding a Package as an Aggregate in a Subproject` in `docs/PACKAGING_HOWTO.md`: + +````markdown +## Python 3.12 packages (pyproject builds) + +RPM-only Python libraries for RHEL-family targets are built for `/usr/bin/python3.12` +and named `python3.12-` (`%{python3_pkgprefix}-`; plain `python3-` on +openSUSE). The directory is `python3-`. Examples: +everything under `root/ppg/devel/pgadmin/python3-*` and the build-backend stack in +`root/ppg/common/deps/` (`python3-flit-core`, `python3-packaging`, `python3-pathspec`, +`python3-trove-classifiers`, `python3-hatchling`). + +**Reuse before you build.** RHEL 9 already ships `python3.12-{cffi,cryptography,idna, +pycparser,urllib3,setuptools,pip,wheel}` (AppStream) and `python3.12-{packaging 23.2, +pluggy,pytest,setuptools-rust,flit-core 3.9,Cython 0.29}` (CRB); `ppg:common:deps` has +`six`, `dateutil`, `psutil`, `click`, `dns`. Depend on those by their RPM name — without a +version floor, since RHEL's versions are often older than upstream's declared minimum but +work — and only package what is missing. RHEL's *build backends* are too old for current +PEP 639 metadata; use ours from `ppg:common:deps` (see the conditional below). + +**Source:** the PyPI sdist via `download_url`, literal version: + +```xml + + + https://files.pythonhosted.org/packages/source/f/flask/flask-3.1.3.tar.gz + + +``` + +**Spec header** (identical in every package; copy from `root/ppg/devel/pgadmin/python3-flask/rpm/python3-flask.spec`): + +```rpmspec +%global debug_package %{nil} # noarch only; drop for C/Rust extensions +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") +# extensions: python3_sitearch with 'platlib' instead + +Name: %{python3_pkgprefix}-flask +Version: 3.1.3 +Release: 1%{?dist} +Source0: https://files.pythonhosted.org/packages/source/f/flask/flask-3.1.3.tar.gz +BuildArch: noarch +Epoch: 1 +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +``` + +Then the backend the sdist's `[build-system]` names — `python%{python3_buildversion}-flit-core`, +`%{python3_pkgprefix}-poetry-core`, `%{python3_pkgprefix}-pdm-backend`, `%{python3_pkgprefix}-setuptools_scm` +(EPEL) when `setuptools_scm` is listed — and for hatchling the distro/ours switch: + +```rpmspec +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling # ours, ppg:common:deps +%else +BuildRequires: python3-hatchling # EL10 CRB / openSUSE +%endif +``` + +Every runtime `Requires:` is also a `BuildRequires:` so `%check` can import the module. + +**Build/install — pyproject packages** (setuptools, flit, hatchling, poetry, pdm): + +```rpmspec +%prep +%autosetup -p1 -n flask-%{version} + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -c "import flask" + +%files +%{python3_sitelib}/* +%{_bindir}/flask +``` + +Self-hosting backends (hatchling, poetry-core, pdm-backend, flit-core) add +`export PYTHONPATH=$PWD/src` (flit-core: `$PWD/.`) before `pip wheel`. Rust extensions +(bcrypt) add a `cargo_vendor` service (`cargotoml` pointing at the crate's `Cargo.toml`), +`Source1: vendor.tar.gz`, `%autosetup -a1`, `CARGO_NET_OFFLINE=true`. + +**Build/install — legacy `setup.py`-only packages** (no `pyproject.toml`): keep the +`%{__ospython} setup.py build` / `setup.py install --single-version-externally-managed -O1 +--root=%{buildroot} --record=INSTALLED_FILES` recipe and `%files -f INSTALLED_FILES`, as in +`root/ppg/common/deps/python3-click`. + +**Bumping:** edit the sdist URL in `obs/_service`, `Version:` in the spec, add a +`%changelog` entry. Nothing is generated. + +--- + +```` + +And add this row to the "Quick Reference: File Checklist" table, after the `rpm/.spec` row: + +```markdown +| `rpm/python3-.spec` | Python 3.12 libs | Template in "Python 3.12 packages"; `Version:` literal; runtime deps also `BuildRequires` | +``` + +- [ ] **Step 2: Update root/README.md** + +In the `### devel/pgadmin/ — a version-independent devel project` paragraph of `root/README.md`, replace the sentence + +`Packages there follow the normal `rpm/` + `obs/_service` layout; the npm dependencies are vendored at sync time by the `npm_lockfile` → `node_modules` service pair (see `docs/PERCONA_OBS_TOOL.md`).` + +with + +`Packages there follow the normal `rpm/` + `obs/_service` layout. The Python side is the ~70 `python3-*` directories (pgAdmin's Python 3.12 closure, built from PyPI sdists — see "Python 3.12 packages" in `docs/PACKAGING_HOWTO.md`); the npm dependencies are vendored at sync time by the `npm_lockfile` → `node_modules` service pair (see `docs/PERCONA_OBS_TOOL.md`). The shared Python build-backend stack (`python3-flit-core`, `python3-packaging`, `python3-pathspec`, `python3-trove-classifiers`, `python3-hatchling`) lives in `ppg/common/deps/` because other products' Python packages need it too.` + +- [ ] **Step 3: Run the repo checks and commit** + +```bash +venv/bin/black percona_obs/ && venv/bin/pyright && venv/bin/pytest -q +git add docs/PACKAGING_HOWTO.md root/README.md +git commit -s -m "docs: how to write Python 3.12 (pyproject) packages; document the pgAdmin Python stack" +``` +Expected: `left unchanged`, `0 errors`, `147 passed`. + +--- + +### Task 5: Build everything in PR #12's OBS project and fix what fails + +**Goal:** All 77 packages build green in `isv:percona:PR:pr-12` — the six common:deps ones on every repo they build for, the 71 pgadmin ones on UBI_9 x86_64 and aarch64 — with the dnspython cascade (python3-etcd, percona-patroni) also green. + +**Files:** +- Modify (only as build failures dictate): individual `root/**/python3-*/rpm/*.spec` or `obs/_service` files +- Push: `percona pgadmin-sp1` + +**Acceptance Criteria:** +- [ ] `git push percona pgadmin-sp1` succeeds and a new `OBS PR Check` run appears for the pushed head (`gh run list --repo percona/obs-packaging --branch pgadmin-sp1 --limit 1`) +- [ ] The run's "Sync packages to OBS" job succeeds and its log shows `+ package isv:percona:PR:pr-12:ppg:common:deps/python3-hatchling` (and the other 5) and 71 `+ package isv:percona:PR:pr-12:ppg:devel:pgadmin/python3-...` lines, plus dep-promotes of `python3-etcd` and `percona-patroni` +- [ ] `_result` of `isv:percona:PR:pr-12:ppg:devel:pgadmin` shows `code="succeeded"` for all 71 packages on UBI_9 x86_64 and aarch64 (no `failed`, `unresolvable`, `broken`) +- [ ] `_result` of `isv:percona:PR:pr-12:ppg:common:deps` shows `succeeded` for `python3-dns` on every RPM repo, for the five backend packages on RockyLinux_8/9 and UBI_8/9 (and `disabled` on RockyLinux_10/openSUSE), and for `python3-etcd` +- [ ] Every fix made during the loop is a committed spec/_service edit with a one-line rationale in the commit message; the fixes are listed in the PR body (Task 6) + +**Verify:** `curl -s "https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result" | grep -c 'code="succeeded"'` → `142` (71 × 2 archs) + +**Steps:** + +- [ ] **Step 1: Push** + +```bash +git log --oneline percona/pgadmin-sp1..HEAD # expect: the 4 commits from Tasks 1-4 (+ the two spec commits already there if not yet pushed) +git push percona pgadmin-sp1 +gh run list --repo percona/obs-packaging --branch pgadmin-sp1 --limit 2 --json databaseId,headSha,status,conclusion --jq '.[] | "\(.databaseId) \(.headSha[0:7]) \(.status)/\(.conclusion)"' +``` +Expected: a run for the new head. (Actions may be delayed after outages; if no run appears within 15 minutes and githubstatus.com shows Actions degraded, wait — do not re-push.) + +- [ ] **Step 2: Watch the sync job, then the builds** + +Sync job log (after it completes): `gh api repos/percona/obs-packaging/actions/jobs//logs | grep -E "\+ package|dep-promote|error" | head -120`. Expected: 77 `+ package` lines for our packages plus dep-promotes of `python3-etcd` and the `percona-patroni` packages; no `error:`. + +Build results (poll every ~10 min; OBS builds the stack bottom-up over 1–2 hours): +```bash +curl -s "https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:common:deps/_result?package=python3-flit-core&package=python3-packaging&package=python3-pathspec&package=python3-trove-classifiers&package=python3-hatchling&package=python3-dns&package=python3-etcd" | grep -E "/_log" | grep -nE "error|Error|No such|not found|ModuleNotFoundError" | tail -30` (use `.../ppg:common:deps//x86_64/...` for batch A). Known likely causes and their fixes (edit the spec by hand, commit, push — each push re-syncs only the changed package): + - `ModuleNotFoundError` in `%check` → a runtime dependency missing from `BuildRequires`/`Requires`; add it (`%{python3_pkgprefix}-`, or the RHEL name for reused packages). + - `nothing provides python3.12-` (unresolvable) → the dependency's RPM name differs (RHEL names: `python3.12-setuptools_scm` is EPEL's exact name; ours are `%{python3_pkgprefix}-`); fix the name. + - `error: Installed (but unpackaged) file(s)` → a console script or data file outside `%{python3_sitelib}`; add the path to `%files`. + - `license-files`/`license field should be dict` → a backend too old resolved (EL10/openSUSE distro backend on a package that should not build there) — check the package's `build:` flags / conditional. + - bcrypt `failed to select a version for ... offline` → vendor tarball not found by cargo; ensure `%autosetup -a1` extracted `vendor/` and `.cargo/config` at the sdist root; if the vendor config lives elsewhere, `cp .cargo/config.toml src/_bcrypt/.cargo/` in `%prep`. + - gssapi `Cython.Compiler` errors → `python3.12-cython` (ours, 3.1.3) not selected over CRB 0.29: add `BuildRequires: %{python3_pkgprefix}-cython >= 3.1`. + - Pillow feature errors → adjust the `-C =disable` list in `%build`. + - trove-classifiers `use_calver` still present → the `sed` in `%prep` missed; inspect the sdist's `setup.py` and adapt the expression. + Commit each fix: `git commit -s -m "python3-: "`, then `git push percona pgadmin-sp1`. + +- [ ] **Step 4: Record the final state** + +When both `_result` queries show the expected end state, save the final counts (number of fix commits, list of packages that needed fixes) for Task 6. + +--- + +### Task 6: Update PR #12 and the effort's records + +**Goal:** PR #12's body describes SP3 (what was added, versions policy, the dnspython bump and its cascade, verification results, fixes made during the build loop), and the spec's risk table reflects what actually happened. + +**Files:** +- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` (§8 rows: mark each risk as observed/not, add any new failure class found) +- Modify (via `gh pr edit`): PR #12 title and body + +**Acceptance Criteria:** +- [ ] `gh pr view 12 --repo percona/obs-packaging --json title --jq .title` → `pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack` +- [ ] The PR body has an `## SP3 — Changes` section (package counts, the six common:deps packages, dnspython bump + cascade, version pins) and an `## SP3` block under Verification (dry-runs, OBS results, list of build-loop fixes) +- [ ] Spec §8 has no row left saying only "verified by the first build"; each says what happened +- [ ] Spec change committed and pushed with `git push percona pgadmin-sp1` + +**Verify:** `gh pr view 12 --repo percona/obs-packaging --json body --jq .body | grep -c "SP3"` ≥ 3 + +**Steps:** + +- [ ] **Step 1: Update spec §8 with the observed outcome** — for every row in the risks table, replace the mitigation text's forward-looking part with what the OBS builds showed (e.g. "did not occur", or "occurred on : fixed by "). Commit: `git commit -s -m "docs: SP3 spec — record build outcomes against the risk table"`. + +- [ ] **Step 2: Edit the PR** — fetch the current body (`gh pr view 12 --repo percona/obs-packaging --json body --jq .body > /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/pr12-body.md`), then: + - Title: `pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack`. + - Summary list: add `- **SP3 — Python 3.12 stack:** the 71 python3.12-* RPMs of pgAdmin 4's dependency closure in ppg:devel:pgadmin, plus a shared build-backend stack and a dnspython bump in ppg:common:deps.` + - New section after `## SP2 — Changes`: + ```markdown + ## SP3 — Changes + + - **`root/ppg/devel/pgadmin/python3-*` (71 new)** — pgAdmin 4 `REL-9_9`'s Python 3.12 closure (cloud extras excluded), one EL spec template: PyPI sdist via `download_url`, `pip wheel --no-build-isolation` + `pip install --root` (legacy `setup.py` for 9 packages), import smoke test in `%check`, runtime deps doubled as `BuildRequires`. Build tools `python3-cython` 3.1.3, `python3-poetry-core`, `python3-pdm-backend`. bcrypt vendors its Rust crate with `cargo_vendor`. + - **`root/ppg/common/deps/`** — `python3-dns` bumped 1.15.0 → dnspython 2.8.0 (email-validator ≥ 2.0; name kept, `python3-etcd`/`percona-patroni` rebuilt by dep-cascade); new shared build-backend stack `python3-flit-core` 3.12.0, `python3-packaging` 25.0, `python3-pathspec`, `python3-trove-classifiers`, `python3-hatchling` 1.28.0 for EL8/EL9/UBI (RHEL's backends cannot build PEP 639 metadata; EL10/openSUSE use their distro ones via a spec conditional). + - **Version policy** — reuse RHEL 9's `python3.12-{cffi,cryptography 41,idna,pycparser,urllib3 1.26,setuptools 68}` and common:deps' `six/dateutil/psutil/click`; pinned down where PEP 639 metadata would need setuptools ≥ 77 (keyring 25.2.1, jaraco.context 6.0.1, jaraco.functools 4.1.0, importlib-resources 6.5.2, Pillow 11.1.0), bidict 0.23.1, pyotp 2.9.0, ua-parser 0.18.0. + - **Docs** — `docs/PACKAGING_HOWTO.md` "Python 3.12 packages (pyproject builds)"; `root/README.md`. + - Design: `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md`. + ``` + - Under `## Verification`, add a `**SP3**` block with: the dry-run results, the OBS `_result` end state (counts), the list of build-loop fixes (package → cause → commit), and the dnspython cascade result (python3-etcd + patroni green). + - `gh pr edit 12 --repo percona/obs-packaging --title "" --body-file <the file>`. + +- [ ] **Step 3: Push the spec update** + +```bash +git push percona pgadmin-sp1 +``` + +--- + +## Self-review + +- **Spec coverage:** §3 decisions → Tasks 1–3 (reuse via `REUSED` map, pins in Appendix B, dnspython bump and backend stack in batch A, plain directories, sdist sources, `%check` policy, naming) and Task 4 (docs). §4 inventory → Appendix B (77 entries = 71 + 6). §5 template → Appendix A `spec_text` (header, families, hatchling conditional, `%files`, `%check` with `PYTHONPATH`, changelog, trove-classifiers patch, bcrypt cargo). §6 → Global Constraints (scratchpad-only script) + Task 4. §7 verification → Tasks 1–3 dry-runs and Task 5 OBS acceptance. §8 risks → Task 5 Step 3 fix catalogue and Task 6 Step 1. §9 out of scope respected. +- **Placeholder scan:** none — every step has its command/content; the render script and data are complete in the appendices. +- **Consistency:** batch lists sum to 6 + 36 + 35 = 77 and match Appendix B's `project` fields; RPM names follow `python3-<norm>` everywhere except `python3-dns`; the docs section reproduces the exact template Appendix A emits. + +--- + +## Appendix A — `render_stack.py` (throwaway; save to the scratchpad, do not commit) + +```python +"""Render the SP3 Python 3.12 stack package directories from stack.json. + +Throwaway: run once, commit the resulting package directories, never commit +this script. Usage: python render_stack.py <repo-root> [--only name,name] + +For every entry in stack.json writes + <repo>/root/<project-dir>/python3-<name>/package.yaml + <repo>/root/<project-dir>/python3-<name>/obs/_service + <repo>/root/<project-dir>/python3-<name>/rpm/python3-<name>.spec +following the spec template in docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md §5. +""" + +import datetime +import json +import re +import sys +from pathlib import Path + +HERE = Path(__file__).parent +STACK = json.loads((HERE / "stack.json").read_text()) +TODAY = datetime.date(2026, 8, 27) # fixed so re-runs are byte-identical +CHANGELOG_DATE = TODAY.strftime("%a %b %d %Y") + +PROJECT_DIR = {"pgadmin": "ppg/devel/pgadmin", "common": "ppg/common/deps"} + +# PyPI name (normalised) -> RPM name for packages we do NOT build here. +REUSED = { + "cffi": "python3.12-cffi", + "cryptography": "python3.12-cryptography", + "idna": "python3.12-idna", + "pycparser": "python3.12-pycparser", + "urllib3": "python3.12-urllib3", + "setuptools": "python3.12-setuptools", + "click": "%{python3_pkgprefix}-click", + "six": "%{python3_pkgprefix}-six", + "python-dateutil": "%{python3_pkgprefix}-dateutil", + "psutil": "%{python3_pkgprefix}-psutil", + "dnspython": "%{python3_pkgprefix}-dns", + "pluggy": "python3.12-pluggy", +} +# Classifier / free-text licence strings -> SPDX identifiers. +LICENSE = { + "MIT License": "MIT", + "MIT": "MIT", + "BSD License": "BSD-3-Clause", + "Apache Software License": "Apache-2.0", + "Apache 2.0": "Apache-2.0", + "ISC License (ISCL)": "ISC", + "GNU Lesser General Public License v3 (LGPLv3)": "LGPL-3.0-only", + "GNU Lesser General Public License v2 or later (LGPLv2+)": "LGPL-2.1-or-later", + "LGPL": "LGPL-2.1-or-later", + "Python Software Foundation License": "PSF-2.0", + "Mozilla Public License 2.0 (MPL 2.0)": "MPL-2.0", + "GNU General Public License v3 or later (GPLv3+)": "GPL-3.0-or-later", + "MIT-CMU": "MIT-CMU", + "MPL 2.0": "MPL-2.0", + "LGPL v3": "LGPL-3.0-only", + "ISC License": "ISC", + "GNU General Public License v3 (GPLv3)": "GPL-3.0-only", + "The Unlicense (Unlicense)": "Unlicense", +} +# Per-package overrides where PyPI metadata is missing or ambiguous (checked upstream). +LICENSE_BY_PKG = { + "hatchling": "MIT", + "python-engineio": "MIT", + "python-socketio": "MIT", + "bidict": "MPL-2.0", + "ldap3": "LGPL-3.0-only", + "pytz": "MIT", + "shellingham": "ISC", + "mdurl": "MIT", + "markdown-it-py": "MIT", + "typing-extensions": "PSF-2.0", + "jsonformatter": "MIT", + "libgravatar": "MIT", + "qrcode": "BSD-3-Clause", + "Flask-Principal": "MIT", + "Flask-Login": "MIT", + "simple-websocket": "MIT", + "wsproto": "MIT", + "ua-parser": "Apache-2.0", + "user-agents": "MIT", + "passlib": "BSD-3-Clause", + "psycopg": "LGPL-3.0-only", + "psycopg-c": "LGPL-3.0-only", +} + + +def spdx(pypi: str, lic: str) -> str: + if pypi in LICENSE_BY_PKG: + return LICENSE_BY_PKG[pypi] + lic = lic.strip() + return LICENSE.get(lic, lic or "see upstream") +# Directory / RPM-name exceptions. +DIRNAME = {"dnspython": "dns"} +# Import smoke-test overrides (wheel top_level.txt missing or namespace pkgs). +MODULES = { + "Flask-Principal": ["flask_principal"], + "jsonformatter": ["jsonformatter"], + "psycopg-c": ["psycopg_c"], + "jaraco.classes": ["jaraco.classes"], + "jaraco.context": ["jaraco.context"], + "jaraco.functools": ["jaraco.functools"], + "backports.zstd": ["backports.zstd"], + "poetry-core": ["poetry.core.masonry.api"], + "pdm-backend": ["pdm.backend"], + "flit-core": ["flit_core.buildapi"], + "Cython": ["Cython"], + "brotli": ["brotli"], + "PyNaCl": ["nacl"], +} +# Self-hosting build backends: the directory to put on PYTHONPATH so the +# backend can build itself with --no-build-isolation. +SELF_HOSTING = {"hatchling": "src", "poetry-core": "src", "pdm-backend": "src", "flit-core": "."} +# Extra BuildRequires by package (beyond family defaults). +EXTRA_BR = { + "bcrypt": ["%{python3_pkgprefix}-setuptools-rust", "cargo", "rust", "gcc"], + "gssapi": ["%{python3_pkgprefix}-cython", "krb5-devel", "gcc"], + "SQLAlchemy": ["%{python3_pkgprefix}-cython", "gcc"], + "psycopg-c": ["libpq-devel", "gcc"], + "PyNaCl": ["python3.12-cffi", "libffi-devel", "make", "gcc"], + "pillow": ["libjpeg-turbo-devel", "zlib-devel", "gcc"], + "backports.zstd": ["libzstd-devel", "gcc"], + "brotli": ["gcc-c++"], + "greenlet": ["gcc-c++"], + "MarkupSafe": ["gcc"], + "Cython": ["gcc"], + "SecretStorage": [], +} +NEEDS_SCM = lambda r: bool(re.search(r"setuptools[-_]scm", r["build_requires"])) # noqa: E731 +# Build flags (package.yaml build:) — the common:deps hatchling stack only. +DISABLE_DISTRO_HATCHLING = {"hatchling", "pathspec", "trove-classifiers", "flit-core", "packaging"} +BUILD_FLAGS_DISTRO = ["RockyLinux_10", "openSUSE_Leap_16", "openSUSE_Tumbleweed"] +# pip wheel config settings. +CONFIG_SETTINGS = { + "pillow": " ".join( + f"-C {f}" for f in [ + "platform-guessing=disable", "zlib=enable", "jpeg=enable", "tiff=disable", + "freetype=disable", "raqm=disable", "lcms=disable", "webp=disable", + "xcb=disable", "jpeg2000=disable", "imagequant=disable", "avif=disable", + ] + ) +} +# Previous changelog entries to keep (package -> text block). +OLD_CHANGELOG = { + "dnspython": "* Mon Mar 30 2026 Percona Build/Release Team <eng-build@percona.com> - 1.15.0-1\n- Initial build of python3-dns 1.15.0\n", +} + + +def norm(name: str) -> str: + return re.sub(r"[._]", "-", name.lower()) + + +BUILT = {norm(r["pypi"]): r for r in STACK} + + +def rpm_dep(req: str) -> str | None: + """PyPI requirement 'Name spec' -> 'Requires:' target, or None to drop.""" + m = re.match(r"^([A-Za-z0-9_.\-]+)\s*(.*)$", req) + if not m: + return None + name, spec = norm(m.group(1)), m.group(2).strip() + if name in REUSED: + target = REUSED[name] + elif name in BUILT: + target = f"%{{python3_pkgprefix}}-{DIRNAME.get(BUILT[name]['pypi'], name)}" + else: + return None # not in our closure (optional/extra dependency) + floor = None + for part in [p.strip() for p in spec.split(",") if p.strip()]: + mm = re.match(r"^(>=|~=|==)\s*([0-9][0-9A-Za-z.]*)$", part) + if mm: + floor = mm.group(2) + # RHEL's reused packages are older than several floors; the libraries work + # with them (spec §3), so never emit a floor against a reused package. + if floor and name not in REUSED: + return f"{target} >= {floor}" + return target + + +def spec_text(r: dict) -> str: + pypi, ver = r["pypi"], r["version"] + name = DIRNAME.get(pypi, norm(pypi)) + fam = "self-hosting" if pypi in SELF_HOSTING else r["family"] + native = r["native"] + sitedir = "python3_sitearch" if native else "python3_sitelib" + lines = [] + if not native: + lines.append("%global debug_package %{nil}\n") + lines.append( + "%if 0%{?rhel} && 0%{?rhel} >= 8\n" + "%global __ospython %{_bindir}/python3.12\n" + "%global python3_pkgprefix python3.12\n" + "%global python3_buildversion 3.12\n" + "%global __requires_exclude ^python3\\\\.12dist\n" + "%else\n" + "%global __ospython %{_bindir}/python3\n" + "%global python3_pkgprefix python3\n" + "%global python3_buildversion 3\n" + "%endif\n" + "%{expand: %%global py3ver %(echo `%{__ospython} -c \"import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')\" `)}\n" + ) + if native: + lines.append("%global python3_sitearch %(%{__ospython} -Esc \"import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))\")\n") + else: + lines.append("%global python3_sitelib %(%{__ospython} -Esc \"import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))\")\n") + lines.append( + f"\nName: %{{python3_pkgprefix}}-{name}\n" + f"Version: {ver}\n" + "Release: 1%{?dist}\n" + f"Summary: {r['summary'] or pypi}\n" + f"License: {spdx(pypi, r['license'])}\n" + f"URL: {r['url']}\n" + f"Source0: {r['sdist_url']}\n" + ) + if pypi == "bcrypt": + lines.append("Source1: vendor.tar.gz\n") + if not native: + lines.append("BuildArch: noarch\n") + lines.append( + "Vendor: Percona, LLC\n" + "Packager: Percona Development Team <https://jira.percona.com>\n" + "Epoch: 1\n\n" + ) + br = [ + "python%{python3_buildversion}-devel", + "python%{python3_buildversion}-pip", + "python%{python3_buildversion}-setuptools", + "python%{python3_buildversion}-wheel", + ] + if fam == "flit": + br.append("python%{python3_buildversion}-flit-core") + if fam == "poetry": + br.append("%{python3_pkgprefix}-poetry-core") + if fam == "pdm": + br.append("%{python3_pkgprefix}-pdm-backend") + if NEEDS_SCM(r) and pypi not in SELF_HOSTING: + br.append("%{python3_pkgprefix}-setuptools_scm") + br += EXTRA_BR.get(pypi, []) + for b in br: + lines.append(f"BuildRequires: {b}\n") + if fam == "hatchling": + lines.append( + "%if 0%{?rhel} == 8 || 0%{?rhel} == 9\n" + "BuildRequires: %{python3_pkgprefix}-hatchling\n" + "%else\n" + "BuildRequires: python3-hatchling\n" + "%endif\n" + ) + reqs = [] + for req in r["requires"]: + dep = rpm_dep(req) + if dep and dep not in reqs: + reqs.append(dep) + if reqs: + # Runtime deps are also build deps so %check can import the module. + lines.append("# runtime dependencies, also needed by the %check import test\n") + for d in reqs: + lines.append(f"BuildRequires: {d}\n") + lines.append("\n") + for d in reqs: + lines.append(f"Requires: {d}\n") + role = ( + "part of the pgAdmin 4 (percona-pgadmin4) dependency stack" + if r["project"] == "pgadmin" + else "a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages" + ) + lines.append(f"\n%description\n{r['summary'] or pypi}.\n\nBuilt for Python 3.12 from the PyPI sdist; {role}.\n") + # prep + lines.append(f"\n%prep\n%autosetup -p1 -n {r['sdist_top']}") + if pypi == "bcrypt": + lines[-1] += " -a1" + lines.append("\n") + if pypi == "trove-classifiers": + lines.append( + "# Build without calver (not packaged): pin the version literally.\n" + "sed -i 's/\"calver\"//; s/, *\\]/]/' pyproject.toml\n" + "sed -i 's/use_calver=\"[^\"]*\",/version=\"%{version}\",/; /setup_requires=\\[\"calver\"\\],/d' setup.py\n" + ) + # build / install + if fam == "setup.py": + lines.append( + "\n%build\n%{__ospython} setup.py build\n\n" + "%install\n%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES\n" + f"find %{{buildroot}}%{{{sitedir}}} -mindepth 1 -type d | sed \"s|%{{buildroot}}||\" | sed 's/^/%dir /' >> INSTALLED_FILES\n" + ) + else: + env = "" + if pypi in SELF_HOSTING: + env = f"export PYTHONPATH=$PWD/{SELF_HOSTING[pypi]}\n" + if pypi == "bcrypt": + env += "export CARGO_NET_OFFLINE=true\nexport CARGO_HOME=$PWD/.cargo\n" + cs = CONFIG_SETTINGS.get(pypi, "") + lines.append( + f"\n%build\n{env}%{{__ospython}} -m pip wheel --no-deps --no-build-isolation --no-index {cs + ' ' if cs else ''}--wheel-dir dist .\n\n" + "%install\n%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl\n" + ) + # check + mods = MODULES.get(pypi) or [m for m in r["modules"] if not m.startswith("_")] + # Import from the installed buildroot (src-layout packages are not importable + # from the source directory). + lines.append( + f"\n%check\nPYTHONPATH=%{{buildroot}}%{{{sitedir}}} %{{__ospython}} -c \"{'; '.join('import ' + m for m in mods)}\"\n" + ) + # files + if fam == "setup.py": + lines.append("\n%files -f INSTALLED_FILES\n%defattr(-,root,root)\n") + else: + lines.append(f"\n%files\n%{{{sitedir}}}/*\n") + for s in r["scripts"]: + lines.append(f"%{{_bindir}}/{s}\n") + # changelog + what = "pgAdmin 4 dependency stack" if r["project"] == "pgadmin" else "shared Python 3.12 build stack" + if pypi == "dnspython": + what = "bump to 2.8.0 (email-validator needs >= 2.0); built with hatchling" + lines.append( + f"\n%changelog\n* {CHANGELOG_DATE} Percona Development Team <info@percona.com> - {ver}-1\n" + f"- Package {pypi} {ver} for Python 3.12 ({what})\n" + ) + if pypi in OLD_CHANGELOG: + lines.append("\n" + OLD_CHANGELOG[pypi]) + return "".join(lines) + + +def service_text(r: dict) -> str: + s = "<services>\n <service name=\"download_url\">\n" f" <param name=\"url\">{r['sdist_url']}</param>\n </service>\n" + if r["pypi"] == "bcrypt": + s += ( + " <!-- Vendor the Rust crate's dependencies (Cargo.toml lives under src/_bcrypt)\n" + " for an offline OBS build; produces vendor.tar.gz incl. .cargo/config. -->\n" + " <service mode=\"buildtime\" name=\"cargo_vendor\">\n" + f" <param name=\"src\">{r['sdist_filename']}</param>\n" + " <param name=\"cargotoml\">src/_bcrypt/Cargo.toml</param>\n" + " <param name=\"compression\">gz</param>\n" + " <param name=\"update\">false</param>\n" + " </service>\n" + ) + return s + "</services>\n" + + +def package_yaml(r: dict) -> str: + pypi, ver = r["pypi"], r["version"] + who = "pgAdmin 4 (percona-pgadmin4)" if r["project"] == "pgadmin" else "Percona PostgreSQL packages" + text = ( + f"title: {pypi} {ver} for Python 3.12\n" + "description: |\n" + f" {r['summary'] or pypi}. RPM-only python3.12-* build from the PyPI sdist,\n" + f" consumed by {who}. Bump by editing obs/_service (sdist URL) and\n" + " rpm/*.spec (Version + changelog).\n" + ) + if pypi in DISABLE_DISTRO_HATCHLING: + text += "\n# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours.\nbuild:\n" + for f in BUILD_FLAGS_DISTRO: + text += f" {f}: false\n" + return text + + +def main(argv): + repo = Path(argv[1]).resolve() + only = set(argv[argv.index("--only") + 1].split(",")) if "--only" in argv else None + for r in STACK: + if only and r["pypi"] not in only and norm(r["pypi"]) not in only: + continue + name = DIRNAME.get(r["pypi"], norm(r["pypi"])) + pkg = repo / "root" / PROJECT_DIR[r["project"]] / f"python3-{name}" + (pkg / "obs").mkdir(parents=True, exist_ok=True) + (pkg / "rpm").mkdir(parents=True, exist_ok=True) + (pkg / "package.yaml").write_text(package_yaml(r)) + (pkg / "obs" / "_service").write_text(service_text(r)) + (pkg / "rpm" / f"python3-{name}.spec").write_text(spec_text(r)) + print(pkg.relative_to(repo)) + + +if __name__ == "__main__": + main(sys.argv) +``` + +## Appendix B — `stack.json` (data for Appendix A; save next to it as `stack.json`, do not commit) + +```json +[ + { + "pypi": "alembic", + "version": "1.19.1", + "project": "pgadmin", + "summary": "A database migration tool for SQLAlchemy", + "url": "https://alembic.sqlalchemy.org", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/a/alembic/alembic-1.19.1.tar.gz", + "sdist_filename": "alembic-1.19.1.tar.gz", + "sdist_top": "alembic-1.19.1", + "family": "setuptools", + "native": false, + "requires": [ + "SQLAlchemy >=1.4.23", + "Mako", + "typing-extensions >=4.12" + ], + "build_requires": "\"setuptools>=77.0.3\"", + "scripts": [ + "alembic" + ], + "modules": [ + "alembic" + ] + }, + { + "pypi": "Authlib", + "version": "1.6.12", + "project": "pgadmin", + "summary": "The ultimate Python library in building OAuth and OpenID Connect servers and clients", + "url": "https://github.com/authlib/authlib", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/A/Authlib/authlib-1.6.12.tar.gz", + "sdist_filename": "authlib-1.6.12.tar.gz", + "sdist_top": "authlib-1.6.12", + "family": "setuptools", + "native": false, + "requires": [ + "cryptography" + ], + "build_requires": "\"setuptools\", \"wheel\"", + "scripts": [], + "modules": [ + "authlib" + ] + }, + { + "pypi": "babel", + "version": "2.18.0", + "project": "pgadmin", + "summary": "Internationalization utilities", + "url": "https://babel.pocoo.org/", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/babel/babel-2.18.0.tar.gz", + "sdist_filename": "babel-2.18.0.tar.gz", + "sdist_top": "babel-2.18.0", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [ + "pybabel" + ], + "modules": [ + "babel" + ] + }, + { + "pypi": "backports.zstd", + "version": "1.7.0", + "project": "pgadmin", + "summary": "Backport of compression.zstd", + "url": "https://github.com/rogdham/backports.zstd", + "license": "PSF-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/backports.zstd/backports_zstd-1.7.0.tar.gz", + "sdist_filename": "backports_zstd-1.7.0.tar.gz", + "sdist_top": "backports_zstd-1.7.0", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools>=80\"", + "scripts": [], + "modules": [ + "backports" + ] + }, + { + "pypi": "bcrypt", + "version": "5.0.0", + "project": "pgadmin", + "summary": "Modern password hashing for your software and your servers", + "url": "https://pypi.org/project/bcrypt/", + "license": "Apache-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/bcrypt/bcrypt-5.0.0.tar.gz", + "sdist_filename": "bcrypt-5.0.0.tar.gz", + "sdist_top": "bcrypt-5.0.0", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools>=42.0.0\", \"wheel\", \"setuptools-rust>=1.7.0\",", + "scripts": [], + "modules": [ + "bcrypt" + ] + }, + { + "pypi": "bidict", + "version": "0.23.1", + "project": "pgadmin", + "summary": "The bidirectional mapping library for Python", + "url": "https://pypi.org/project/bidict/", + "license": "MPL 2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/bidict/bidict-0.23.1.tar.gz", + "sdist_filename": "bidict-0.23.1.tar.gz", + "sdist_top": "bidict-0.23.1", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools >= 40.9.0\"", + "scripts": [], + "modules": [ + "bidict" + ] + }, + { + "pypi": "blinker", + "version": "1.9.0", + "project": "pgadmin", + "summary": "Fast, simple object-to-object and broadcast signaling", + "url": "https://github.com/pallets-eco/blinker/", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/blinker/blinker-1.9.0.tar.gz", + "sdist_filename": "blinker-1.9.0.tar.gz", + "sdist_top": "blinker-1.9.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "blinker" + ] + }, + { + "pypi": "brotli", + "version": "1.2.0", + "project": "pgadmin", + "summary": "Python bindings for the Brotli compression library", + "url": "https://github.com/google/brotli", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/b/brotli/brotli-1.2.0.tar.gz", + "sdist_filename": "brotli-1.2.0.tar.gz", + "sdist_top": "brotli-1.2.0", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools\", \"pkgconfig\"", + "scripts": [], + "modules": [ + "brotli" + ] + }, + { + "pypi": "decorator", + "version": "5.3.1", + "project": "pgadmin", + "summary": "Decorators for Humans", + "url": "https://pypi.org/project/decorator/", + "license": "BSD-2-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/d/decorator/decorator-5.3.1.tar.gz", + "sdist_filename": "decorator-5.3.1.tar.gz", + "sdist_top": "decorator-5.3.1", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools>=77.0.0\"", + "scripts": [], + "modules": [ + "decorator" + ] + }, + { + "pypi": "email-validator", + "version": "2.3.0", + "project": "pgadmin", + "summary": "A robust email address syntax and deliverability validation library", + "url": "https://github.com/JoshData/python-email-validator", + "license": "Unlicense", + "sdist_url": "https://files.pythonhosted.org/packages/source/e/email-validator/email_validator-2.3.0.tar.gz", + "sdist_filename": "email_validator-2.3.0.tar.gz", + "sdist_top": "email_validator-2.3.0", + "family": "setuptools", + "native": false, + "requires": [ + "dnspython >=2.0.0", + "idna >=2.0.0" + ], + "build_requires": "", + "scripts": [ + "email_validator" + ], + "modules": [ + "email_validator" + ] + }, + { + "pypi": "Flask", + "version": "3.1.3", + "project": "pgadmin", + "summary": "A simple framework for building complex web applications", + "url": "https://github.com/pallets/flask/", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask/flask-3.1.3.tar.gz", + "sdist_filename": "flask-3.1.3.tar.gz", + "sdist_top": "flask-3.1.3", + "family": "flit", + "native": false, + "requires": [ + "blinker >=1.9.0", + "click >=8.1.3", + "itsdangerous >=2.2.0", + "jinja2 >=3.1.2", + "markupsafe >=2.1.1", + "werkzeug >=3.1.0" + ], + "build_requires": "\"flit_core>=3.11,<4\"", + "scripts": [ + "flask" + ], + "modules": [ + "flask" + ] + }, + { + "pypi": "flask-babel", + "version": "4.0.0", + "project": "pgadmin", + "summary": "Adds i18n/l10n support for Flask applications", + "url": "https://github.com/python-babel/flask-babel", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/f/flask-babel/flask_babel-4.0.0.tar.gz", + "sdist_filename": "flask_babel-4.0.0.tar.gz", + "sdist_top": "flask_babel-4.0.0", + "family": "poetry", + "native": false, + "requires": [ + "pytz (>=2022.7)", + "Flask (>=2.0)", + "Babel (>=2.12)", + "Jinja2 (>=3.1)" + ], + "build_requires": "\"poetry-core>=1.0.0\"", + "scripts": [], + "modules": [ + "flask_babel" + ] + }, + { + "pypi": "Flask-Compress", + "version": "1.24", + "project": "pgadmin", + "summary": "Compress responses in your Flask app with gzip, deflate, brotli or zstandard", + "url": "https://github.com/colour-science/flask-compress", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Compress/flask_compress-1.24.tar.gz", + "sdist_filename": "flask_compress-1.24.tar.gz", + "sdist_top": "flask_compress-1.24", + "family": "setuptools", + "native": false, + "requires": [ + "flask", + "brotli", + "backports.zstd" + ], + "build_requires": "\"setuptools>=42\", \"wheel\", \"setuptools_scm[toml", + "scripts": [], + "modules": [ + "flask_compress" + ] + }, + { + "pypi": "Flask-Login", + "version": "0.6.3", + "project": "pgadmin", + "summary": "User authentication and session management for Flask", + "url": "https://github.com/maxcountryman/flask-login", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Login/Flask-Login-0.6.3.tar.gz", + "sdist_filename": "Flask-Login-0.6.3.tar.gz", + "sdist_top": "Flask-Login-0.6.3", + "family": "setup.py", + "native": false, + "requires": [ + "Flask >=1.0.4", + "Werkzeug >=1.0.1" + ], + "build_requires": "", + "scripts": [], + "modules": [ + "flask_login" + ] + }, + { + "pypi": "Flask-Mail", + "version": "0.10.0", + "project": "pgadmin", + "summary": "Flask extension for sending email", + "url": "https://github.com/pallets-eco/flask-mail/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Mail/flask_mail-0.10.0.tar.gz", + "sdist_filename": "flask_mail-0.10.0.tar.gz", + "sdist_top": "flask_mail-0.10.0", + "family": "flit", + "native": false, + "requires": [ + "flask", + "blinker" + ], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "flask_mail" + ] + }, + { + "pypi": "Flask-Migrate", + "version": "4.1.0", + "project": "pgadmin", + "summary": "SQLAlchemy database migrations for Flask applications using Alembic", + "url": "https://github.com/miguelgrinberg/flask-migrate", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Migrate/flask_migrate-4.1.0.tar.gz", + "sdist_filename": "flask_migrate-4.1.0.tar.gz", + "sdist_top": "flask_migrate-4.1.0", + "family": "setuptools", + "native": false, + "requires": [ + "Flask >=0.9", + "Flask-SQLAlchemy >=1.0", + "alembic >=1.9.0" + ], + "build_requires": "\"setuptools>=61.2\",", + "scripts": [], + "modules": [ + "flask_migrate" + ] + }, + { + "pypi": "Flask-Paranoid", + "version": "0.3.0", + "project": "pgadmin", + "summary": "Simple user session protection", + "url": "https://github.com/miguelgrinberg/flask-paranoid", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Paranoid/Flask-Paranoid-0.3.0.tar.gz", + "sdist_filename": "Flask-Paranoid-0.3.0.tar.gz", + "sdist_top": "Flask-Paranoid-0.3.0", + "family": "setuptools", + "native": false, + "requires": [ + "Flask (>=0.10)" + ], + "build_requires": "\"setuptools>=42\", \"wheel\"", + "scripts": [], + "modules": [ + "flask_paranoid" + ] + }, + { + "pypi": "Flask-Principal", + "version": "0.4.0", + "project": "pgadmin", + "summary": "Identity management for flask", + "url": "http://packages.python.org/Flask-Principal/", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Principal/Flask-Principal-0.4.0.tar.gz", + "sdist_filename": "Flask-Principal-0.4.0.tar.gz", + "sdist_top": "Flask-Principal-0.4.0", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [] + }, + { + "pypi": "Flask-Security-Too", + "version": "5.6.2", + "project": "pgadmin", + "summary": "Quickly add security features to your Flask application", + "url": "https://github.com/pallets-eco/flask-security", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Security-Too/flask_security_too-5.6.2.tar.gz", + "sdist_filename": "flask_security_too-5.6.2.tar.gz", + "sdist_top": "flask_security_too-5.6.2", + "family": "flit", + "native": false, + "requires": [ + "Flask >=3.0.0", + "Flask-Login >=0.6.3", + "Flask-Principal >=0.4.0", + "Flask-WTF >=1.1.2", + "email-validator >=2.0.0", + "markupsafe >=2.1.0", + "passlib >=1.7.4", + "setuptools", + "wtforms >=3.0.0", + "importlib_resources >=5.10.0" + ], + "build_requires": "\"flit_core >=3.8,<4\"", + "scripts": [], + "modules": [ + "flask_security" + ] + }, + { + "pypi": "Flask-SocketIO", + "version": "5.5.1", + "project": "pgadmin", + "summary": "Socket.IO integration for Flask applications", + "url": "https://github.com/miguelgrinberg/flask-socketio", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-SocketIO/flask_socketio-5.5.1.tar.gz", + "sdist_filename": "flask_socketio-5.5.1.tar.gz", + "sdist_top": "flask_socketio-5.5.1", + "family": "setuptools", + "native": false, + "requires": [ + "Flask >=0.9", + "python-socketio >=5.12.0" + ], + "build_requires": "\"setuptools>=61.2\",", + "scripts": [], + "modules": [ + "flask_socketio" + ] + }, + { + "pypi": "Flask-SQLAlchemy", + "version": "3.1.1", + "project": "pgadmin", + "summary": "Add SQLAlchemy support to your Flask application", + "url": "https://pypi.org/project/Flask-SQLAlchemy/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-SQLAlchemy/flask_sqlalchemy-3.1.1.tar.gz", + "sdist_filename": "flask_sqlalchemy-3.1.1.tar.gz", + "sdist_top": "flask_sqlalchemy-3.1.1", + "family": "flit", + "native": false, + "requires": [ + "flask >=2.2.5", + "sqlalchemy >=2.0.16" + ], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "flask_sqlalchemy" + ] + }, + { + "pypi": "Flask-WTF", + "version": "1.2.2", + "project": "pgadmin", + "summary": "Form rendering, validation, and CSRF protection for Flask with WTForms", + "url": "https://pypi.org/project/Flask-WTF/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-WTF/flask_wtf-1.2.2.tar.gz", + "sdist_filename": "flask_wtf-1.2.2.tar.gz", + "sdist_top": "flask_wtf-1.2.2", + "family": "hatchling", + "native": false, + "requires": [ + "flask", + "itsdangerous", + "wtforms" + ], + "build_requires": "\"hatchling\"", + "scripts": [], + "modules": [ + "flask_wtf" + ] + }, + { + "pypi": "greenlet", + "version": "3.5.5", + "project": "pgadmin", + "summary": "Lightweight in-process concurrent programming", + "url": "https://greenlet.readthedocs.io", + "license": "MIT AND PSF-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/g/greenlet/greenlet-3.5.5.tar.gz", + "sdist_filename": "greenlet-3.5.5.tar.gz", + "sdist_top": "greenlet-3.5.5", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools >= 77.0.3\"", + "scripts": [], + "modules": [ + "greenlet" + ] + }, + { + "pypi": "gssapi", + "version": "1.10.1", + "project": "pgadmin", + "summary": "Python GSSAPI Wrapper", + "url": "https://github.com/pythongssapi/python-gssapi", + "license": "ISC", + "sdist_url": "https://files.pythonhosted.org/packages/source/g/gssapi/gssapi-1.10.1.tar.gz", + "sdist_filename": "gssapi-1.10.1.tar.gz", + "sdist_top": "gssapi-1.10.1", + "family": "setuptools", + "native": true, + "requires": [ + "decorator" + ], + "build_requires": "\"Cython == 3.1.3\", \"setuptools >= 40.6.0\", # Start of PEP 517 support for setuptools", + "scripts": [], + "modules": [ + "gssapi" + ] + }, + { + "pypi": "h11", + "version": "0.16.0", + "project": "pgadmin", + "summary": "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1", + "url": "https://github.com/python-hyper/h11", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/h/h11/h11-0.16.0.tar.gz", + "sdist_filename": "h11-0.16.0.tar.gz", + "sdist_top": "h11-0.16.0", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "h11" + ] + }, + { + "pypi": "importlib-resources", + "version": "6.5.2", + "project": "pgadmin", + "summary": "Read resources from Python packages", + "url": "https://github.com/python/importlib_resources", + "license": "Apache Software License", + "sdist_url": "https://files.pythonhosted.org/packages/source/i/importlib-resources/importlib_resources-6.5.2.tar.gz", + "sdist_filename": "importlib_resources-6.5.2.tar.gz", + "sdist_top": "importlib_resources-6.5.2", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools>=61.2\", \"setuptools_scm[toml", + "scripts": [], + "modules": [ + "importlib_resources" + ] + }, + { + "pypi": "itsdangerous", + "version": "2.2.0", + "project": "pgadmin", + "summary": "Safely pass data to untrusted environments and back", + "url": "https://github.com/pallets/itsdangerous/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/i/itsdangerous/itsdangerous-2.2.0.tar.gz", + "sdist_filename": "itsdangerous-2.2.0.tar.gz", + "sdist_top": "itsdangerous-2.2.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "itsdangerous" + ] + }, + { + "pypi": "jaraco.classes", + "version": "3.4.0", + "project": "pgadmin", + "summary": "Utility functions for Python class constructs", + "url": "https://github.com/jaraco/jaraco.classes", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/j/jaraco.classes/jaraco.classes-3.4.0.tar.gz", + "sdist_filename": "jaraco.classes-3.4.0.tar.gz", + "sdist_top": "jaraco.classes-3.4.0", + "family": "setuptools", + "native": false, + "requires": [ + "more-itertools" + ], + "build_requires": "\"setuptools>=56\", \"setuptools_scm[toml", + "scripts": [], + "modules": [ + "jaraco" + ] + }, + { + "pypi": "jaraco.context", + "version": "6.0.1", + "project": "pgadmin", + "summary": "Useful decorators and context managers", + "url": "https://github.com/jaraco/jaraco.context", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/j/jaraco.context/jaraco_context-6.0.1.tar.gz", + "sdist_filename": "jaraco_context-6.0.1.tar.gz", + "sdist_top": "jaraco_context-6.0.1", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools>=61.2\", \"setuptools_scm[toml", + "scripts": [], + "modules": [ + "jaraco" + ] + }, + { + "pypi": "jaraco.functools", + "version": "4.1.0", + "project": "pgadmin", + "summary": "Functools like those found in stdlib", + "url": "https://github.com/jaraco/jaraco.functools", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/j/jaraco.functools/jaraco_functools-4.1.0.tar.gz", + "sdist_filename": "jaraco_functools-4.1.0.tar.gz", + "sdist_top": "jaraco_functools-4.1.0", + "family": "setuptools", + "native": false, + "requires": [ + "more-itertools" + ], + "build_requires": "\"setuptools>=61.2\", \"setuptools_scm[toml", + "scripts": [], + "modules": [ + "jaraco" + ] + }, + { + "pypi": "jeepney", + "version": "0.9.0", + "project": "pgadmin", + "summary": "Low-level, pure Python DBus protocol wrapper", + "url": "https://gitlab.com/takluyver/jeepney", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/j/jeepney/jeepney-0.9.0.tar.gz", + "sdist_filename": "jeepney-0.9.0.tar.gz", + "sdist_top": "jeepney-0.9.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core >=3.11,<4\"", + "scripts": [], + "modules": [ + "jeepney" + ] + }, + { + "pypi": "Jinja2", + "version": "3.1.6", + "project": "pgadmin", + "summary": "A very fast and expressive template engine", + "url": "https://github.com/pallets/jinja/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/J/Jinja2/jinja2-3.1.6.tar.gz", + "sdist_filename": "jinja2-3.1.6.tar.gz", + "sdist_top": "jinja2-3.1.6", + "family": "flit", + "native": false, + "requires": [ + "MarkupSafe >=2.0" + ], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "jinja2" + ] + }, + { + "pypi": "jsonformatter", + "version": "0.3.4", + "project": "pgadmin", + "summary": "Python log in json format", + "url": "https://github.com/MyColorfulDays/jsonformatter.git", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/j/jsonformatter/jsonformatter-0.3.4.tar.gz", + "sdist_filename": "jsonformatter-0.3.4.tar.gz", + "sdist_top": "jsonformatter-0.3.4", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools\"", + "scripts": [], + "modules": [] + }, + { + "pypi": "keyring", + "version": "25.2.1", + "project": "pgadmin", + "summary": "Store and access your passwords safely", + "url": "https://github.com/jaraco/keyring", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/k/keyring/keyring-25.2.1.tar.gz", + "sdist_filename": "keyring-25.2.1.tar.gz", + "sdist_top": "keyring-25.2.1", + "family": "setuptools", + "native": false, + "requires": [ + "jaraco.classes", + "jaraco.functools", + "jaraco.context", + "SecretStorage >=3.2", + "jeepney >=0.4.2" + ], + "build_requires": "\"setuptools>=61.2\", \"setuptools_scm[toml", + "scripts": [ + "keyring" + ], + "modules": [ + "keyring" + ] + }, + { + "pypi": "ldap3", + "version": "2.9.1", + "project": "pgadmin", + "summary": "A strictly RFC 4510 conforming LDAP V3 pure Python client library", + "url": "https://github.com/cannatag/ldap3", + "license": "LGPL v3", + "sdist_url": "https://files.pythonhosted.org/packages/source/l/ldap3/ldap3-2.9.1.tar.gz", + "sdist_filename": "ldap3-2.9.1.tar.gz", + "sdist_top": "ldap3-2.9.1", + "family": "setup.py", + "native": false, + "requires": [ + "pyasn1 (>=0.4.6)" + ], + "build_requires": "", + "scripts": [], + "modules": [ + "ldap3" + ] + }, + { + "pypi": "libgravatar", + "version": "1.0.4", + "project": "pgadmin", + "summary": "A library that provides a Python 3 interface for the Gravatar API", + "url": "https://github.com/pabluk/libgravatar", + "license": "GNU General Public License v3 (GPLv3)", + "sdist_url": "https://files.pythonhosted.org/packages/source/l/libgravatar/libgravatar-1.0.4.tar.gz", + "sdist_filename": "libgravatar-1.0.4.tar.gz", + "sdist_top": "libgravatar-1.0.4", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "libgravatar" + ] + }, + { + "pypi": "Mako", + "version": "1.4.1", + "project": "pgadmin", + "summary": "A super-fast templating language that borrows the best ideas from the existing templating languages", + "url": "https://www.makotemplates.org/", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/M/Mako/mako-1.4.1.tar.gz", + "sdist_filename": "mako-1.4.1.tar.gz", + "sdist_top": "mako-1.4.1", + "family": "setuptools", + "native": false, + "requires": [ + "MarkupSafe >=2.0" + ], + "build_requires": "\"setuptools>=77.0.0\"", + "scripts": [ + "mako-render" + ], + "modules": [ + "mako" + ] + }, + { + "pypi": "markdown-it-py", + "version": "4.2.0", + "project": "pgadmin", + "summary": "Python port of markdown-it. Markdown parsing, done right!", + "url": "https://github.com/executablebooks/markdown-it-py", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/m/markdown-it-py/markdown_it_py-4.2.0.tar.gz", + "sdist_filename": "markdown_it_py-4.2.0.tar.gz", + "sdist_top": "markdown_it_py-4.2.0", + "family": "flit", + "native": false, + "requires": [ + "mdurl ~=0.1" + ], + "build_requires": "\"flit_core >=3.4,<4\"", + "scripts": [ + "markdown-it" + ], + "modules": [ + "markdown_it" + ] + }, + { + "pypi": "MarkupSafe", + "version": "3.0.3", + "project": "pgadmin", + "summary": "Safely add untrusted strings to HTML/XML markup", + "url": "https://github.com/pallets/markupsafe/", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/M/MarkupSafe/markupsafe-3.0.3.tar.gz", + "sdist_filename": "markupsafe-3.0.3.tar.gz", + "sdist_top": "markupsafe-3.0.3", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools>=77\"", + "scripts": [], + "modules": [ + "markupsafe" + ] + }, + { + "pypi": "mdurl", + "version": "0.1.2", + "project": "pgadmin", + "summary": "Markdown URL utilities", + "url": "https://github.com/executablebooks/mdurl", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/m/mdurl/mdurl-0.1.2.tar.gz", + "sdist_filename": "mdurl-0.1.2.tar.gz", + "sdist_top": "mdurl-0.1.2", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core>=3.2.0,<4\"", + "scripts": [], + "modules": [ + "mdurl" + ] + }, + { + "pypi": "more-itertools", + "version": "11.1.0", + "project": "pgadmin", + "summary": "More routines for operating on iterables, beyond itertools", + "url": "https://github.com/more-itertools/more-itertools", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/m/more-itertools/more_itertools-11.1.0.tar.gz", + "sdist_filename": "more_itertools-11.1.0.tar.gz", + "sdist_top": "more_itertools-11.1.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core >=3.12,<4\"", + "scripts": [], + "modules": [ + "more_itertools" + ] + }, + { + "pypi": "paramiko", + "version": "3.5.1", + "project": "pgadmin", + "summary": "SSH2 protocol library", + "url": "https://paramiko.org", + "license": "LGPL", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/paramiko/paramiko-3.5.1.tar.gz", + "sdist_filename": "paramiko-3.5.1.tar.gz", + "sdist_top": "paramiko-3.5.1", + "family": "setup.py", + "native": false, + "requires": [ + "bcrypt >=3.2", + "cryptography >=3.3", + "pynacl >=1.5" + ], + "build_requires": "", + "scripts": [], + "modules": [ + "paramiko" + ] + }, + { + "pypi": "passlib", + "version": "1.7.4", + "project": "pgadmin", + "summary": "comprehensive password hashing framework supporting over 30 schemes", + "url": "https://passlib.readthedocs.io", + "license": "BSD", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/passlib/passlib-1.7.4.tar.gz", + "sdist_filename": "passlib-1.7.4.tar.gz", + "sdist_top": "passlib-1.7.4", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "passlib" + ] + }, + { + "pypi": "pillow", + "version": "11.1.0", + "project": "pgadmin", + "summary": "Python Imaging Library (Fork)", + "url": "https://python-pillow.github.io", + "license": "MIT-CMU", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pillow/pillow-11.1.0.tar.gz", + "sdist_filename": "pillow-11.1.0.tar.gz", + "sdist_top": "pillow-11.1.0", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools>=67.8\",", + "scripts": [], + "modules": [ + "PIL" + ] + }, + { + "pypi": "psycopg", + "version": "3.2.10", + "project": "pgadmin", + "summary": "PostgreSQL database adapter for Python", + "url": "https://psycopg.org/", + "license": "GNU Lesser General Public License v3 (LGPLv3)", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/psycopg/psycopg-3.2.10.tar.gz", + "sdist_filename": "psycopg-3.2.10.tar.gz", + "sdist_top": "psycopg-3.2.10", + "family": "setuptools", + "native": false, + "requires": [ + "typing-extensions >=4.6" + ], + "build_requires": "\"setuptools>=49.2.0\", \"wheel>=0.37\"", + "scripts": [], + "modules": [ + "psycopg" + ] + }, + { + "pypi": "psycopg-c", + "version": "3.2.10", + "project": "pgadmin", + "summary": "PostgreSQL database adapter for Python -- C optimisation distribution", + "url": "https://psycopg.org/", + "license": "GNU Lesser General Public License v3 (LGPLv3)", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/psycopg-c/psycopg_c-3.2.10.tar.gz", + "sdist_filename": "psycopg_c-3.2.10.tar.gz", + "sdist_top": "psycopg_c-3.2.10", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "\"setuptools >= 49.2.0\", \"wheel >= 0.37\", \"tomli >= 2.0.1; python_version < '3.11'\",", + "scripts": [], + "modules": [] + }, + { + "pypi": "pyasn1", + "version": "0.6.4", + "project": "pgadmin", + "summary": "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)", + "url": "https://github.com/pyasn1/pyasn1", + "license": "BSD-2-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pyasn1/pyasn1-0.6.4.tar.gz", + "sdist_filename": "pyasn1-0.6.4.tar.gz", + "sdist_top": "pyasn1-0.6.4", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools\"", + "scripts": [], + "modules": [ + "pyasn1" + ] + }, + { + "pypi": "Pygments", + "version": "2.21.0", + "project": "pgadmin", + "summary": "Pygments is a syntax highlighting package written in Python", + "url": "https://pygments.org", + "license": "BSD-2-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/P/Pygments/pygments-2.21.0.tar.gz", + "sdist_filename": "pygments-2.21.0.tar.gz", + "sdist_top": "pygments-2.21.0", + "family": "hatchling", + "native": false, + "requires": [], + "build_requires": "\"hatchling>=1.27\"", + "scripts": [ + "pygmentize" + ], + "modules": [ + "pygments" + ] + }, + { + "pypi": "PyNaCl", + "version": "1.6.2", + "project": "pgadmin", + "summary": "Python binding to the Networking and Cryptography (NaCl) library", + "url": "https://github.com/pyca/pynacl", + "license": "Apache-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/P/PyNaCl/pynacl-1.6.2.tar.gz", + "sdist_filename": "pynacl-1.6.2.tar.gz", + "sdist_top": "pynacl-1.6.2", + "family": "setuptools", + "native": true, + "requires": [ + "cffi >=2.0.0" + ], + "build_requires": "\"setuptools>=61.0.0,!=74.0.0\", \"wheel\", \"cffi>=1.4.1; platform_python_implementation != 'PyPy' and python_version < '3.9'\", \"cffi>=2.0.0; platform_python_implementation != 'PyPy' and python_version >= '3.9'\",", + "scripts": [], + "modules": [ + "nacl" + ] + }, + { + "pypi": "PyOTP", + "version": "2.9.0", + "project": "pgadmin", + "summary": "Python One Time Password Library", + "url": "https://github.com/pyotp/pyotp", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/P/PyOTP/pyotp-2.9.0.tar.gz", + "sdist_filename": "pyotp-2.9.0.tar.gz", + "sdist_top": "pyotp-2.9.0", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "pyotp" + ] + }, + { + "pypi": "python-engineio", + "version": "4.13.5", + "project": "pgadmin", + "summary": "Engine.IO server and client for Python", + "url": "https://github.com/miguelgrinberg/python-engineio", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/python-engineio/python_engineio-4.13.5.tar.gz", + "sdist_filename": "python_engineio-4.13.5.tar.gz", + "sdist_top": "python_engineio-4.13.5", + "family": "setuptools", + "native": false, + "requires": [ + "simple-websocket >=0.10.0" + ], + "build_requires": "\"setuptools>=61.2\"", + "scripts": [], + "modules": [ + "engineio" + ] + }, + { + "pypi": "python-socketio", + "version": "5.16.4", + "project": "pgadmin", + "summary": "Socket.IO server and client for Python", + "url": "https://github.com/miguelgrinberg/python-socketio", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/python-socketio/python_socketio-5.16.4.tar.gz", + "sdist_filename": "python_socketio-5.16.4.tar.gz", + "sdist_top": "python_socketio-5.16.4", + "family": "setuptools", + "native": false, + "requires": [ + "bidict >=0.21.0", + "python-engineio >=4.13.2" + ], + "build_requires": "\"setuptools>=61.2\"", + "scripts": [], + "modules": [ + "socketio" + ] + }, + { + "pypi": "pytz", + "version": "2025.2", + "project": "pgadmin", + "summary": "World timezone definitions, modern and historical", + "url": "http://pythonhosted.org/pytz", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pytz/pytz-2025.2.tar.gz", + "sdist_filename": "pytz-2025.2.tar.gz", + "sdist_top": "pytz-2025.2", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "pytz" + ] + }, + { + "pypi": "qrcode", + "version": "8.2", + "project": "pgadmin", + "summary": "QR Code image generator", + "url": "https://github.com/lincolnloop/python-qrcode", + "license": "BSD", + "sdist_url": "https://files.pythonhosted.org/packages/source/q/qrcode/qrcode-8.2.tar.gz", + "sdist_filename": "qrcode-8.2.tar.gz", + "sdist_top": "qrcode-8.2", + "family": "poetry", + "native": false, + "requires": [], + "build_requires": "\"poetry-core\"", + "scripts": [ + "qr" + ], + "modules": [ + "qrcode" + ] + }, + { + "pypi": "rich", + "version": "15.0.0", + "project": "pgadmin", + "summary": "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal", + "url": "https://github.com/Textualize/rich", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/r/rich/rich-15.0.0.tar.gz", + "sdist_filename": "rich-15.0.0.tar.gz", + "sdist_top": "rich-15.0.0", + "family": "poetry", + "native": false, + "requires": [ + "markdown-it-py >=2.2.0", + "pygments <3.0.0,>=2.13.0" + ], + "build_requires": "\"poetry-core>=1.0.0\"", + "scripts": [], + "modules": [ + "rich" + ] + }, + { + "pypi": "SecretStorage", + "version": "3.5.0", + "project": "pgadmin", + "summary": "Python bindings to FreeDesktop.org Secret Service API", + "url": "https://github.com/mitya57/secretstorage", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/S/SecretStorage/secretstorage-3.5.0.tar.gz", + "sdist_filename": "secretstorage-3.5.0.tar.gz", + "sdist_top": "secretstorage-3.5.0", + "family": "setuptools", + "native": false, + "requires": [ + "cryptography >=2.0", + "jeepney >=0.6" + ], + "build_requires": "\"setuptools>=77.0\"", + "scripts": [], + "modules": [ + "secretstorage" + ] + }, + { + "pypi": "shellingham", + "version": "1.5.4", + "project": "pgadmin", + "summary": "Tool to Detect Surrounding Shell", + "url": "https://github.com/sarugaku/shellingham", + "license": "ISC License", + "sdist_url": "https://files.pythonhosted.org/packages/source/s/shellingham/shellingham-1.5.4.tar.gz", + "sdist_filename": "shellingham-1.5.4.tar.gz", + "sdist_top": "shellingham-1.5.4", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools\", \"wheel\"", + "scripts": [], + "modules": [ + "shellingham" + ] + }, + { + "pypi": "simple-websocket", + "version": "1.1.0", + "project": "pgadmin", + "summary": "Simple WebSocket server and client for Python", + "url": "https://github.com/miguelgrinberg/simple-websocket", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/s/simple-websocket/simple_websocket-1.1.0.tar.gz", + "sdist_filename": "simple_websocket-1.1.0.tar.gz", + "sdist_top": "simple_websocket-1.1.0", + "family": "setuptools", + "native": false, + "requires": [ + "wsproto" + ], + "build_requires": "\"setuptools>=61.2\",", + "scripts": [], + "modules": [ + "simple_websocket" + ] + }, + { + "pypi": "SQLAlchemy", + "version": "2.0.52", + "project": "pgadmin", + "summary": "Database Abstraction Library", + "url": "https://www.sqlalchemy.org", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/S/SQLAlchemy/sqlalchemy-2.0.52.tar.gz", + "sdist_filename": "sqlalchemy-2.0.52.tar.gz", + "sdist_top": "sqlalchemy-2.0.52", + "family": "setuptools", + "native": true, + "requires": [ + "greenlet >=1", + "typing-extensions >=4.6.0" + ], + "build_requires": "\"setuptools>=61.0\", \"cython>=0.29.24; platform_python_implementation == 'CPython'\", # Skip cython when using pypy", + "scripts": [], + "modules": [ + "sqlalchemy" + ] + }, + { + "pypi": "sqlparse", + "version": "0.6.0", + "project": "pgadmin", + "summary": "A non-validating SQL parser", + "url": "https://github.com/andialbrecht/sqlparse", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/s/sqlparse/sqlparse-0.6.0.tar.gz", + "sdist_filename": "sqlparse-0.6.0.tar.gz", + "sdist_top": "sqlparse-0.6.0", + "family": "hatchling", + "native": false, + "requires": [], + "build_requires": "\"hatchling\"", + "scripts": [ + "sqlformat" + ], + "modules": [ + "sqlparse" + ] + }, + { + "pypi": "sshtunnel", + "version": "0.4.0", + "project": "pgadmin", + "summary": "Pure python SSH tunnels", + "url": "https://github.com/pahaz/sshtunnel", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.tar.gz", + "sdist_filename": "sshtunnel-0.4.0.tar.gz", + "sdist_top": "sshtunnel-0.4.0", + "family": "setuptools", + "native": false, + "requires": [ + "paramiko (>=2.7.2)" + ], + "build_requires": "\"setuptools\", \"wheel\"", + "scripts": [ + "sshtunnel" + ], + "modules": [ + "sshtunnel" + ] + }, + { + "pypi": "typer", + "version": "0.19.2", + "project": "pgadmin", + "summary": "Typer, build great CLIs. Easy to code. Based on Python type hints", + "url": "https://github.com/fastapi/typer", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/t/typer/typer-0.19.2.tar.gz", + "sdist_filename": "typer-0.19.2.tar.gz", + "sdist_top": "typer-0.19.2", + "family": "pdm", + "native": false, + "requires": [ + "click >=8.0.0", + "typing-extensions >=3.7.4.3", + "shellingham >=1.3.0", + "rich >=10.11.0" + ], + "build_requires": "\"pdm-backend\",", + "scripts": [ + "typer" + ], + "modules": [ + "typer" + ] + }, + { + "pypi": "typing-extensions", + "version": "4.16.0", + "project": "pgadmin", + "summary": "Backported and Experimental Type Hints for Python 3.9+", + "url": "https://pypi.org/project/typing-extensions/", + "license": "PSF-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/t/typing-extensions/typing_extensions-4.16.0.tar.gz", + "sdist_filename": "typing_extensions-4.16.0.tar.gz", + "sdist_top": "typing_extensions-4.16.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core >=3.11,<4\"", + "scripts": [], + "modules": [ + "typing_extensions" + ] + }, + { + "pypi": "ua-parser", + "version": "0.18.0", + "project": "pgadmin", + "summary": "Python port of Browserscope's user agent parser", + "url": "https://github.com/ua-parser/uap-python", + "license": "Apache 2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/u/ua-parser/ua-parser-0.18.0.tar.gz", + "sdist_filename": "ua-parser-0.18.0.tar.gz", + "sdist_top": "ua-parser-0.18.0", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "ua_parser" + ] + }, + { + "pypi": "user-agents", + "version": "2.2.0", + "project": "pgadmin", + "summary": "A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings", + "url": "https://github.com/selwin/python-user-agents", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/u/user-agents/user-agents-2.2.0.tar.gz", + "sdist_filename": "user-agents-2.2.0.tar.gz", + "sdist_top": "user-agents-2.2.0", + "family": "setup.py", + "native": false, + "requires": [ + "ua-parser (>=0.10.0)" + ], + "build_requires": "", + "scripts": [], + "modules": [ + "user_agents" + ] + }, + { + "pypi": "Werkzeug", + "version": "3.1.8", + "project": "pgadmin", + "summary": "The comprehensive WSGI web application library", + "url": "https://github.com/pallets/werkzeug/", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/W/Werkzeug/werkzeug-3.1.8.tar.gz", + "sdist_filename": "werkzeug-3.1.8.tar.gz", + "sdist_top": "werkzeug-3.1.8", + "family": "flit", + "native": false, + "requires": [ + "markupsafe >=2.1.1" + ], + "build_requires": "\"flit_core<4\"", + "scripts": [], + "modules": [ + "werkzeug" + ] + }, + { + "pypi": "wsproto", + "version": "1.3.2", + "project": "pgadmin", + "summary": "Pure-Python WebSocket protocol implementation", + "url": "https://github.com/python-hyper/wsproto/", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/w/wsproto/wsproto-1.3.2.tar.gz", + "sdist_filename": "wsproto-1.3.2.tar.gz", + "sdist_top": "wsproto-1.3.2", + "family": "setuptools", + "native": false, + "requires": [ + "h11 <1,>=0.16.0" + ], + "build_requires": "\"setuptools>=77\"", + "scripts": [], + "modules": [ + "wsproto" + ] + }, + { + "pypi": "WTForms", + "version": "3.2.2", + "project": "pgadmin", + "summary": "Form validation and rendering for Python web development", + "url": "https://pypi.org/project/WTForms/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/W/WTForms/wtforms-3.2.2.tar.gz", + "sdist_filename": "wtforms-3.2.2.tar.gz", + "sdist_top": "wtforms-3.2.2", + "family": "hatchling", + "native": false, + "requires": [ + "markupsafe >=1.1.1" + ], + "build_requires": "\"hatchling\"", + "scripts": [], + "modules": [ + "wtforms" + ] + }, + { + "pypi": "Cython", + "version": "3.1.3", + "project": "pgadmin", + "summary": "The Cython compiler for writing C extensions in the Python language", + "url": "https://cython.org/", + "license": "Apache-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/C/Cython/cython-3.1.3.tar.gz", + "sdist_filename": "cython-3.1.3.tar.gz", + "sdist_top": "cython-3.1.3", + "family": "setup.py", + "native": true, + "requires": [], + "build_requires": "", + "scripts": [ + "cygdb", + "cython", + "cythonize" + ], + "modules": [ + "Cython", + "cython", + "pyximport" + ] + }, + { + "pypi": "poetry-core", + "version": "2.2.1", + "project": "pgadmin", + "summary": "Poetry PEP 517 Build Backend", + "url": "https://github.com/python-poetry/poetry-core", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/poetry-core/poetry_core-2.2.1.tar.gz", + "sdist_filename": "poetry_core-2.2.1.tar.gz", + "sdist_top": "poetry_core-2.2.1", + "family": "poetry", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "poetry" + ] + }, + { + "pypi": "pdm-backend", + "version": "2.4.5", + "project": "pgadmin", + "summary": "The build backend used by PDM that supports latest packaging standards", + "url": "https://github.com/pdm-project/pdm-backend", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pdm-backend/pdm_backend-2.4.5.tar.gz", + "sdist_filename": "pdm_backend-2.4.5.tar.gz", + "sdist_top": "pdm_backend-2.4.5", + "family": "pdm", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "pdm" + ] + }, + { + "pypi": "dnspython", + "version": "2.8.0", + "project": "common", + "summary": "DNS toolkit", + "url": "https://pypi.org/project/dnspython/", + "license": "ISC", + "sdist_url": "https://files.pythonhosted.org/packages/source/d/dnspython/dnspython-2.8.0.tar.gz", + "sdist_filename": "dnspython-2.8.0.tar.gz", + "sdist_top": "dnspython-2.8.0", + "family": "hatchling", + "native": false, + "requires": [], + "build_requires": "\"hatchling>=1.21.0\"", + "scripts": [], + "modules": [ + "dns" + ] + }, + { + "pypi": "hatchling", + "version": "1.28.0", + "project": "common", + "summary": "Modern, extensible Python build backend", + "url": "https://hatch.pypa.io/latest/", + "license": "", + "sdist_url": "https://files.pythonhosted.org/packages/source/h/hatchling/hatchling-1.28.0.tar.gz", + "sdist_filename": "hatchling-1.28.0.tar.gz", + "sdist_top": "hatchling-1.28.0", + "family": "setuptools", + "native": false, + "requires": [ + "packaging >=24.2", + "pathspec >=0.10.1", + "pluggy >=1.0.0", + "trove-classifiers" + ], + "build_requires": "", + "scripts": [ + "hatchling" + ], + "modules": [ + "hatchling" + ] + }, + { + "pypi": "pathspec", + "version": "0.12.1", + "project": "common", + "summary": "Utility library for gitignore style pattern matching of file paths", + "url": "https://pypi.org/project/pathspec/", + "license": "Mozilla Public License 2.0 (MPL 2.0)", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pathspec/pathspec-0.12.1.tar.gz", + "sdist_filename": "pathspec-0.12.1.tar.gz", + "sdist_top": "pathspec-0.12.1", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core >=3.2,<4\"", + "scripts": [], + "modules": [ + "pathspec" + ] + }, + { + "pypi": "trove-classifiers", + "version": "2025.9.11.17", + "project": "common", + "summary": "Canonical source for classifiers on PyPI (pypi.org)", + "url": "https://github.com/pypa/trove-classifiers", + "license": "Apache Software License", + "sdist_url": "https://files.pythonhosted.org/packages/source/t/trove-classifiers/trove_classifiers-2025.9.11.17.tar.gz", + "sdist_filename": "trove_classifiers-2025.9.11.17.tar.gz", + "sdist_top": "trove_classifiers-2025.9.11.17", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools\", \"calver\"", + "scripts": [ + "trove-classifiers" + ], + "modules": [ + "trove_classifiers" + ] + }, + { + "pypi": "flit-core", + "version": "3.12.0", + "project": "common", + "summary": "Distribution-building parts of Flit. See flit package for more information", + "url": "https://github.com/pypa/flit", + "license": "BSD-3-Clause", + "sdist_url": "https://files.pythonhosted.org/packages/source/f/flit-core/flit_core-3.12.0.tar.gz", + "sdist_filename": "flit_core-3.12.0.tar.gz", + "sdist_top": "flit_core-3.12.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "flit_core" + ] + }, + { + "pypi": "packaging", + "version": "25.0", + "project": "common", + "summary": "Core utilities for Python packages", + "url": "https://github.com/pypa/packaging", + "license": "Apache Software License", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/packaging/packaging-25.0.tar.gz", + "sdist_filename": "packaging-25.0.tar.gz", + "sdist_top": "packaging-25.0", + "family": "flit", + "native": false, + "requires": [], + "build_requires": "\"flit_core >=3.3\"", + "scripts": [], + "modules": [ + "packaging" + ] + } +] +``` diff --git a/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md.tasks.json b/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md.tasks.json new file mode 100644 index 00000000..1c4653d3 --- /dev/null +++ b/docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md.tasks.json @@ -0,0 +1,57 @@ +{ + "planPath": "docs/superpowers/plans/2026-08-27-pgadmin4-py312-stack.md", + "tasks": [ + { + "id": 1, + "subject": "Task 1: Render 77 pkg dirs; commit batch A (common:deps)", + "status": "completed", + "description": "**Goal:** The render script and data are materialised in the scratchpad, all 77 directories exist in the worktree exactly as the template prescribes, every spec parses, a sample dry-run sync resolves the sources, and the six `ppg:common:deps` packages are committed.\n\n**Files:**\n- Create (scratchpad, not committed): `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/render_stack.py` (plan Appendix A), `.../scratchpad/stack.json` (plan Appendix B), `.../scratchpad/check_render.py` (Task 1 Step 1)\n- Create: `root/ppg/common/deps/python3-{flit-core,packaging,pathspec,trove-classifiers,hatchling}/{package.yaml,obs/_service,rpm/python3-<name>.spec}`\n- Modify: `root/ppg/common/deps/python3-dns/obs/_service`, `root/ppg/common/deps/python3-dns/rpm/python3-dns.spec`; Create: `root/ppg/common/deps/python3-dns/package.yaml`\n- Create (left uncommitted for Tasks 2-3): the 71 `root/ppg/devel/pgadmin/python3-*/` directories\n\n**Acceptance Criteria:**\n- [ ] `find root/ppg/devel/pgadmin -name '*.spec' | wc -l` prints `71`; the 6 common:deps directories exist\n- [ ] `check_render.py root` reports `specs: 77 parsed OK: 77 failures: 0` and `non-SPDX-looking: []`\n- [ ] `sync push --dry-run -P isv-pr ppg:common:deps python3-hatchling` / `python3-trove-classifiers` / `python3-dns` each end with `sync successful (dry run)` and list the spec + sdist upload\n- [ ] `git show --stat HEAD` lists 17 files (15 new + 2 modified)\n- [ ] `git status --short` afterwards shows only the 71 untracked pgadmin directories (plus pre-existing `.profile`, `venv`)\n\n**Verify:** `venv/bin/python /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/check_render.py root` -> `specs: 77 parsed OK: 77 failures: 0`\n\n```json:metadata\n{\"files\": [\"root/ppg/common/deps/python3-flit-core\", \"root/ppg/common/deps/python3-packaging\", \"root/ppg/common/deps/python3-pathspec\", \"root/ppg/common/deps/python3-trove-classifiers\", \"root/ppg/common/deps/python3-hatchling\", \"root/ppg/common/deps/python3-dns\", \"root/ppg/devel/pgadmin\"], \"verifyCommand\": \"venv/bin/python /tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/check_render.py root\", \"acceptanceCriteria\": [\"71 pgadmin specs and 6 common:deps directories rendered\", \"check_render.py: 77 parsed OK, 0 failures, no non-SPDX licences\", \"dry-run sync of hatchling, trove-classifiers, dns succeeds\", \"batch A commit touches 17 files\"], \"modelTier\": \"standard\"}\n```" + }, + { + "id": 2, + "subject": "Task 2: Commit batch B (pgadmin build tools + leaves)", + "status": "completed", + "blockedBy": [ + 1 + ], + "description": "**Goal:** The 36 pgadmin packages that depend on nothing else in the pgadmin project (three build tools + 33 leaves) are committed, after a dry-run proves the `cargo_vendor` path for bcrypt and the plain path for a native leaf.\n\n**Files:**\n- Create: `root/ppg/devel/pgadmin/python3-{cython,poetry-core,pdm-backend}/` and the 33 leaf directories (authlib babel backports-zstd bcrypt bidict blinker brotli decorator email-validator flask-principal greenlet h11 importlib-resources itsdangerous jaraco-context jeepney jsonformatter libgravatar markupsafe mdurl more-itertools passlib pillow psycopg-c pyasn1 pygments pynacl pyotp pytz shellingham sqlparse typing-extensions ua-parser), each `package.yaml`, `obs/_service`, `rpm/python3-<name>.spec`\n\n**Acceptance Criteria:**\n- [ ] `sync push --dry-run -P isv-pr ppg:devel:pgadmin python3-bcrypt` prints `service download_url`, `service cargo_vendor` and a 3-file upload (spec, bcrypt-5.0.0.tar.gz, vendor.tar.gz)\n- [ ] `sync push --dry-run -P isv-pr ppg:devel:pgadmin python3-psycopg-c` prints `~ 2 files` and `sync successful (dry run)`\n- [ ] `git show --stat HEAD | tail -1` reports `108 files changed`\n- [ ] `git status --short | grep -c '^?? root/ppg/devel/pgadmin/'` -> `35`\n\n**Verify:** `git show --stat HEAD | tail -1` -> `108 files changed, ... insertions(+)`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin\"], \"verifyCommand\": \"git show --stat HEAD | tail -1\", \"acceptanceCriteria\": [\"bcrypt dry-run shows download_url + cargo_vendor and 3 files\", \"psycopg-c dry-run succeeds\", \"batch B commit = 108 files\", \"35 pgadmin directories remain untracked\"], \"modelTier\": \"mechanical\"}\n```" + }, + { + "id": 3, + "subject": "Task 3: Commit batch C (pgadmin dependents)", + "status": "completed", + "blockedBy": [ + 2 + ], + "description": "**Goal:** The remaining 35 pgadmin packages, which depend on batch A/B packages, are committed after a dry-run of one package per remaining build family.\n\n**Files:**\n- Create: `root/ppg/devel/pgadmin/python3-{alembic,flask,flask-babel,flask-compress,flask-login,flask-mail,flask-migrate,flask-paranoid,flask-security-too,flask-socketio,flask-sqlalchemy,flask-wtf,gssapi,jaraco-classes,jaraco-functools,jinja2,keyring,ldap3,mako,markdown-it-py,paramiko,psycopg,python-engineio,python-socketio,qrcode,rich,secretstorage,simple-websocket,sqlalchemy,sshtunnel,typer,user-agents,werkzeug,wsproto,wtforms}/`\n\n**Acceptance Criteria:**\n- [ ] Dry-runs of python3-flask (flit), python3-wtforms (hatchling), python3-paramiko (setup.py), python3-typer (pdm), python3-rich (poetry) each end with `sync successful (dry run)` and `~ 2 files`\n- [ ] `git show --stat HEAD | tail -1` reports `105 files changed`\n- [ ] `git status --short | grep -c '^?? root/'` -> `0`\n\n**Verify:** `git status --short | grep -c '^?? root/'` -> `0`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin\"], \"verifyCommand\": \"git status --short | grep -c '^?? root/'\", \"acceptanceCriteria\": [\"five family dry-runs succeed\", \"batch C commit = 105 files\", \"no untracked root/ paths remain\"], \"modelTier\": \"mechanical\"}\n```" + }, + { + "id": 4, + "subject": "Task 4: Document the template and new package sets", + "status": "completed", + "blockedBy": [ + 3 + ], + "description": "**Goal:** `docs/PACKAGING_HOWTO.md` explains how a Python 3.12 pyproject-based package is written here (so the next one follows the same pattern), and `root/README.md` says what `devel/pgadmin/` and `common/deps/` now contain.\n\n**Files:**\n- Modify: `docs/PACKAGING_HOWTO.md` (new section \"Python 3.12 packages (pyproject builds)\" before \"## Adding a Package as an Aggregate in a Subproject\"; one checklist row) - full text in plan Task 4 Step 1\n- Modify: `root/README.md` (the `### devel/pgadmin/` paragraph) - exact text in plan Task 4 Step 2\n\n**Acceptance Criteria:**\n- [ ] `grep -n \"^## Python 3.12 packages\" docs/PACKAGING_HOWTO.md` finds the section before `## Adding a Package as an Aggregate in a Subproject`\n- [ ] The section contains the header block, the pip wheel/pip install recipe, the setup.py legacy recipe, the hatchling `%if 0%{?rhel} == 8 || 0%{?rhel} == 9` conditional, the `%check` line and the reuse policy\n- [ ] `grep -c \"python3-hatchling\\|build-backend stack\" root/README.md` >= 2\n- [ ] `venv/bin/pytest -q` -> `147 passed`; `venv/bin/black percona_obs/` -> unchanged; `venv/bin/pyright` -> `0 errors`\n\n**Verify:** `grep -n \"^## Python 3.12 packages\\|^## Adding a Package as an Aggregate\" docs/PACKAGING_HOWTO.md` -> the Python section's line number is smaller\n\n```json:metadata\n{\"files\": [\"docs/PACKAGING_HOWTO.md\", \"root/README.md\"], \"verifyCommand\": \"grep -n '^## Python 3.12 packages\\\\|^## Adding a Package as an Aggregate' docs/PACKAGING_HOWTO.md\", \"acceptanceCriteria\": [\"how-to section present and positioned before the aggregate section\", \"section covers header, both recipes, hatchling conditional, %check, reuse policy\", \"README mentions the Python stack and the build-backend stack\", \"black/pyright/pytest green\"], \"modelTier\": \"standard\"}\n```" + }, + { + "id": 5, + "subject": "Task 5: Build all 77 in PR #12's OBS project; fix loop", + "status": "completed", + "blockedBy": [ + 4 + ], + "description": "**Goal:** All 77 packages build green in `isv:percona:PR:pr-12` - the six common:deps ones on every repo they build for, the 71 pgadmin ones on UBI_9 x86_64 and aarch64 - with the dnspython cascade (python3-etcd, percona-patroni) also green.\n\n**Files:**\n- Modify (only as build failures dictate): individual `root/**/python3-*/rpm/*.spec` or `obs/_service`\n- Push: `percona pgadmin-sp1` (authorised by the user for PR #12)\n\n**Acceptance Criteria:**\n- [ ] `git push percona pgadmin-sp1` succeeds and an `OBS PR Check` run appears for the pushed head\n- [ ] The run's \"Sync packages to OBS\" job succeeds; its log shows the 6 common:deps and 71 pgadmin `+ package` lines plus dep-promotes of python3-etcd and percona-patroni\n- [ ] `_result` of `isv:percona:PR:pr-12:ppg:devel:pgadmin` shows `code=\"succeeded\"` for all 71 packages on UBI_9 x86_64 and aarch64\n- [ ] `_result` of `isv:percona:PR:pr-12:ppg:common:deps` shows `succeeded` for python3-dns on every RPM repo, for the five backend packages on RockyLinux_8/9 and UBI_8/9 (`disabled` on RockyLinux_10/openSUSE), and for python3-etcd\n- [ ] Every fix made during the loop is a committed spec/_service edit with a one-line rationale\n\n**Verify:** `curl -s \"https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result\" | grep -c 'code=\"succeeded\"'` -> `142`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin\", \"root/ppg/common/deps\"], \"verifyCommand\": \"curl -s 'https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result' | grep -c 'code=\\\"succeeded\\\"'\", \"acceptanceCriteria\": [\"push succeeded and PR check ran\", \"sync job promoted 77 packages + cascade\", \"142 succeeded results in pgadmin project\", \"common:deps packages green where enabled, etcd green\", \"all fixes committed with rationale\"], \"modelTier\": \"frontier\"}\n```" + }, + { + "id": 6, + "subject": "Task 6: Update PR #12 body and the spec's risk table", + "status": "completed", + "blockedBy": [ + 5 + ], + "description": "**Goal:** PR #12's body describes SP3 (what was added, versions policy, the dnspython bump and its cascade, verification results, fixes made during the build loop), and the spec's risk table reflects what actually happened.\n\n**Files:**\n- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` (section 8 rows: mark each risk as observed/not, add any new failure class found)\n- Modify (via `gh pr edit`): PR #12 title and body - exact texts in plan Task 6 Step 2\n\n**Acceptance Criteria:**\n- [ ] PR title is `pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack`\n- [ ] PR body has an `## SP3 - Changes` section and an SP3 block under Verification (dry-runs, OBS results, build-loop fixes, cascade result)\n- [ ] Spec section 8 has no row left saying only \"verified by the first build\"\n- [ ] Spec change committed and pushed with `git push percona pgadmin-sp1`\n\n**Verify:** `gh pr view 12 --repo percona/obs-packaging --json body --jq .body | grep -c \"SP3\"` >= 3\n\n```json:metadata\n{\"files\": [\"docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md\"], \"verifyCommand\": \"gh pr view 12 --repo percona/obs-packaging --json body --jq .body | grep -c SP3\", \"acceptanceCriteria\": [\"PR title updated\", \"PR body has SP3 changes + verification\", \"spec risk table records outcomes\", \"spec commit pushed\"], \"modelTier\": \"standard\"}\n```" + } + ], + "lastUpdated": "2026-08-27T16:14:45Z" +} \ No newline at end of file diff --git a/docs/superpowers/plans/2026-08-28-pgadmin4-package.md b/docs/superpowers/plans/2026-08-28-pgadmin4-package.md new file mode 100644 index 00000000..60096b9b --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-pgadmin4-package.md @@ -0,0 +1,2101 @@ +# pgAdmin 4 Package (SP4) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build `percona-pgadmin4` (pgAdmin 4 9.17, server mode) for UBI-9 in `ppg:devel:pgadmin`, with a `-gunicorn` subpackage that a container image can run directly, a `-httpd` subpackage for hosts, and a `-doc` subpackage — after bringing the SP3 Python 3.12 stack up to the 9.17 dependency closure. + +**Architecture:** The package is built from the upstream git tag `REL-9_17` through the SP1 service chain (`obs_scm` → `npm_lockfile` → `node_modules` → `tar` → `recompress` → `set_version`); the spec runs webpack against the vendored npm tarballs served by `local-npm-registry` (SP2), builds the `pgadmin4` wheel with upstream's `pkg/pip/setup_pip.py`, and installs it into the Python 3.12 site-packages next to the SP3 `python3.12-*` stack. Distribution configuration lives in a shipped `config_distro.py` that also maps `PGADMIN_CONFIG_*` environment variables to settings, so the same package serves the container (gunicorn launcher + env) and hosts (httpd + mod_wsgi, or the systemd unit). + +**Tech Stack:** RPM spec (EL9, `python3.12`), OBS services (`obs_scm`, `npm_lockfile`, `node_modules`, `set_version`), Node.js 20 + npm + webpack, `local-npm-registry`, gunicorn 26, Apache httpd + `python3.12-mod_wsgi`, systemd (`systemd-rpm-macros`, sysusers, tmpfiles), podman for the smoke test. + +**Spec:** `docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` (SP4). The SP3 stack spec `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` governs the `python3-*` package template; Task 1 amends its §4 inventory. + +## Global Constraints + +- Everything is delivered on branch `pgadmin-sp1` in this worktree (`/home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1`) and tested through PR #12 (`percona/obs-packaging`). **Never `git push` without the user's explicit approval for that push.** Never `gh pr create`; never add the `obs-sync` label. PR body edits also need the user's go-ahead. +- Commits: `git commit -s`; no `Co-Authored-By` lines; message style `pgadmin: <what>` / `docs: <what>` as in the branch history. +- OBS: `-P isv` and `-P isv-pr` are PRODUCTION profiles — only `--dry-run` operations locally; builds are observed on `https://build.opensuse.org` (public API `https://api.opensuse.org/public/build/<project>/_result`), never triggered or pushed with the tool. Sync happens via the PR workflow after a push. +- No local RPM builds (`rpmbuild`, `mock`, `osc build`) — validation is OBS-first. Local checks are limited to `rpmspec -P`/`rpmspec -q`, `python3 -m py_compile`, `bash -n`, `patch --dry-run`, and `percona-obs sync push --dry-run`. +- All `python3-*` package specs follow the SP3 template exactly (preamble `__ospython=/usr/bin/python3.12`, `python3_pkgprefix python3.12`, `Epoch: 1`, `pip wheel --no-deps --no-build-isolation --no-index`, `%check` = `PYTHONPATH=… %{__ospython} -P -c "import X"`); Task 1 produces them with the render script in Appendix A — no hand edits to rendered specs except where a step says so. +- Runtime dependencies in `percona-pgadmin4` are spelled as `%{python3_pkgprefix}-<pypi-normalised-name>` for packages built in this repo, and as the verbatim RHEL/UBI names for reused ones (`python3.12-cryptography`, `python3.12-setuptools`, `python3.12-urllib3`, `python3.12-mod_wsgi`). No version floors on reused RHEL packages. +- The pgadmin project must be installable from its own repository: every `ppg:common:deps` package a pgadmin package `Requires` is aggregated into `root/ppg/devel/pgadmin/<pkg>/obs/_aggregate` (`ppg:common:deps` is `publish: false`). +- Authlib stays at 1.6.12 (SP3 ruling; 9.17's `Authlib==1.7.*` pin is not enforced — the spec's `Requires` carries no floor). `joserfc` is not packaged (needs cryptography ≥ 45; RHEL 9 ships 41). `libpass` replaces `passlib` (`Provides/Conflicts %{python3_pkgprefix}-passlib`). `importlib-resources` leaves the stack (no consumer in 9.17). +- Multi-line shell in this worktree: the shell guard refuses compound commands (`for`, `$( )`, `cd … &&`, pipes into loops). Use plain single commands, `python3 -c`, or write a script file and run it. + +**User decisions (already made):** +- "start with sp4"; approach **A** — port the openSUSE `pgadmin4.spec` to EL9/python3.12 (not the PGDG spec, not a from-scratch spec). +- "The main purpose of packaging pgadmin is to afterwards create a container image with pgadmin to run pgadmin" — the package must serve as the payload of a container image: `-gunicorn` subpackage with launcher + `PGADMIN_CONFIG_*` env mapping; httpd integration stays for hosts. +- Docs: openSUSE-style — `-doc` ships the rst sources; the in-app "Online Help" link is patched to the upstream online docs for the running version (no Sphinx build). +- "Move to REL-9_17 now" — the `_service` pins `REL-9_17` (SP1 used `REL-9_9`); the SP3 stack is bumped to 9.17's closure first. +- httpd + mod_wsgi is the primary host deployment; the systemd unit is shipped **disabled**; gunicorn is the container runtime. +- Pushes: "Push once now; ask again before each fix push" (SP3 rule, carried over). + +--- + +## File map + +| Path | Task | Responsibility | +|---|---|---| +| `root/ppg/devel/pgadmin/python3-{flask-security-too,flask-socketio,flask-wtf,gssapi,psycopg,psycopg-c,pytz,typer,cython}/rpm/*.spec` | 1 | Bumped SP3 packages (rendered) | +| `root/ppg/devel/pgadmin/python3-{annotated-doc,certifi,libpass,gunicorn}/{package.yaml,obs/_service,rpm/*.spec}` | 1 | New SP3 packages (rendered) | +| `root/ppg/devel/pgadmin/python3-{passlib,importlib-resources}/` | 1 | Removed | +| `root/ppg/devel/pgadmin/python3-{click,six,dateutil,psutil,dns}/obs/_aggregate` | 1 | Aggregates of `ppg:common:deps` runtime deps | +| `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` | 1 | §4 inventory amended for 9.17 | +| `root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml` | 2 | Package metadata | +| `root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service` | 2 | Source chain (obs_scm REL-9_17 → npm_lockfile → node_modules → tar → recompress → set_version) | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec` | 2 | The spec (base, -gunicorn, -httpd, -doc) | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py` | 2 | Distribution config + `PGADMIN_CONFIG_*` env mapping | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/run_pgadmin.py` | 2 | WSGI entry module for gunicorn | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/gunicorn_config.py` | 2 | gunicorn logging config | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn` | 2 | Launcher script (container entry point) | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service` | 2 | systemd unit (disabled by default) | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.sysusers` | 2 | `pgadmin` system user | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.tmpfiles` | 2 | `/run/pgadmin4` | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-httpd.conf` | 2 | Apache conf.d snippet | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-setup-web` | 2 | Host setup helper (setup-db, SELinux, httpd restart) | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch` | 2 | Online Help → upstream docs URL | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0002-make-cloud-packages-optional.patch` | 2 | openSUSE patch, verbatim | +| `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0003-use-os-makedirs.patch` | 2 | openSUSE patch, verbatim | +| `root/README.md`, `docs/PERCONA_OBS_TOOL.md` | 3 | Tree/tool docs mention the pgadmin project's application package | +| (OBS project `isv:percona:PR:pr-12:ppg:devel:pgadmin`) | 4 | Build loop | +| (podman, UBI-9) | 5 | Container smoke test — user gate | +| `docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` §9, PR #12 body | 6 | Outcomes recorded | + +--- + +### Task 1: SP3 stack changes for the 9.17 closure + +**Goal:** Bring the `python3-*` stack in `root/ppg/devel/pgadmin/` to pgAdmin 9.17's dependency closure — 9 version bumps, 4 new packages, 2 removals, 5 aggregates — with the SP3 spec inventory updated to match. + +**Files:** +- Modify (re-rendered): `root/ppg/devel/pgadmin/python3-flask-security-too/rpm/python3-flask-security-too.spec` (5.5.0 → 5.8.2), `python3-flask-socketio` (5.5.1 → 5.6.1), `python3-flask-wtf` (1.2.2 → 1.3.0), `python3-gssapi` (1.9.0 → 1.11.1), `python3-psycopg` (3.2.x → 3.3.4), `python3-psycopg-c` (3.2.x → 3.3.4), `python3-pytz` (→ 2026.3.post1), `python3-typer` (0.15.x → 0.26.8), `python3-cython` (→ 3.2.4) — each `rpm/<dir>.spec` plus `obs/_service` and `package.yaml` (rendered together; `_service`/`package.yaml` content changes only where the sdist URL/version changed). +- Create (rendered): `root/ppg/devel/pgadmin/python3-annotated-doc/`, `python3-certifi/`, `python3-libpass/`, `python3-gunicorn/` — each with `package.yaml`, `obs/_service`, `rpm/<dir>.spec`. +- Delete: `root/ppg/devel/pgadmin/python3-passlib/`, `root/ppg/devel/pgadmin/python3-importlib-resources/`. +- Create: `root/ppg/devel/pgadmin/python3-click/obs/_aggregate`, `python3-six/obs/_aggregate`, `python3-dateutil/obs/_aggregate`, `python3-psutil/obs/_aggregate`, `python3-dns/obs/_aggregate`. +- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` §4 inventory table rows for the packages above. + +**Acceptance Criteria:** +- [ ] `rpmspec -q --qf '%{NAME}-%{VERSION}\n' <spec>` succeeds for all 13 rendered specs and prints the 9.17 versions (`python3.12-flask-security-too-5.8.2`, `python3.12-flask-socketio-5.6.1`, `python3.12-flask-wtf-1.3.0`, `python3.12-gssapi-1.11.1`, `python3.12-psycopg-3.3.4`, `python3.12-psycopg-c-3.3.4`, `python3.12-pytz-2026.3.post1`, `python3.12-typer-0.26.8`, `python3.12-cython-3.2.4`, `python3.12-annotated-doc-0.0.5`, `python3.12-certifi-2026.6.17`, `python3.12-libpass-1.9.3`, `python3.12-gunicorn-26.2.0`). +- [ ] `python3-libpass.spec` contains `Provides: %{python3_pkgprefix}-passlib = %{version}` and `Conflicts: %{python3_pkgprefix}-passlib < 1.9`; `python3-flask-security-too.spec` requires `%{python3_pkgprefix}-libpass >= 1.9.3` and no longer mentions `passlib` or `importlib-resources`. +- [ ] `python3-typer.spec` requires `%{python3_pkgprefix}-annotated-doc >= 0.0.2` and `%{python3_pkgprefix}-rich >= 13.8.0` and does not require `click` or `typing-extensions` (upstream 0.26 metadata). +- [ ] `python3-psycopg.spec`, `python3-psycopg-c.spec`, `python3-gunicorn.spec` carry the PEP 639 `sed` in `%prep` (`license = {text = …}`). +- [ ] `git grep -l passlib root/ppg/devel/pgadmin` lists only `python3-libpass` and `python3-flask-security-too` (the latter only in a `# ` comment, if any) — no `python3-passlib` directory remains; `python3-importlib-resources` directory is gone. +- [ ] Five `_aggregate` files exist and each names the matching `ppg:common:deps` source package. +- [ ] `venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-gunicorn` and `… python3-libpass` succeed (service chain runs locally; no upload). +- [ ] SP3 spec §4 table reflects the 9.17 versions, the four additions and the two removals. + +**Verify:** `venv/bin/python /tmp/…/scratchpad/sp4-bump/check_bump.py` (written in Step 4) → `OK 13 specs`; `git status --short root/ppg/devel/pgadmin | wc -l` → 13 packages' files changed/added plus 2 deletions plus 5 aggregates. + +**Steps:** + +- [ ] **Step 1: Materialise the render inputs** + +Create a working directory in the session scratchpad (`$SCRATCH` below is `/tmp/claude-1000/-home-rdias-Work-percona-obs-packaging/4a404ac4-d132-42d5-92af-b4a8d5e081ba/scratchpad/sp4-bump`; `mkdir -p` it) and write three files into it, byte-for-byte from the appendices of this plan: + +- `render_stack.py` ← Appendix A +- `stack.json` ← Appendix B (the 13 packages to render; `Authlib` and `joserfc` are deliberately absent) +- `known.json` ← Appendix C (names of the other 66 stack packages, for `Requires` name resolution — without it the renderer drops dependencies on packages outside the rendered set) + +Check: `venv/bin/python -c "import json; print(len(json.load(open('$SCRATCH/stack.json'))), len(json.load(open('$SCRATCH/known.json'))))"` → `13 66`. + +- [ ] **Step 2: Remove the two retired packages first** + +```bash +git rm -r -q root/ppg/devel/pgadmin/python3-passlib root/ppg/devel/pgadmin/python3-importlib-resources +``` + +Rationale: `libpass` installs the `passlib` module and `Provides/Conflicts` the old name; `importlib-resources` has no consumer in the 9.17 closure (Flask-Security-Too 5.8 dropped it). + +- [ ] **Step 3: Render the 13 packages into the tree** + +```bash +venv/bin/python $SCRATCH/render_stack.py /home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1 +``` + +The renderer writes `root/ppg/devel/pgadmin/<dir>/{package.yaml,obs/_service,rpm/<dir>.spec}` for each of the 13 entries, overwriting the 9 existing packages and creating the 4 new ones. Expected output ends with `rendered 13 packages`. Then `git status --short root/ppg/devel/pgadmin` must show: 9 `M` groups (only `rpm/*.spec` and, where the sdist filename changed, `obs/_service`), 4 `??` directories (`python3-annotated-doc`, `python3-certifi`, `python3-libpass`, `python3-gunicorn`), and the 2 `D` groups from Step 2. If any other package directory shows as modified, the renderer was pointed at the wrong data — stop and compare with Appendix B. + +- [ ] **Step 4: Write and run the check script** + +`$SCRATCH/check_bump.py`: + +```python +"""Assert the 13 rendered specs carry the 9.17 versions and the intended deltas.""" +import json, pathlib, re, subprocess, sys + +ROOT = pathlib.Path("/home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1/root/ppg/devel/pgadmin") +S = pathlib.Path(__file__).parent +norm = lambda n: re.sub(r"[._]", "-", n.lower()) +rows = json.load(open(S / "stack.json")) +must = { + "libpass": ["Provides: %{python3_pkgprefix}-passlib = %{version}", + "Conflicts: %{python3_pkgprefix}-passlib < 1.9"], + "Flask-Security-Too": ["Requires: %{python3_pkgprefix}-libpass >= 1.9.3"], + "typer": ["Requires: %{python3_pkgprefix}-annotated-doc >= 0.0.2", + "Requires: %{python3_pkgprefix}-rich >= 13.8.0"], + "psycopg": ["license = {text ="], "psycopg-c": ["license = {text ="], "gunicorn": ["license = {text ="], +} +must_not = { + "Flask-Security-Too": ["passlib >=", "importlib-resources"], + "typer": ["Requires: %{python3_pkgprefix}-click", "typing-extensions"], +} +bad = 0 +for r in rows: + d = f"python3-{norm(r['pypi'])}" + spec = ROOT / d / "rpm" / f"{d}.spec" + p = subprocess.run(["rpmspec", "-q", "--qf", "%{NAME}-%{VERSION}\n", str(spec)], capture_output=True, text=True) + want = f"python3.12-{norm(r['pypi'])}-{r['version']}" + if p.returncode or want not in p.stdout: + bad += 1; print("VERSION/PARSE FAIL", d, p.stdout.strip(), p.stderr.strip()[:200]) + text = spec.read_text() + for line in must.get(r["pypi"], []): + if line not in text: bad += 1; print("MISSING", d, line) + for frag in must_not.get(r["pypi"], []): + if frag in text: bad += 1; print("UNEXPECTED", d, frag) +for gone in ("python3-passlib", "python3-importlib-resources"): + if (ROOT / gone).exists(): bad += 1; print("STILL PRESENT", gone) +print("OK 13 specs" if not bad else f"FAILED {bad}") +sys.exit(1 if bad else 0) +``` + +Run: `venv/bin/python $SCRATCH/check_bump.py` → `OK 13 specs`. + +- [ ] **Step 5: Add the five `ppg:common:deps` aggregates** + +First confirm the source package names: `ls root/ppg/common/deps` must list `python3-click`, `python3-six`, `python3-dateutil`, `python3-psutil`, `python3-dns` (use the exact directory names found if any differ — the `<package>` element must be the common:deps source package name). For each, create `root/ppg/devel/pgadmin/<name>/obs/_aggregate` with this content, substituting the name: + +```xml +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-click</package> + </aggregate> +</aggregatelist> +``` + +A single Python one-liner is the guard-safe way to write all five: + +```bash +venv/bin/python -c " +import pathlib +for n in ['python3-click','python3-six','python3-dateutil','python3-psutil','python3-dns']: + d=pathlib.Path('root/ppg/devel/pgadmin')/n/'obs'; d.mkdir(parents=True, exist_ok=True) + (d/'_aggregate').write_text('<aggregatelist>\n <aggregate project=\"\${OBS_ROOTPRJ}:ppg:common:deps\">\n <package>%s</package>\n </aggregate>\n</aggregatelist>\n' % n) +print('ok')" +``` + +Check: `grep -h '<package>' root/ppg/devel/pgadmin/python3-{click,six,dateutil,psutil,dns}/obs/_aggregate` prints the five names. (Aggregate packages have no `package.yaml` — see `root/ppg/staging/14/etcd/`.) + +- [ ] **Step 6: Amend the SP3 spec inventory** + +In `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` §4 (the inventory table): change the version cells of the nine bumped packages to `5.8.2`, `5.6.1`, `1.3.0`, `1.11.1`, `3.3.4`, `3.3.4`, `2026.3.post1`, `0.26.8`, `3.2.4`; delete the `passlib` and `importlib-resources` rows; add rows for `annotated-doc 0.0.5` (pyproject/pdm-backend → check Appendix B `family`), `certifi 2026.6.17`, `libpass 1.9.3` (hatchling; Provides/Conflicts passlib), `gunicorn 26.2.0` (scripts `gunicorn`, `gunicornc`). Append one paragraph after the table: + +> **9.17 update (SP4, 2026-08-28).** pgAdmin moved from REL-9_9 to REL-9_17; the closure changed as follows: bumps Flask-Security-Too 5.8.2, Flask-SocketIO 5.6.1, Flask-WTF 1.3.0, gssapi 1.11.1 (Cython 3.2.4), psycopg/psycopg-c 3.3.4, pytz 2026.3.post1, typer 0.26.8; additions annotated-doc 0.0.5, certifi 2026.6.17, libpass 1.9.3 (replaces passlib; `Provides/Conflicts python3.12-passlib`), gunicorn 26.2.0 (container runtime, SP4 §3); removals passlib, importlib-resources. Not adopted: Authlib 1.7.x (ruling: stay on 1.6.12), joserfc 1.7.4 (needs cryptography ≥ 45; RHEL 9 ships 41). + +Use exact-string edits (the Edit tool) per row; do not reflow the table. + +- [ ] **Step 7: Dry-run two packages through the sync path** + +```bash +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-gunicorn +venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-libpass +``` + +Expected: each ends with the dry-run summary listing the files that *would* be uploaded (`python3-gunicorn.spec`, `gunicorn-26.2.0.tar.gz`, `_service`, …) and no error. `--dry-run` is mandatory — `isv-pr` is production. + +- [ ] **Step 8: Commit** + +```bash +git add -A root/ppg/devel/pgadmin docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md +git commit -s -m "pgadmin: py3.12 stack for pgAdmin 9.17 + +Bump Flask-Security-Too 5.8.2, Flask-SocketIO 5.6.1, Flask-WTF 1.3.0, +gssapi 1.11.1 (Cython 3.2.4), psycopg/psycopg-c 3.3.4, pytz 2026.3.post1, +typer 0.26.8. Add annotated-doc 0.0.5, certifi 2026.6.17, libpass 1.9.3 +(replaces passlib) and gunicorn 26.2.0 (container runtime). Drop passlib +and importlib-resources. Aggregate the common:deps runtime packages +(click, six, dateutil, psutil, dns) so the project repo is self-contained." +``` + +The push of this commit happens in Task 4 (with approval) — do not push here. + +--- + +### Task 2: The `percona-pgadmin4` package + +**Goal:** Add `root/ppg/devel/pgadmin/percona-pgadmin4/` — spec, service chain, support files and patches — producing `percona-pgadmin4`, `-gunicorn`, `-httpd` and `-doc`, verified as far as local tooling allows (spec parse, script syntax, patch dry-run, sync dry-run). + +**Files:** +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/run_pgadmin.py` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/gunicorn_config.py` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.sysusers` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.tmpfiles` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-httpd.conf` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-setup-web` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0002-make-cloud-packages-optional.patch` +- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0003-use-os-makedirs.patch` + +**Acceptance Criteria:** +- [ ] `rpmspec -P root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec` succeeds with a stub `node_modules.spec.inc` in `--define "_sourcedir …"` and shows four `%package`/`%files` sections (`percona-pgadmin4`, `-gunicorn`, `-httpd`, `-doc`). +- [ ] `python3 -m py_compile` passes for `config_distro.py`, `run_pgadmin.py`, `gunicorn_config.py`; `bash -n` passes for `percona-pgadmin4-gunicorn` and `percona-pgadmin4-setup-web`; `systemd-analyze verify --man=no` of the unit reports nothing about syntax (missing `ExecStart` binary on this machine is acceptable and expected). +- [ ] All three patches apply with `patch -p1 --dry-run` against a fresh `git archive` of `pgadmin4` tag `REL-9_17` (`web/pgadmin/help/__init__.py`, `web/pgadmin/misc/__init__.py`, `web/pgadmin/setup/data_directory.py`). +- [ ] `config_distro.py` executed with `PGADMIN_CONFIG_SERVER_MODE=false PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS=7 PGADMIN_CONFIG_HELP_PATH=/x` yields `SERVER_MODE is False`, `MAX_LOGIN_ATTEMPTS == 7`, `HELP_PATH == '/x'` (unit check in Step 3). +- [ ] `venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin percona-pgadmin4` runs the full service chain locally (obs_scm clone of REL-9_17, npm_lockfile, node_modules download) and ends listing the files it would upload, including `percona-pgadmin4-9.17.tar.gz`, `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc`; the generated `node_modules.spec.inc` is used for the `rpmspec -P` check above instead of the stub. +- [ ] `git status` shows only the new `percona-pgadmin4/` directory; committed. + +**Verify:** `rpmspec -P --define "_sourcedir $SCRATCH/pgadmin-src" root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec | grep -c '^%files'` → `4`; `venv/bin/python $SCRATCH/check_config_distro.py` → `OK`. + +**Steps:** + +- [ ] **Step 1: `package.yaml` and `obs/_service`** + +`root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml`: + +```yaml +title: pgAdmin 4 (server mode) for UBI-9 +description: | + pgAdmin 4 built from the upstream git tag (REL-9_17) with the SP1 npm vendoring + chain (obs_scm -> npm_lockfile -> node_modules) and the Python 3.12 stack of this + project. Subpackages: -gunicorn (launcher + systemd unit; the container runtime), + -httpd (Apache httpd + mod_wsgi integration for hosts), -doc (documentation + sources). Design: docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md +``` + +`root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service`: + +```xml +<services> + <service name="obs_scm"> + <param name="scm">git</param> + <param name="url">https://github.com/pgadmin-org/pgadmin4.git</param> + <param name="revision">REL-9_17</param> + <param name="versionformat">@PARENT_TAG@</param> + <param name="versionrewrite-pattern">REL-(\d+)_(\d+)</param> + <param name="versionrewrite-replacement">\1.\2</param> + <param name="filename">percona-pgadmin4</param> + </service> + <service name="npm_lockfile" mode="manual"> + <param name="archive">percona-pgadmin4*.obscpio</param> + <param name="subdir">web</param> + </service> + <service name="node_modules" mode="manual"> + <param name="cpio">node_modules.obscpio</param> + <param name="output">node_modules.spec.inc</param> + <param name="source-offset">10000</param> + </service> + <service name="tar" mode="buildtime"/> + <service name="recompress" mode="buildtime"> + <param name="file">*.tar</param> + <param name="compression">gz</param> + </service> + <service name="set_version" mode="buildtime"/> +</services> +``` + +This is the SP1-verified chain with only the revision changed (`REL-9_9` → `REL-9_17`). `obs_scm` produces `percona-pgadmin4-9.17.obscpio` + `percona-pgadmin4.obsinfo`; `npm_lockfile` runs `npm install --package-lock-only` in `web/` and emits `package-lock.json`; `node_modules` downloads every tarball into `node_modules.obscpio` and writes `SourceNNNNN:` lines to `node_modules.spec.inc` starting at 10000; at build time `tar`+`recompress` yield `percona-pgadmin4-9.17.tar.gz` and `set_version` rewrites `Version:` to `9.17`. + +- [ ] **Step 2: Support files — configuration and WSGI** + +`rpm/config_distro.py` (installed as `%{python3_sitelib}/pgadmin4/config_distro.py`; pgAdmin imports it after `config.py` and before `config_local.py`/`/etc/pgadmin/config_system.py`): + +```python +# Distribution configuration for percona-pgadmin4 (server mode). +# +# Precedence (lowest to highest): config.py (upstream defaults) < this file < +# config_local.py < /etc/pgadmin/config_system.py. Container images and unit +# files override any setting through the environment: every PGADMIN_CONFIG_<NAME> +# variable becomes the setting <NAME>; the value is parsed as a Python literal +# (numbers, True/False/None, quoted strings, lists, dicts) and kept as a plain +# string when it is not one — the same contract as the upstream container image. +import ast +import os + +SERVER_MODE = True +MINIFY_HTML = False +UPGRADE_CHECK_ENABLED = False +HELP_PATH = '/usr/share/doc/percona-pgadmin4/en_US' +LOG_FILE = '/var/log/pgadmin/pgadmin4.log' +SQLITE_PATH = '/var/lib/pgadmin/pgadmin4.db' +SESSION_DB_PATH = '/var/lib/pgadmin/sessions' +STORAGE_DIR = '/var/lib/pgadmin/storage' +AZURE_CREDENTIAL_CACHE_DIR = '/var/lib/pgadmin/azurecredentialcache' +KERBEROS_CCACHE_DIR = '/var/lib/pgadmin/krbccache' +DEFAULT_BINARY_PATHS = { + "pg": "/usr/pgsql-18/bin", + "pg-13": "/usr/pgsql-13/bin", + "pg-14": "/usr/pgsql-14/bin", + "pg-15": "/usr/pgsql-15/bin", + "pg-16": "/usr/pgsql-16/bin", + "pg-17": "/usr/pgsql-17/bin", + "pg-18": "/usr/pgsql-18/bin", +} + +_PREFIX = 'PGADMIN_CONFIG_' +for _key, _value in os.environ.items(): + if not _key.startswith(_PREFIX) or len(_key) == len(_PREFIX): + continue + _literal = {'true': 'True', 'false': 'False'}.get(_value.strip().lower(), _value) + try: + globals()[_key[len(_PREFIX):]] = ast.literal_eval(_literal) + except (ValueError, SyntaxError): + globals()[_key[len(_PREFIX):]] = _value +del _PREFIX, _key, _value, _literal +``` + +(`del` of `_key`/`_value`/`_literal` is guarded by the loop having run at least once only if any variable exists — `os.environ` always has entries, so the names are always bound; keep the `del` as written.) + +`rpm/run_pgadmin.py` (installed as `%{python3_sitelib}/pgadmin4/run_pgadmin.py`; gunicorn's WSGI target `run_pgadmin:app`): + +```python +# WSGI entry point for gunicorn: `gunicorn … run_pgadmin:app`. +# Mirrors upstream's container run_pgadmin.py; the app module is pgAdmin4.py +# in this directory, which builds the Flask application on import. +import builtins +import os +import sys + +# Set SERVER_MODE explicitly for the builtin check performed by config.py. +builtins.SERVER_MODE = True + +_here = os.path.dirname(os.path.abspath(__file__)) +if _here not in sys.path: + sys.path.insert(0, _here) + +from pgAdmin4 import app # noqa: E402,F401 +``` + +`rpm/gunicorn_config.py` (installed as `%{python3_sitelib}/pgadmin4/gunicorn_config.py`; upstream's container logging config, unchanged in substance): + +```python +# gunicorn configuration for percona-pgadmin4-gunicorn. +# Log to stdout/stderr (container-friendly); the launcher passes bind/workers/TLS +# on the command line so environment variables stay the single knob. +import logging + +logconfig_dict = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'generic': { + 'format': '%(asctime)s [%(process)d] [%(levelname)s] %(message)s', + 'datefmt': '[%Y-%m-%d %H:%M:%S %z]', + 'class': 'logging.Formatter', + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'generic', + 'stream': 'ext://sys.stdout', + }, + 'error_console': { + 'class': 'logging.StreamHandler', + 'formatter': 'generic', + 'stream': 'ext://sys.stderr', + }, + }, + 'loggers': { + 'gunicorn.error': {'level': 'INFO', 'handlers': ['error_console'], 'propagate': False}, + 'gunicorn.access': {'level': 'INFO', 'handlers': ['console'], 'propagate': False}, + }, + 'root': {'level': logging.INFO, 'handlers': ['console']}, +} +``` + +- [ ] **Step 3: Unit-check `config_distro.py`** + +`$SCRATCH/check_config_distro.py`: + +```python +"""config_distro.py must apply PGADMIN_CONFIG_* env overrides with literal parsing.""" +import os, runpy, sys +src = "/home/rdias/Work/percona-obs-packaging/.claude/worktrees/pgadmin-sp1/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py" +os.environ.update({ + "PGADMIN_CONFIG_SERVER_MODE": "false", + "PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS": "7", + "PGADMIN_CONFIG_HELP_PATH": "/x", + "PGADMIN_CONFIG_AUTHENTICATION_SOURCES": "['internal', 'ldap']", + "PGADMIN_CONFIG_": "ignored", +}) +g = runpy.run_path(src) +assert g["SERVER_MODE"] is False, g["SERVER_MODE"] +assert g["MAX_LOGIN_ATTEMPTS"] == 7 and isinstance(g["MAX_LOGIN_ATTEMPTS"], int) +assert g["HELP_PATH"] == "/x" +assert g["AUTHENTICATION_SOURCES"] == ["internal", "ldap"] +assert g["DEFAULT_BINARY_PATHS"]["pg-17"] == "/usr/pgsql-17/bin" +assert "_key" not in g and "_PREFIX" not in g +print("OK") +``` + +Run: `venv/bin/python $SCRATCH/check_config_distro.py` → `OK`. (Write this check before `config_distro.py` if you follow TDD strictly: it fails with `FileNotFoundError` first, then passes.) + +- [ ] **Step 4: Support files — gunicorn launcher, unit, sysusers, tmpfiles** + +`rpm/percona-pgadmin4-gunicorn` (installed as `%{_bindir}/percona-pgadmin4-gunicorn`, mode 0755; the container `CMD`): + +```bash +#!/bin/bash +# percona-pgadmin4-gunicorn — run pgAdmin 4 under gunicorn. +# +# Environment (all optional): +# PGADMIN_LISTEN_ADDRESS bind address (default 127.0.0.1) +# PGADMIN_LISTEN_PORT bind port (default 5050) +# PGADMIN_ENABLE_TLS "true" to serve HTTPS using /certs/server.cert + /certs/server.key +# GUNICORN_ACCESS_LOGFILE access log target (default "-" = stdout) +# GUNICORN_THREADS threads per worker (default 25) +# GUNICORN_LIMIT_REQUEST_LINE (default 8190) +# PGADMIN_CONFIG_<SETTING> any pgAdmin setting, applied by config_distro.py +# PGADMIN_DEFAULT_EMAIL / PGADMIN_DEFAULT_PASSWORD +# create the initial admin user when no configuration +# database exists yet (same names as the upstream image). +# The systemd unit runs this script as user pgadmin; containers run it as their +# unprivileged user with /var/lib/pgadmin and /var/log/pgadmin writable. +set -euo pipefail + +PGADMIN_DIR=/usr/lib/python3.12/site-packages/pgadmin4 +: "${PGADMIN_LISTEN_ADDRESS:=127.0.0.1}" +: "${PGADMIN_LISTEN_PORT:=5050}" +: "${GUNICORN_ACCESS_LOGFILE:=-}" +: "${GUNICORN_THREADS:=25}" +: "${GUNICORN_LIMIT_REQUEST_LINE:=8190}" + +cd "${PGADMIN_DIR}" + +# First start: create the configuration database and the initial user. +if [ ! -e "${PGADMIN_CONFIG_SQLITE_PATH:-/var/lib/pgadmin/pgadmin4.db}" ] && \ + [ -n "${PGADMIN_DEFAULT_EMAIL:-}" ] && [ -n "${PGADMIN_DEFAULT_PASSWORD:-}" ]; then + PGADMIN_SETUP_EMAIL="${PGADMIN_DEFAULT_EMAIL}" \ + PGADMIN_SETUP_PASSWORD="${PGADMIN_DEFAULT_PASSWORD}" \ + /usr/bin/python3.12 setup.py setup-db +fi + +TLS_ARGS=() +if [ "${PGADMIN_ENABLE_TLS:-}" = "true" ]; then + TLS_ARGS=(--certfile /certs/server.cert --keyfile /certs/server.key) +fi + +exec /usr/bin/gunicorn \ + --bind "${PGADMIN_LISTEN_ADDRESS}:${PGADMIN_LISTEN_PORT}" \ + --workers 1 --threads "${GUNICORN_THREADS}" \ + --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE}" \ + --access-logfile "${GUNICORN_ACCESS_LOGFILE}" \ + "${TLS_ARGS[@]}" \ + -c gunicorn_config.py run_pgadmin:app +``` + +Note `setup.py setup-db` is upstream's setup entry point (`web/setup.py`, installed by the wheel under `pgadmin4/`); it honours `PGADMIN_SETUP_EMAIL`/`PGADMIN_SETUP_PASSWORD`. The gunicorn console script is `/usr/bin/gunicorn` (from `python3.12-gunicorn`; SP3 survey `scripts`: `gunicorn`, `gunicornc`). The hard-coded `PGADMIN_DIR` equals `%{python3_sitelib}/pgadmin4` on EL9 python3.12 (`/usr/lib/python3.12/site-packages`) — the spec asserts this in `%install` (Step 6). + +`rpm/percona-pgadmin4.service`: + +```ini +[Unit] +Description=pgAdmin 4 (gunicorn) +Documentation=https://www.pgadmin.org/docs/ +After=network.target + +[Service] +Type=exec +User=pgadmin +Group=pgadmin +RuntimeDirectory=pgadmin4 +Environment=PGADMIN_LISTEN_ADDRESS=127.0.0.1 +Environment=PGADMIN_LISTEN_PORT=5050 +EnvironmentFile=-/etc/sysconfig/percona-pgadmin4 +ExecStart=/usr/bin/percona-pgadmin4-gunicorn +Restart=on-failure +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/pgadmin /var/log/pgadmin +PrivateTmp=true +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target +``` + +`rpm/percona-pgadmin4.sysusers`: + +``` +u pgadmin - "pgAdmin 4" /var/lib/pgadmin /sbin/nologin +``` + +`rpm/percona-pgadmin4.tmpfiles`: + +``` +d /run/pgadmin4 0755 pgadmin pgadmin - +``` + +- [ ] **Step 5: Support files — httpd integration** + +`rpm/percona-pgadmin4-httpd.conf` (installed as `%{_sysconfdir}/httpd/conf.d/percona-pgadmin4.conf`, `%config(noreplace)`): + +```apache +# pgAdmin 4 under Apache httpd + mod_wsgi (python3.12-mod_wsgi). +# URL: http://<host>/pgadmin4/ Run `percona-pgadmin4-setup-web` once after install. +LoadModule wsgi_module modules/mod_wsgi_python3.so + +WSGIDaemonProcess pgadmin user=pgadmin group=pgadmin processes=1 threads=25 \ + python-path=/usr/lib/python3.12/site-packages/pgadmin4 \ + home=/var/lib/pgadmin lang=en_US.UTF-8 locale=en_US.UTF-8 +WSGIScriptAlias /pgadmin4 /usr/lib/python3.12/site-packages/pgadmin4/pgAdmin4.wsgi + +<Directory /usr/lib/python3.12/site-packages/pgadmin4/> + WSGIProcessGroup pgadmin + WSGIApplicationGroup %{GLOBAL} + Require all granted +</Directory> +``` + +(`10-wsgi-python3.conf` from `python3.12-mod_wsgi` already loads the module in `conf.modules.d`; the explicit `LoadModule` here is harmless — httpd ignores a second identical `LoadModule` — and keeps the snippet self-describing. If the OBS build's `%check`/smoke test shows `module wsgi_module is already loaded, skipping` warnings, that is expected.) + +`rpm/percona-pgadmin4-setup-web` (installed as `%{_bindir}/percona-pgadmin4-setup-web`, 0755): + +```bash +#!/bin/bash +# percona-pgadmin4-setup-web — one-time host setup for the httpd (mod_wsgi) deployment. +# +# 1. creates the pgAdmin configuration database and the initial admin user +# (prompts unless PGADMIN_SETUP_EMAIL and PGADMIN_SETUP_PASSWORD are set) +# 2. fixes ownership of /var/lib/pgadmin and /var/log/pgadmin +# 3. on SELinux hosts, allows httpd network access and labels the data directories +# 4. enables and restarts httpd (skip with --no-service or when not booted with systemd) +set -euo pipefail + +NO_SERVICE=0 +for arg in "$@"; do + case "$arg" in + --no-service) NO_SERVICE=1 ;; + -h|--help) sed -n '2,8p' "$0"; exit 0 ;; + *) echo "unknown option: $arg" >&2; exit 2 ;; + esac +done + +if [ "$(id -u)" -ne 0 ]; then + echo "percona-pgadmin4-setup-web must run as root" >&2 + exit 1 +fi + +PGADMIN_DIR=/usr/lib/python3.12/site-packages/pgadmin4 +DATA_DIR=/var/lib/pgadmin +LOG_DIR=/var/log/pgadmin + +echo "Setting up pgAdmin 4 in web mode (httpd + mod_wsgi)..." +mkdir -p "${DATA_DIR}/sessions" "${DATA_DIR}/storage" "${LOG_DIR}" +chown -R pgadmin:pgadmin "${DATA_DIR}" "${LOG_DIR}" + +if [ ! -e "${DATA_DIR}/pgadmin4.db" ]; then + echo "Creating configuration database..." + (cd "${PGADMIN_DIR}" && runuser -u pgadmin -- /usr/bin/python3.12 setup.py setup-db) + chmod 0600 "${DATA_DIR}/pgadmin4.db" +fi + +if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then + echo "Configuring SELinux..." + setsebool -P httpd_can_network_connect 1 || true + setsebool -P httpd_can_network_connect_db 1 || true + if command -v semanage >/dev/null 2>&1; then + semanage fcontext -a -t httpd_var_lib_t "${DATA_DIR}(/.*)?" 2>/dev/null || \ + semanage fcontext -m -t httpd_var_lib_t "${DATA_DIR}(/.*)?" + semanage fcontext -a -t httpd_log_t "${LOG_DIR}(/.*)?" 2>/dev/null || \ + semanage fcontext -m -t httpd_log_t "${LOG_DIR}(/.*)?" + restorecon -R "${DATA_DIR}" "${LOG_DIR}" + else + echo "warning: semanage not found (install policycoreutils-python-utils); skipping file contexts" >&2 + fi +fi + +if [ "${NO_SERVICE}" -eq 0 ] && [ -d /run/systemd/system ]; then + echo "Enabling and restarting httpd..." + systemctl enable --now httpd + systemctl restart httpd +else + echo "Skipping the httpd service step (--no-service or no systemd)." +fi + +echo "pgAdmin 4 is available at http://$(hostname -f 2>/dev/null || hostname)/pgadmin4" +``` + +- [ ] **Step 6: The spec** + +`rpm/percona-pgadmin4.spec`: + +```spec +# Ported from openSUSE's pgadmin4.spec (Factory) to EL9 + python3.12 for UBI-9. +# Bumps: change <param name="revision"> in obs/_service (REL-<major>_<minor>) and +# re-check web/requirements.txt against the python3-* packages of this project. +%global debug_package %{nil} +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") +%global pgadmin_dir %{python3_sitelib}/pgadmin4 +%global pgadmin_user pgadmin +%global pgadmin_data %{_sharedstatedir}/pgadmin +%global pgadmin_log %{_localstatedir}/log/pgadmin +%global pgadmin_etc %{_sysconfdir}/pgadmin + +Name: percona-pgadmin4 +Version: 1.0.0 +Release: 1%{?dist} +Summary: Management tool for PostgreSQL (pgAdmin 4, server mode) +License: PostgreSQL +URL: https://www.pgadmin.org +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> + +Source0: %{name}-%{version}.tar.gz +Source1: config_distro.py +Source2: run_pgadmin.py +Source3: gunicorn_config.py +Source4: percona-pgadmin4-gunicorn +Source5: percona-pgadmin4.service +Source6: percona-pgadmin4.sysusers +Source7: percona-pgadmin4.tmpfiles +Source8: percona-pgadmin4-httpd.conf +Source9: percona-pgadmin4-setup-web +Source20: package-lock.json +# SourceNNNNN: npm tarballs vendored by the node_modules service (offset 10000) +Source100: node_modules.spec.inc +%include %{_sourcedir}/node_modules.spec.inc + +# Help > Online Help opens the upstream documentation for the running version +# (no local Sphinx build; -doc ships the rst sources). +Patch1: 0001-help-menu-online-docs.patch +# openSUSE: do not fail at import time when the cloud SDKs are missing +Patch2: 0002-make-cloud-packages-optional.patch +# openSUSE: create data directories with os.makedirs (parents) instead of os.mkdir +Patch3: 0003-use-os-makedirs.patch + +BuildArch: noarch + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: local-npm-registry +BuildRequires: nodejs >= 20 +BuildRequires: npm +BuildRequires: systemd-rpm-macros +# runtime stack, needed for the %%check import (cloud SDKs — boto3, azure-*, google-* — are +# not packaged; Patch2 makes the cloud deployment module tolerate their absence) +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-flask-babel +BuildRequires: %{python3_pkgprefix}-flask-compress +BuildRequires: %{python3_pkgprefix}-flask-login +BuildRequires: %{python3_pkgprefix}-flask-mail +BuildRequires: %{python3_pkgprefix}-flask-migrate +BuildRequires: %{python3_pkgprefix}-flask-paranoid +BuildRequires: %{python3_pkgprefix}-flask-security-too +BuildRequires: %{python3_pkgprefix}-flask-socketio +BuildRequires: %{python3_pkgprefix}-flask-sqlalchemy +BuildRequires: %{python3_pkgprefix}-flask-wtf +BuildRequires: %{python3_pkgprefix}-wtforms +BuildRequires: %{python3_pkgprefix}-werkzeug +BuildRequires: %{python3_pkgprefix}-sqlalchemy +BuildRequires: %{python3_pkgprefix}-sqlparse +BuildRequires: %{python3_pkgprefix}-psycopg +BuildRequires: %{python3_pkgprefix}-psycopg-c +BuildRequires: %{python3_pkgprefix}-libpass +BuildRequires: %{python3_pkgprefix}-bcrypt +BuildRequires: %{python3_pkgprefix}-authlib +BuildRequires: %{python3_pkgprefix}-pyotp +BuildRequires: %{python3_pkgprefix}-qrcode +BuildRequires: %{python3_pkgprefix}-ldap3 +BuildRequires: %{python3_pkgprefix}-gssapi +BuildRequires: %{python3_pkgprefix}-sshtunnel +BuildRequires: %{python3_pkgprefix}-paramiko +BuildRequires: %{python3_pkgprefix}-keyring +BuildRequires: %{python3_pkgprefix}-typer +BuildRequires: %{python3_pkgprefix}-jsonformatter +BuildRequires: %{python3_pkgprefix}-libgravatar +BuildRequires: %{python3_pkgprefix}-user-agents +BuildRequires: %{python3_pkgprefix}-pytz +BuildRequires: %{python3_pkgprefix}-certifi +BuildRequires: %{python3_pkgprefix}-dateutil +BuildRequires: %{python3_pkgprefix}-psutil +BuildRequires: python%{python3_buildversion}-cryptography +BuildRequires: python%{python3_buildversion}-urllib3 + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-flask-babel +Requires: %{python3_pkgprefix}-flask-compress +Requires: %{python3_pkgprefix}-flask-login +Requires: %{python3_pkgprefix}-flask-mail +Requires: %{python3_pkgprefix}-flask-migrate +Requires: %{python3_pkgprefix}-flask-paranoid +Requires: %{python3_pkgprefix}-flask-security-too +Requires: %{python3_pkgprefix}-flask-socketio +Requires: %{python3_pkgprefix}-flask-sqlalchemy +Requires: %{python3_pkgprefix}-flask-wtf +Requires: %{python3_pkgprefix}-wtforms +Requires: %{python3_pkgprefix}-werkzeug +Requires: %{python3_pkgprefix}-sqlalchemy +Requires: %{python3_pkgprefix}-sqlparse +Requires: %{python3_pkgprefix}-psycopg +Requires: %{python3_pkgprefix}-psycopg-c +Requires: %{python3_pkgprefix}-libpass +Requires: %{python3_pkgprefix}-bcrypt +Requires: %{python3_pkgprefix}-authlib +Requires: %{python3_pkgprefix}-pyotp +Requires: %{python3_pkgprefix}-qrcode +Requires: %{python3_pkgprefix}-ldap3 +Requires: %{python3_pkgprefix}-gssapi +Requires: %{python3_pkgprefix}-sshtunnel +Requires: %{python3_pkgprefix}-paramiko +Requires: %{python3_pkgprefix}-keyring +Requires: %{python3_pkgprefix}-typer +Requires: %{python3_pkgprefix}-jsonformatter +Requires: %{python3_pkgprefix}-libgravatar +Requires: %{python3_pkgprefix}-user-agents +Requires: %{python3_pkgprefix}-pytz +Requires: %{python3_pkgprefix}-certifi +Requires: %{python3_pkgprefix}-dateutil +Requires: %{python3_pkgprefix}-psutil +Requires: python%{python3_buildversion}-cryptography +Requires: python%{python3_buildversion}-setuptools +Requires: python%{python3_buildversion}-urllib3 +Requires(pre): shadow-utils +Provides: pgadmin4 = %{version}-%{release} +Suggests: %{name}-doc + +%description +pgAdmin 4 is the leading open source management tool for PostgreSQL. This +package installs the web application in server mode under +%{pgadmin_dir}, with distribution defaults in config_distro.py (data in +%{pgadmin_data}, logs in %{pgadmin_log}, site overrides in +%{pgadmin_etc}/config_system.py). Any setting can also be overridden with a +PGADMIN_CONFIG_<SETTING> environment variable. Install %{name}-gunicorn to run +it stand-alone (containers, systemd) or %{name}-httpd to serve it from Apache. + +%package gunicorn +Summary: Run pgAdmin 4 stand-alone under gunicorn +Requires: %{name} = %{version}-%{release} +Requires: %{python3_pkgprefix}-gunicorn +%{?systemd_requires} + +%description gunicorn +Launcher script (%{_bindir}/percona-pgadmin4-gunicorn) and systemd unit that +serve pgAdmin 4 with gunicorn. Configured through the environment +(PGADMIN_LISTEN_ADDRESS/PORT, PGADMIN_ENABLE_TLS, PGADMIN_DEFAULT_EMAIL/PASSWORD, +PGADMIN_CONFIG_<SETTING>); this is the runtime used by container images. The +service is not enabled by default. + +%package httpd +Summary: Serve pgAdmin 4 from Apache httpd with mod_wsgi +Requires: %{name} = %{version}-%{release} +Requires: httpd +Requires: python%{python3_buildversion}-mod_wsgi + +%description httpd +Apache configuration (/pgadmin4 via mod_wsgi, one daemon process with 25 +threads running as the pgadmin user) and the percona-pgadmin4-setup-web helper +that creates the configuration database, applies SELinux settings and +restarts httpd. + +%package doc +Summary: Documentation sources for pgAdmin 4 +BuildArch: noarch + +%description doc +The reStructuredText sources of the pgAdmin 4 documentation +(%{_docdir}/%{name}/en_US). The rendered manual for the installed release is +online at https://www.pgadmin.org/docs/pgadmin4/; the application's Help menu +links there. + +%prep +%autosetup -p1 -n %{name}-%{version} + +# The git tag is exported without .git: record the upstream commit the way +# pkg/src/build.sh does, and neutralise the "git:hash" npm script. +awk '/^commit:/ {print $2}' %{_sourcedir}/%{name}.obsinfo > web/commit_hash +sed -i 's/"git:hash": "[^"]*"/"git:hash": "exit 0"/' web/package.json +# Upstream pins Yarn via "packageManager"; npm refuses to run with it set. +sed -i -z 's/,\n *"packageManager": "[^"]*"//' web/package.json +# Executable bits and shebangs on files that end up in site-packages +chmod -x web/pgadmin/misc/cloud/*.py web/pgadmin/misc/cloud/utils/*.py 2>/dev/null || : +sed -i '1{/^#!/d}' web/pgadmin/misc/cloud/*.py web/pgadmin/misc/cloud/utils/*.py 2>/dev/null || : +find web/pgadmin -name '*.py' -perm /111 -exec chmod -x {} + +# Vendored npm dependencies: package-lock.json + tarballs served by local-npm-registry +cp %{SOURCE20} web/package-lock.json + +%build +# Frontend +pushd web +local-npm-registry %{_sourcedir} install --legacy-peer-deps --ignore-scripts --no-audit --no-fund +NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072 npx webpack --config webpack.config.js +rm -rf node_modules +popd + +# Wheel (upstream's pip packaging helper; produces pgadmin4-<ver>-py3-none-any.whl) +pushd pkg/pip +%{__ospython} setup_pip.py bdist_wheel +popd + +%install +%{__ospython} -m pip install --root %{buildroot} --no-deps --no-index --no-warn-script-location \ + pkg/pip/dist/pgadmin4-*.whl + +# The launcher and httpd conf hard-code the site-packages path: assert it. +test "%{python3_sitelib}" = "%{_prefix}/lib/python%{python3_buildversion}/site-packages" + +# distribution config + gunicorn entry points +install -m 0644 %{SOURCE1} %{buildroot}%{pgadmin_dir}/config_distro.py +install -m 0644 %{SOURCE2} %{buildroot}%{pgadmin_dir}/run_pgadmin.py +install -m 0644 %{SOURCE3} %{buildroot}%{pgadmin_dir}/gunicorn_config.py + +# The wheel installs upstream's CLI entry point as pgadmin4-cli; keep it. +# Data, log and configuration directories +install -d -m 0750 %{buildroot}%{pgadmin_data} +install -d -m 0755 %{buildroot}%{pgadmin_data}/storage +install -d -m 0700 %{buildroot}%{pgadmin_data}/sessions +install -d -m 0750 %{buildroot}%{pgadmin_log} +install -d -m 0750 %{buildroot}%{pgadmin_etc} +cat > %{buildroot}%{pgadmin_etc}/config_system.py <<'EOF' +# Site-specific pgAdmin 4 settings (highest precedence; imported after config_distro.py +# and config_local.py). Any setting from config.py may be set here, e.g. +# DEFAULT_SERVER = '0.0.0.0' +# MAX_LOGIN_ATTEMPTS = 3 +EOF + +# users and runtime dirs +install -D -m 0644 %{SOURCE6} %{buildroot}%{_sysusersdir}/%{name}.conf +install -D -m 0644 %{SOURCE7} %{buildroot}%{_tmpfilesdir}/%{name}.conf + +# -gunicorn +install -D -m 0755 %{SOURCE4} %{buildroot}%{_bindir}/percona-pgadmin4-gunicorn +install -D -m 0644 %{SOURCE5} %{buildroot}%{_unitdir}/%{name}.service +install -d -m 0755 %{buildroot}/run/pgadmin4 + +# -httpd +install -D -m 0644 %{SOURCE8} %{buildroot}%{_sysconfdir}/httpd/conf.d/%{name}.conf +install -D -m 0755 %{SOURCE9} %{buildroot}%{_bindir}/percona-pgadmin4-setup-web + +# -doc: rst sources (drop build tooling and editor artefacts) +install -d -m 0755 %{buildroot}%{_docdir}/%{name} +cp -a docs/en_US %{buildroot}%{_docdir}/%{name}/en_US +rm -f %{buildroot}%{_docdir}/%{name}/en_US/Makefile.sphinx \ + %{buildroot}%{_docdir}/%{name}/en_US/conf.py \ + %{buildroot}%{_docdir}/%{name}/en_US/build_code_snippet.py \ + %{buildroot}%{_docdir}/%{name}/en_US/.gitignore +find %{buildroot}%{_docdir}/%{name}/en_US -name '*.excalidraw' -delete +install -m 0644 LICENSE README.md %{buildroot}%{_docdir}/%{name}/ + +# byte-compile with the interpreter that runs the app +%{__ospython} -m compileall -q -s %{buildroot} -p / %{buildroot}%{pgadmin_dir} + +%check +PYTHONPATH=%{buildroot}%{pgadmin_dir} PGADMIN_CONFIG_HELP_PATH=/nonexistent \ + %{__ospython} -P -c "import config, config_distro; assert config.SERVER_MODE is True; assert config.HELP_PATH == '/nonexistent'" + +%pre +%sysusers_create_compat %{SOURCE6} + +%post gunicorn +%systemd_post %{name}.service +%tmpfiles_create %{_tmpfilesdir}/%{name}.conf + +%preun gunicorn +%systemd_preun %{name}.service + +%postun gunicorn +%systemd_postun_with_restart %{name}.service + +%files +%license LICENSE +%doc README.md +%{pgadmin_dir}/ +%{python3_sitelib}/pgadmin4-*.dist-info/ +%{_bindir}/pgadmin4-cli +%{_sysusersdir}/%{name}.conf +%dir %attr(0750,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data} +%dir %attr(0755,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data}/storage +%dir %attr(0700,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data}/sessions +%dir %attr(0750,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_log} +%dir %attr(0750,root,%{pgadmin_user}) %{pgadmin_etc} +%config(noreplace) %attr(0640,root,%{pgadmin_user}) %{pgadmin_etc}/config_system.py + +%files gunicorn +%{_bindir}/percona-pgadmin4-gunicorn +%{_unitdir}/%{name}.service +%{_tmpfilesdir}/%{name}.conf +%dir %attr(0755,%{pgadmin_user},%{pgadmin_user}) /run/pgadmin4 + +%files httpd +%config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf +%{_bindir}/percona-pgadmin4-setup-web + +%files doc +%docdir %{_docdir}/%{name}/en_US +%{_docdir}/%{name}/en_US/ + +%changelog +* Fri Aug 28 2026 Percona Development Team <https://jira.percona.com> - 1.0.0-1 +- Initial percona-pgadmin4 package (pgAdmin 4, server mode) for UBI-9, ported + from openSUSE's pgadmin4.spec: gunicorn and httpd/mod_wsgi runtimes, rst docs. +``` + +Notes the implementer must respect: +- `%{__requires_exclude}` keeps rpm from generating `python3.12dist(...)` requirements (which UBI's stack does not provide) — the same setting the SP3 template uses. +- The `%files` entry `%{_bindir}/pgadmin4-cli` matches the console script declared by `pkg/pip/setup_pip.py` (`entry_points console_scripts pgadmin4-cli = pgadmin4.setup:main`). If the OBS build's `%install` shows a different name in `--no-warn-script-location` output, adjust in the fix loop and record it in the ledger. +- `%license LICENSE` / `README.md` are at the tarball root (upstream repo root). +- `LICENSE`/`README.md` are also copied under `%{_docdir}` for the `-doc` layout; both are listed only in the main package via `%license`/`%doc` — the copies under `%{_docdir}/%{name}/` are picked up by `%doc`'s directory. If rpmbuild reports them unpackaged, replace the `install -m 0644 LICENSE README.md …` line with nothing (they are already `%license`/`%doc`). +- The `%check` imports `config` with `PYTHONPATH` set to the installed tree so `config_distro.py` is exercised; `HELP_PATH` override proves the env mapping runs in situ. + +- [ ] **Step 7: The three patches** + +`rpm/0001-help-menu-online-docs.patch` — replaces the local help URL in the Help menu with the upstream online documentation for the running release: + +```diff +From: Percona Development Team <https://jira.percona.com> +Subject: Help menu: link "Online Help" to the upstream docs for this release + +The package ships the documentation sources only (-doc); the rendered +manual lives at https://www.pgadmin.org/docs/pgadmin4/<release>/. + +--- a/web/pgadmin/help/__init__.py ++++ b/web/pgadmin/help/__init__.py +@@ -34,7 +34,8 @@ + priority=100, + target='pgadmin_help', + icon='fa fa-question', +- url=url_for('help.static', filename='index.html')), ++ url='https://www.pgadmin.org/docs/pgadmin4/%s.%s/' % ( ++ config.APP_RELEASE, config.APP_REVISION)), + + MenuItem(name='mnu_pgadmin_website', + label=gettext('pgAdmin Website'), +``` + +The hunk was generated against REL-9_17 and applies cleanly (no fuzz, no offset); Step 8 re-checks it with `patch -p1 --dry-run`. `import config` already exists in that module (line 14 in REL-9_17); `url_for` stays imported because `HelpModule` uses it elsewhere — do not remove the import. + +`rpm/0002-make-cloud-packages-optional.patch` — openSUSE's patch, verbatim (Appendix D). + +`rpm/0003-use-os-makedirs.patch` — openSUSE's patch, verbatim (Appendix E). + +- [ ] **Step 8: Local verification** + +1. Fresh upstream checkout for patch checks (outside the worktree, in `$SCRATCH/pgadmin-src`): + `git clone --depth 1 --branch REL-9_17 https://github.com/pgadmin-org/pgadmin4.git $SCRATCH/pgadmin-src/pgadmin4` + then, from inside it, `patch -p1 --dry-run < <worktree>/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch` (and 0002, 0003). Expected: `checking file web/pgadmin/help/__init__.py` with no `FAILED`. If `0002` or `0003` report failures (openSUSE wrote them against 8.2/7.4), regenerate the hunk against the 9.17 file keeping the same semantic change and note it in the ledger. +2. Scripts: `bash -n root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn`, `bash -n …/percona-pgadmin4-setup-web`, `python3 -m py_compile …/config_distro.py …/run_pgadmin.py …/gunicorn_config.py`, `systemd-analyze verify --man=no root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service` (expect only "Command … is not executable" style warnings about `/usr/bin/percona-pgadmin4-gunicorn`, nothing about syntax). +3. Config unit check: `venv/bin/python $SCRATCH/check_config_distro.py` → `OK`. +4. Sync dry-run (runs the whole service chain locally — several minutes; it clones REL-9_17, generates the lockfile, downloads ~1500 npm tarballs): + `venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin percona-pgadmin4` + Expected: dry-run summary listing `percona-pgadmin4-9.17.obscpio` (or the `.tar.gz` if the tool runs buildtime services), `percona-pgadmin4.obsinfo`, `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc`, the spec, the nine support files and three patches. The tool caches service output under its cache dir; locate `node_modules.spec.inc` in that output (the dry-run prints the work directory) and copy it to `$SCRATCH/pgadmin-src/node_modules.spec.inc`. +5. Spec parse with the real include: + `rpmspec -P --define "_sourcedir $SCRATCH/pgadmin-src" root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec > $SCRATCH/pgadmin-src/expanded.spec` → exit 0; `grep -c '^%files' $SCRATCH/pgadmin-src/expanded.spec` → `4`; `grep -c '^Source1[0-9][0-9][0-9][0-9]:' $SCRATCH/pgadmin-src/expanded.spec` → a number > 1000 (vendored tarballs). + If the sync dry-run cannot produce the include (e.g. no network), create a stub `$SCRATCH/pgadmin-src/node_modules.spec.inc` containing `Source10000: react-19.0.0.tgz` and run the same `rpmspec -P`; record in the ledger that the include check ran against a stub. + +- [ ] **Step 9: Commit** + +```bash +git add root/ppg/devel/pgadmin/percona-pgadmin4 +git commit -s -m "pgadmin: add percona-pgadmin4 (pgAdmin 4 9.17, server mode) for UBI-9 + +Ported from openSUSE's pgadmin4.spec to EL9/python3.12. Sources come +from the upstream REL-9_17 tag through obs_scm -> npm_lockfile -> +node_modules; webpack runs against local-npm-registry. Subpackages: +-gunicorn (launcher + systemd unit, PGADMIN_CONFIG_* env mapping; the +container runtime), -httpd (mod_wsgi conf + percona-pgadmin4-setup-web), +-doc (rst sources; Help menu links to the online manual)." +``` + +No push in this task. + +--- + +### Task 3: Documentation + +**Goal:** Record the pgadmin project's application package in the tree/tool docs so the next person finds it where the other projects are described. + +**Files:** +- Modify: `root/README.md` — the `ppg/devel/pgadmin` entry (or the devel-tier section that lists project purposes). +- Modify: `docs/PERCONA_OBS_TOOL.md` — the section that mentions `npm_lockfile`/`node_modules` (added in SP1) gets one paragraph on how `percona-pgadmin4` uses the chain. + +**Acceptance Criteria:** +- [ ] `grep -n percona-pgadmin4 root/README.md docs/PERCONA_OBS_TOOL.md` shows at least one hit in each file. +- [ ] `root/README.md` describes `ppg/devel/pgadmin` as: python3.12 stack (`python3-*`), aggregates of `ppg:common:deps` runtime packages, and `percona-pgadmin4` (+ `-gunicorn`/`-httpd`/`-doc`), built for UBI_9 only. +- [ ] No other sections are reflowed or reworded (diff limited to the additions). + +**Verify:** `git diff --stat HEAD~1` after the commit → exactly the two files. + +**Steps:** + +- [ ] **Step 1: `root/README.md`** + +Find the line that introduces `ppg/devel/pgadmin` (`grep -n pgadmin root/README.md`). Directly after that entry's existing description, add: + +```markdown + `percona-pgadmin4` is the application package (pgAdmin 4 in server mode, built from the + upstream git tag through `obs_scm` → `npm_lockfile` → `node_modules`, with webpack served by + `local-npm-registry`). It ships `-gunicorn` (launcher + systemd unit; the runtime a container + image uses), `-httpd` (Apache + `python3.12-mod_wsgi`) and `-doc` (rst sources). The + `python3-*` packages are its Python 3.12 dependency stack; `python3-click`, `python3-six`, + `python3-dateutil`, `python3-psutil` and `python3-dns` are `_aggregate`s of `ppg:common:deps` + so the project repository installs on its own. UBI_9 only. +``` + +Match the indentation of the surrounding list item. If `root/README.md` has no `pgadmin` entry yet, add one under the devel-tier listing following the format of the neighbouring entries (project path, one-line purpose, then the paragraph above). + +- [ ] **Step 2: `docs/PERCONA_OBS_TOOL.md`** + +Find the `npm_lockfile` description (`grep -n npm_lockfile docs/PERCONA_OBS_TOOL.md`). After the paragraph that explains the chain, add: + +```markdown +`root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service` is the reference user of this chain: +`obs_scm` (tag `REL-9_17`, `versionrewrite-pattern REL-(\d+)_(\d+)` → `9.17`) → +`npm_lockfile` (`subdir web`) → `node_modules` (`source-offset 10000`) → `tar`/`recompress`/ +`set_version` at build time. A `sync push --dry-run` of that package runs the whole chain +locally and takes several minutes (it downloads every npm tarball once; later runs hit the +service cache). +``` + +- [ ] **Step 3: Commit** + +```bash +git add root/README.md docs/PERCONA_OBS_TOOL.md +git commit -s -m "docs: describe percona-pgadmin4 and the pgadmin project layout" +``` + +--- + +### Task 4: OBS build loop via PR #12 + +**Goal:** Get every package in `isv:percona:PR:pr-12:ppg:devel:pgadmin` (UBI_9) to `succeeded` after pushing Tasks 1–3: first the stack commit, then the application package — fixing build failures in rounds, each push approved by the user. + +**Files:** +- Modify (as failures dictate): files under `root/ppg/devel/pgadmin/` only. + +**Acceptance Criteria:** +- [ ] Push 1 (after user approval): branch `pgadmin-sp1` pushed to `origin` with Tasks 1–3 commits; PR #12 CI sync run succeeds (`gh run list --branch pgadmin-sp1 --limit 3` shows the `obs-sync`/PR workflow `completed success`). +- [ ] `https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result?repository=UBI_9` shows every `python3-*` package `succeeded` (the 13 rendered ones included), the five aggregates present, and `percona-pgadmin4` `succeeded`, with `code="published"` for the repository. +- [ ] Every fix round is one commit per root cause with the OBS log excerpt in its message body; each push asked for and approved individually. +- [ ] `_result` polled with the `Monitor`/background-script pattern (no foreground sleeps) — the controller, not the implementer, waits. + +**Verify:** `curl -s 'https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result?repository=UBI_9&arch=x86_64' | grep -c 'code="succeeded"'` → equals the number of source packages (`ls root/ppg/devel/pgadmin | wc -l` minus the 5 aggregates, which report `code="succeeded"` too once published — accept either count as long as no `failed`/`unresolvable`/`broken` appears: `grep -cE 'code="(failed|unresolvable|broken)"'` → `0`). + +**Steps:** + +- [ ] **Step 1: Ask for push 1** + +Controller action (not a subagent): ask the user "Tasks 1–3 are committed (stack bump for 9.17, percona-pgadmin4, docs). Push `pgadmin-sp1` to origin so PR #12 syncs them?" Only on a yes: + +```bash +git push origin pgadmin-sp1 +``` + +Record the push SHA in the ledger. Nothing else is pushed without a fresh yes. + +- [ ] **Step 2: Wait for the sync** + +```bash +gh run list --branch pgadmin-sp1 --limit 3 +``` + +Wait for the PR workflow's sync job to complete (`Monitor` on `gh run watch <id> --exit-status`, or a background `until` script checking `gh run view <id> --json status -q .status` every 60 s). On `failure`, read the log (`gh run view <id> --log-failed`) — a `sync` failure is a tooling problem, not a packaging one: fix the tree (e.g. `_service` XML error), commit, and ask before pushing again. + +- [ ] **Step 3: Watch the build** + +Poll `https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result?repository=UBI_9&arch=x86_64` every 5 minutes with a background script (`$SCRATCH/watch_obs.sh` pattern from SP3: `until` loop writing the summary counts to a file; the controller reads the file). The build of `percona-pgadmin4` alone takes 20–40 minutes (webpack); the whole project after a stack push can take 1–3 hours on the x86_64 scheduler. States: `scheduled`/`building`/`blocked` → keep waiting; `unresolvable` → dependency naming issue (check `_result` `details`); `failed` → read the log: + +```bash +curl -s 'https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/UBI_9/x86_64/<package>/_log' | tail -n 200 +``` + +- [ ] **Step 4: Fix rounds** + +For each failed package, diagnose from the log and fix the root cause in the tree. Expected failure classes and their fixes: + +| Symptom in log | Fix | +|---|---| +| `nothing provides python3.12-<x>` for a stack package | the dependency is missing from the project — add its render or fix the `Requires` name (`%{python3_pkgprefix}-<pypi-normalised>`); for a `ppg:common:deps` package add an `_aggregate` | +| `npm ERR! 404 … local-npm-registry` / `ETARGET` | the lockfile and the vendored tarballs disagree: re-run the sync dry-run to regenerate `package-lock.json`/`node_modules.obscpio` (they are service outputs; a re-sync refreshes them) — check `npm_lockfile` flags; if npm needs `--legacy-peer-deps` to resolve, add `<param name="npm-flags">--legacy-peer-deps</param>` to `npm_lockfile` | +| webpack `JavaScript heap out of memory` | raise `NODE_OPTIONS=--max-old-space-size=4096` in `%build` | +| `error: Installed (but unpackaged) file(s)` | add the listed paths to the right `%files` section (typical: `%{_bindir}/pgadmin4-cli` name, `%{_docdir}/%{name}/LICENSE`) | +| `%check` `ModuleNotFoundError: <mod>` | a runtime dependency is missing from `BuildRequires`/`Requires` — add both | +| `ImportError` from `passlib` | `python3.12-libpass` not installed/Provides missing — check Task 1 output | +| `setup_pip.py: error: invalid command 'bdist_wheel'` | `python3.12-wheel` missing from BuildRequires (it is listed — check the log for the real cause) | +| gssapi/psycopg-c `Cython` version errors | Task 1 pinned Cython 3.2.4; confirm `python3-cython` built before them (`blocked` → wait) | + +Commit each fix as `pgadmin: fix <package> — <cause>` with the log excerpt in the body. Ask the user before every push: "Fix round N (<packages>): push?" Then repeat Steps 2–3. If a fix is confined to one package that has no dependants building, say so when asking (a single-package sync does not cancel the others' builds). + +- [ ] **Step 5: Record the outcome in the ledger** + +Append to the ledger: the final `_result` summary (counts by state), the list of fix commits, and the published repo URL `https://download.opensuse.org/repositories/isv:/percona:/PR:/pr-12:/ppg:/devel:/pgadmin/UBI_9/`. + +--- + +### Task 5: Container smoke test (podman, UBI-9) + +**Goal:** Prove the published PR #12 repository installs and runs pgAdmin 4 the way the future container image will: `percona-pgadmin4` + `-gunicorn` installed into a UBI-9 container, first start creates the admin user from the environment, `GET /login` returns HTTP 200 and the page identifies as pgAdmin 4 9.17; and `-httpd` installs cleanly with `httpd -t` accepting the shipped configuration. + +**USER-ORDERED GATE — NON-SKIPPABLE.** This task was requested by the user in the current conversation. It MUST NOT be closed by walking around it, by declaring it "verified inline", or by substituting a cheaper check. Close only after every item in `acceptanceCriteria` has been re-validated independently, with output captured. + +**Files:** +- Create: `$SCRATCH/smoke/pr12.repo`, `$SCRATCH/smoke/smoke-gunicorn.sh`, `$SCRATCH/smoke/smoke-httpd.sh` (scratch only — nothing in the repo unless a packaging fix is needed). +- Modify (only if the test finds a packaging defect): files under `root/ppg/devel/pgadmin/percona-pgadmin4/`. + +**Acceptance Criteria:** +- [ ] `smoke-gunicorn.sh` output contains `INSTALL OK`, `SETUP OK` (the `pgadmin4.db` exists after first start), `HTTP 200` for `http://127.0.0.1:8080/login`, and `VERSION OK 9.17` (the login page HTML contains `9.17`). +- [ ] `smoke-gunicorn.sh` also shows the env mapping works: with `PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS=7` set, `python3.12 -c` inside the container prints `7` from `config.MAX_LOGIN_ATTEMPTS`. +- [ ] `smoke-httpd.sh` output contains `INSTALL OK` and `Syntax OK` from `httpd -t` with `/etc/httpd/conf.d/percona-pgadmin4.conf` present and `mod_wsgi_python3.so` loaded (no `Cannot load` errors). +- [ ] Captured outputs saved to `$SCRATCH/smoke/gunicorn.log` and `$SCRATCH/smoke/httpd.log`; the ledger cites both paths and quotes the four OK tokens. + +**Verify:** `bash $SCRATCH/smoke/smoke-gunicorn.sh 2>&1 | tee $SCRATCH/smoke/gunicorn.log | grep -E 'INSTALL OK|SETUP OK|HTTP 200|VERSION OK|ENV OK 7'` → five lines; `bash $SCRATCH/smoke/smoke-httpd.sh 2>&1 | tee $SCRATCH/smoke/httpd.log | grep -E 'INSTALL OK|Syntax OK'` → two lines. + +**Steps:** + +- [ ] **Step 1: Repository file** + +`$SCRATCH/smoke/pr12.repo` (the PR project's published UBI_9 repo; GPG checking is off because the PR project signs with the OBS project key that the container does not trust): + +```ini +[pr12-pgadmin] +name=isv:percona:PR:pr-12:ppg:devel:pgadmin (UBI_9) +baseurl=https://download.opensuse.org/repositories/isv:/percona:/PR:/pr-12:/ppg:/devel:/pgadmin/UBI_9/ +enabled=1 +gpgcheck=0 +``` + +- [ ] **Step 2: gunicorn smoke script** + +`$SCRATCH/smoke/smoke-gunicorn.sh`: + +```bash +#!/bin/bash +# Install percona-pgadmin4 + -gunicorn in a UBI-9 container from the PR #12 repo, +# start it as the upstream image would, and check the login page. +set -u +HERE=$(dirname "$(readlink -f "$0")") +podman run --rm -v "$HERE/pr12.repo:/etc/yum.repos.d/pr12.repo:ro,z" \ + -e PGADMIN_DEFAULT_EMAIL=admin@example.com -e PGADMIN_DEFAULT_PASSWORD='Sm0keTest!pw' \ + -e PGADMIN_LISTEN_ADDRESS=127.0.0.1 -e PGADMIN_LISTEN_PORT=8080 \ + -e PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS=7 \ + registry.access.redhat.com/ubi9/ubi:latest bash -c ' +set -u +dnf -y -q install percona-pgadmin4 percona-pgadmin4-gunicorn curl >/tmp/dnf.log 2>&1 \ + && echo "INSTALL OK" || { echo "INSTALL FAILED"; tail -n 40 /tmp/dnf.log; exit 1; } +rpm -q percona-pgadmin4 percona-pgadmin4-gunicorn python3.12-gunicorn python3.12-libpass +chown -R pgadmin:pgadmin /var/lib/pgadmin /var/log/pgadmin +cd /usr/lib/python3.12/site-packages/pgadmin4 && PYTHONPATH=$PWD python3.12 -P -c "import config; print(\"ENV OK\", config.MAX_LOGIN_ATTEMPTS)" +runuser -u pgadmin -- env PGADMIN_DEFAULT_EMAIL PGADMIN_DEFAULT_PASSWORD PGADMIN_LISTEN_ADDRESS PGADMIN_LISTEN_PORT \ + /usr/bin/percona-pgadmin4-gunicorn >/tmp/gunicorn.out 2>&1 & +for i in $(seq 1 60); do + curl -s -o /tmp/login.html -w "%{http_code}" http://127.0.0.1:8080/login >/tmp/code 2>/dev/null && [ "$(cat /tmp/code)" = "200" ] && break + sleep 2 +done +echo "HTTP $(cat /tmp/code 2>/dev/null || echo none)" +test -e /var/lib/pgadmin/pgadmin4.db && echo "SETUP OK" || echo "SETUP FAILED" +grep -q "9\.17" /tmp/login.html && echo "VERSION OK 9.17" || { echo "VERSION CHECK FAILED"; head -c 600 /tmp/login.html; } +echo "--- gunicorn output (tail) ---"; tail -n 30 /tmp/gunicorn.out +' +``` + +Run it: `bash $SCRATCH/smoke/smoke-gunicorn.sh 2>&1 | tee $SCRATCH/smoke/gunicorn.log`. Pulls `ubi9/ubi` on first use (public registry, no login). The `runuser … env VAR` form passes only the listed variables; `PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS` is checked separately with the `python3.12 -P -c` line (it prints `ENV OK 7`). + +- [ ] **Step 3: httpd smoke script** + +`$SCRATCH/smoke/smoke-httpd.sh`: + +```bash +#!/bin/bash +# Install percona-pgadmin4-httpd in UBI-9 and check the Apache configuration parses +# with mod_wsgi loaded (no systemd in the container: use httpd -t). +set -u +HERE=$(dirname "$(readlink -f "$0")") +podman run --rm -v "$HERE/pr12.repo:/etc/yum.repos.d/pr12.repo:ro,z" \ + registry.access.redhat.com/ubi9/ubi:latest bash -c ' +set -u +dnf -y -q install percona-pgadmin4 percona-pgadmin4-httpd >/tmp/dnf.log 2>&1 \ + && echo "INSTALL OK" || { echo "INSTALL FAILED"; tail -n 40 /tmp/dnf.log; exit 1; } +rpm -q httpd python3.12-mod_wsgi percona-pgadmin4-httpd +test -f /etc/httpd/conf.d/percona-pgadmin4.conf && echo "CONF PRESENT" +httpd -t 2>&1 +httpd -M 2>/dev/null | grep -i wsgi || echo "wsgi module NOT listed" +PGADMIN_SETUP_EMAIL=admin@example.com PGADMIN_SETUP_PASSWORD="Sm0keTest!pw" /usr/bin/percona-pgadmin4-setup-web --no-service 2>&1 | tail -n 5 +test -e /var/lib/pgadmin/pgadmin4.db && echo "SETUP-WEB OK" +' +``` + +Run it: `bash $SCRATCH/smoke/smoke-httpd.sh 2>&1 | tee $SCRATCH/smoke/httpd.log`. Expected `Syntax OK`, `wsgi_module (shared)`, `SETUP-WEB OK`. (`percona-pgadmin4-setup-web` runs as root here; SELinux is not enforced inside the container, so the SELinux branch is skipped — that branch is exercised on a host, out of scope for this task.) + +- [ ] **Step 4: Fix defects, if any** + +A failure here is a packaging defect (missing `Requires`, wrong path in the launcher, `%files` ownership, `passlib` import, missing `libpq`, …). Fix it in `root/ppg/devel/pgadmin/percona-pgadmin4/` (or the stack package), commit as `pgadmin: fix <what> (smoke test)`, and hand back to the controller to run Task 4's push/build loop for that fix (approval per push), then re-run this task's scripts. Do not paper over a failure by editing the smoke script. + +- [ ] **Step 5: Ledger** + +Append: paths of both logs, the five gunicorn tokens and two httpd tokens as they appeared, the image digest (`podman image inspect registry.access.redhat.com/ubi9/ubi:latest --format '{{.Digest}}'`), and the `percona-pgadmin4` NEVRA installed (`rpm -q` line from the log). + +--- + +### Task 6: Records — spec outcomes and PR #12 + +**Goal:** Close the loop: the SP4 spec's §9 gets the observed outcomes (what broke in OBS and how it was fixed, smoke-test result), and PR #12's title/body describe SP1–SP4 as delivered. + +**Files:** +- Modify: `docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` — §9 (risks/verification outcomes) and, if Task 2/4 changed a decision (e.g. `pgadmin4-cli` script name, `npm-flags`), the affected §5 line. +- Modify (remote, with approval): PR #12 body via `gh pr edit 12 --repo percona/obs-packaging --body-file …`. + +**Acceptance Criteria:** +- [ ] Spec §9 ends with an "Outcomes (2026-08-XX)" list: number of OBS fix rounds and their causes, final `_result` counts, smoke-test tokens with log paths, any decision changed during execution (with the ledger ruling text). +- [ ] The spec no longer states anything the delivered package contradicts (grep for `pgadmin4-cli`, `--legacy-peer-deps`, `REL-9_17`, `Authlib` and compare with the tree). +- [ ] PR #12 body (after user approval of the edit) has an "SP4 — percona-pgadmin4" section: what was added, the stack changes for 9.17, the smoke-test result, and how to run the container (`podman run … -e PGADMIN_DEFAULT_EMAIL … percona-pgadmin4-gunicorn`). No Claude attribution, no `obs-sync` label. +- [ ] Committed (`docs: SP4 outcomes`) and, after approval, pushed with the last fix push or on its own. + +**Verify:** `grep -n "Outcomes" docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` → one hit in §9; `gh pr view 12 --repo percona/obs-packaging --json body -q .body | grep -c "SP4"` → ≥ 1. + +**Steps:** + +- [ ] **Step 1: Spec §9 outcomes** + +Append to §9 of the SP4 spec (Edit tool, after the last risk row/paragraph): + +```markdown +### Outcomes (<date>) + +- OBS (`isv:percona:PR:pr-12:ppg:devel:pgadmin`, UBI_9): <N> fix rounds — <package: cause → fix, one per line>. Final `_result`: <count> succeeded, 0 failed/unresolvable; repository published. +- Stack for 9.17: 9 bumps, 4 additions (annotated-doc, certifi, libpass, gunicorn), 2 removals (passlib, importlib-resources), 5 `_aggregate`s of `ppg:common:deps`. +- Container smoke test (podman, `ubi9/ubi@<digest>`): `INSTALL OK`, `SETUP OK`, `HTTP 200`, `VERSION OK 9.17`, `ENV OK 7`; httpd: `Syntax OK`, `SETUP-WEB OK`. Logs: `<scratch>/smoke/gunicorn.log`, `<scratch>/smoke/httpd.log`. +- Decisions changed during execution: <none | list with ledger ruling text>. +``` + +Fill every `<…>` from the ledger and the logs — a placeholder left in the committed spec is a defect. + +- [ ] **Step 2: Commit** + +```bash +git add docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md +git commit -s -m "docs: SP4 outcomes (OBS fix rounds, smoke test)" +``` + +- [ ] **Step 3: PR #12 body (controller, with approval)** + +Draft the SP4 section into `$SCRATCH/pr12-body.md` by fetching the current body (`gh pr view 12 --repo percona/obs-packaging --json body -q .body > $SCRATCH/pr12-body.md`) and appending: + +```markdown +## SP4 — percona-pgadmin4 (pgAdmin 4 9.17, server mode) + +- `root/ppg/devel/pgadmin/percona-pgadmin4`: spec ported from openSUSE to EL9/python3.12; sources via `obs_scm` (REL-9_17) → `npm_lockfile` → `node_modules`; webpack against `local-npm-registry`; wheel via upstream `pkg/pip/setup_pip.py`. +- Subpackages: `-gunicorn` (launcher `percona-pgadmin4-gunicorn` + systemd unit, disabled; `PGADMIN_CONFIG_*` env → settings; the runtime for the container image), `-httpd` (mod_wsgi conf + `percona-pgadmin4-setup-web`), `-doc` (rst sources; Help menu links to the online manual). +- Python stack for 9.17: bumps Flask-Security-Too 5.8.2, Flask-SocketIO 5.6.1, Flask-WTF 1.3.0, gssapi 1.11.1 (Cython 3.2.4), psycopg/psycopg-c 3.3.4, pytz 2026.3.post1, typer 0.26.8; new annotated-doc, certifi, libpass (replaces passlib), gunicorn; removed passlib, importlib-resources; `_aggregate`s of click/six/dateutil/psutil/dns from `ppg:common:deps`. +- Smoke test (UBI-9 container from this PR's repo): install, first-start setup from `PGADMIN_DEFAULT_EMAIL/PASSWORD`, `GET /login` → 200, version 9.17; `httpd -t` Syntax OK with mod_wsgi. +- Try it: `podman run --rm -p 8080:8080 -e PGADMIN_DEFAULT_EMAIL=admin@example.com -e PGADMIN_DEFAULT_PASSWORD=… -e PGADMIN_LISTEN_ADDRESS=0.0.0.0 -e PGADMIN_LISTEN_PORT=8080 <ubi9 image with percona-pgadmin4-gunicorn installed> percona-pgadmin4-gunicorn`. +``` + +Ask the user: "Update PR #12's body with the SP4 section (draft at `$SCRATCH/pr12-body.md`)?" Only on a yes: `gh pr edit 12 --repo percona/obs-packaging --body-file $SCRATCH/pr12-body.md`. The push of the `docs: SP4 outcomes` commit is asked for separately (or bundled with the last fix push if the user prefers — say so when asking). + +--- + +## Appendix A — `render_stack.py` (final, used by Task 1) + +Write verbatim to `$SCRATCH/sp4-bump/render_stack.py`. It is the SP3 renderer plus: `-P` in `%check`, BuildRequires dedupe, PEP 639 `sed` set, `EXTRA_TAGS` (libpass Provides/Conflicts), `EXTRA_REQ`/`CHECK_BR`/`CHECK_IMPORT`, `known.json` name resolution, and same-name `Requires` dedupe (keeps the entry with a version floor). + +```python +"""Render the SP3 Python 3.12 stack package directories from stack.json. + +Throwaway: run once, commit the resulting package directories, never commit +this script. Usage: python render_stack.py <repo-root> [--only name,name] + +For every entry in stack.json writes + <repo>/root/<project-dir>/python3-<name>/package.yaml + <repo>/root/<project-dir>/python3-<name>/obs/_service + <repo>/root/<project-dir>/python3-<name>/rpm/python3-<name>.spec +following the spec template in docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md §5. +""" + +import datetime +import json +import re +import sys +from pathlib import Path + +HERE = Path(__file__).parent +STACK = json.loads((HERE / "stack.json").read_text()) +TODAY = datetime.date(2026, 8, 27) # fixed so re-runs are byte-identical +CHANGELOG_DATE = TODAY.strftime("%a %b %d %Y") + +PROJECT_DIR = {"pgadmin": "ppg/devel/pgadmin", "common": "ppg/common/deps"} + +# PyPI name (normalised) -> RPM name for packages we do NOT build here. +REUSED = { + "cffi": "python3.12-cffi", + "cryptography": "python3.12-cryptography", + "idna": "python3.12-idna", + "pycparser": "python3.12-pycparser", + "urllib3": "python3.12-urllib3", + "setuptools": "python3.12-setuptools", + "click": "%{python3_pkgprefix}-click", + "six": "%{python3_pkgprefix}-six", + "python-dateutil": "%{python3_pkgprefix}-dateutil", + "psutil": "%{python3_pkgprefix}-psutil", + "dnspython": "%{python3_pkgprefix}-dns", + "pluggy": "python3.12-pluggy", +} +# Classifier / free-text licence strings -> SPDX identifiers. +LICENSE = { + "MIT License": "MIT", + "MIT": "MIT", + "BSD License": "BSD-3-Clause", + "Apache Software License": "Apache-2.0", + "Apache 2.0": "Apache-2.0", + "ISC License (ISCL)": "ISC", + "GNU Lesser General Public License v3 (LGPLv3)": "LGPL-3.0-only", + "GNU Lesser General Public License v2 or later (LGPLv2+)": "LGPL-2.1-or-later", + "LGPL": "LGPL-2.1-or-later", + "Python Software Foundation License": "PSF-2.0", + "Mozilla Public License 2.0 (MPL 2.0)": "MPL-2.0", + "GNU General Public License v3 or later (GPLv3+)": "GPL-3.0-or-later", + "MIT-CMU": "MIT-CMU", + "MPL 2.0": "MPL-2.0", + "LGPL v3": "LGPL-3.0-only", + "ISC License": "ISC", + "GNU General Public License v3 (GPLv3)": "GPL-3.0-only", + "The Unlicense (Unlicense)": "Unlicense", +} +# Per-package overrides where PyPI metadata is missing or ambiguous (checked upstream). +LICENSE_BY_PKG = { + "hatchling": "MIT", + "python-engineio": "MIT", + "python-socketio": "MIT", + "bidict": "MPL-2.0", + "ldap3": "LGPL-3.0-only", + "pytz": "MIT", + "shellingham": "ISC", + "mdurl": "MIT", + "markdown-it-py": "MIT", + "typing-extensions": "PSF-2.0", + "jsonformatter": "MIT", + "libgravatar": "MIT", + "qrcode": "BSD-3-Clause", + "Flask-Principal": "MIT", + "Flask-Login": "MIT", + "simple-websocket": "MIT", + "wsproto": "MIT", + "ua-parser": "Apache-2.0", + "user-agents": "MIT", + "passlib": "BSD-3-Clause", + "psycopg": "LGPL-3.0-only", + "psycopg-c": "LGPL-3.0-only", +} + + +def spdx(pypi: str, lic: str) -> str: + if pypi in LICENSE_BY_PKG: + return LICENSE_BY_PKG[pypi] + lic = lic.strip() + return LICENSE.get(lic, lic or "see upstream") +# Directory / RPM-name exceptions. +DIRNAME = {"dnspython": "dns"} +# Import smoke-test overrides (wheel top_level.txt missing or namespace pkgs). +MODULES = { + "Flask-Principal": ["flask_principal"], + "jsonformatter": ["jsonformatter"], + "psycopg-c": ["psycopg_c"], + "jaraco.classes": ["jaraco.classes"], + "jaraco.context": ["jaraco.context"], + "jaraco.functools": ["jaraco.functools"], + "backports.zstd": ["backports.zstd"], + "poetry-core": ["poetry.core.masonry.api"], + "pdm-backend": ["pdm.backend"], + "flit-core": ["flit_core.buildapi"], + "Cython": ["Cython"], + "brotli": ["brotli"], + "PyNaCl": ["nacl"], +} +# Self-hosting build backends: the directory to put on PYTHONPATH so the +# backend can build itself with --no-build-isolation. +SELF_HOSTING = {"hatchling": "src", "poetry-core": "src", "pdm-backend": "src", "flit-core": "."} +# Extra BuildRequires by package (beyond family defaults). +EXTRA_BR = { + "bcrypt": ["%{python3_pkgprefix}-setuptools-rust", "cargo", "rust", "gcc"], + "gssapi": ["%{python3_pkgprefix}-cython", "krb5-devel", "gcc"], + "SQLAlchemy": ["%{python3_pkgprefix}-cython", "gcc"], + "psycopg-c": ["libpq-devel", "gcc"], + "PyNaCl": ["python3.12-cffi", "libffi-devel", "make", "gcc"], + "pillow": ["libjpeg-turbo-devel", "zlib-devel", "gcc"], + "backports.zstd": ["libzstd-devel", "gcc"], + "brotli": ["gcc-c++"], + "greenlet": ["gcc-c++"], + "MarkupSafe": ["gcc"], + "Cython": ["gcc"], + "SecretStorage": [], +} +NEEDS_SCM = lambda r: bool(re.search(r"setuptools[-_]scm", r["build_requires"])) # noqa: E731 +# Build flags (package.yaml build:) — the common:deps hatchling stack only. +DISABLE_DISTRO_HATCHLING = {"hatchling", "pathspec", "trove-classifiers", "flit-core", "packaging"} +BUILD_FLAGS_DISTRO = ["RockyLinux_10", "openSUSE_Leap_16", "openSUSE_Tumbleweed"] +# pip wheel config settings. +CONFIG_SETTINGS = { + "pillow": " ".join( + f"-C {f}" for f in [ + "platform-guessing=disable", "zlib=enable", "jpeg=enable", "tiff=disable", + "freetype=disable", "raqm=disable", "lcms=disable", "webp=disable", + "xcb=disable", "jpeg2000=disable", "imagequant=disable", "avif=disable", + ] + ) +} +# Previous changelog entries to keep (package -> text block). +OLD_CHANGELOG = { + "dnspython": "* Mon Mar 30 2026 Percona Build/Release Team <eng-build@percona.com> - 1.15.0-1\n- Initial build of python3-dns 1.15.0\n", +} +# setuptools-family sdists with PEP 639 licence metadata (license = "SPDX" string / +# license-files), which RHEL's setuptools 68 rejects: patched in %prep (SP3 fix round 1 +# for the first seven; psycopg 3.3, gunicorn added for the 9.17 bump). +PEP639_PATCH = { + "alembic", "backports.zstd", "greenlet", "Mako", "MarkupSafe", "SecretStorage", "wsproto", + "psycopg", "psycopg-c", "gunicorn", +} +# Extra tags emitted after Requires (package -> lines). +EXTRA_TAGS = { + # libpass is a passlib fork that installs the `passlib` module. + "libpass": [ + "Provides: %{python3_pkgprefix}-passlib = %{version}", + "Conflicts: %{python3_pkgprefix}-passlib < 1.9", + ], +} +# Extra runtime Requires (RPM names) not derivable from PyPI metadata (package -> lines), +# and whether to mirror them as BuildRequires. +EXTRA_REQ = { + "psycopg": [("libpq", True), ("%{python3_pkgprefix}-psycopg-c", False)], # psycopg-c BRs psycopg → no mirror + "Flask-Principal": [("%{python3_pkgprefix}-flask", True), ("%{python3_pkgprefix}-blinker", True)], + "qrcode": [("%{python3_pkgprefix}-pillow", True)], +} +# Build-only extras for the %check import (package -> BuildRequires lines). +CHECK_BR = {"psycopg-c": ["%{python3_pkgprefix}-psycopg"]} +# %check import string overrides. +CHECK_IMPORT = {"psycopg-c": "import psycopg; import psycopg_c"} + + +def norm(name: str) -> str: + return re.sub(r"[._]", "-", name.lower()) + + +# Packages that exist in the stack (for Requires name resolution): the rendered set plus, +# when rendering a subset (e.g. a version bump), everything else already in the tree — +# listed in an optional known.json next to stack.json (same record format). +_KNOWN_FILE = HERE / "known.json" +KNOWN = STACK + (json.loads(_KNOWN_FILE.read_text()) if _KNOWN_FILE.exists() else []) +BUILT = {} +for _r in KNOWN: + BUILT.setdefault(norm(_r["pypi"]), _r) + + +def rpm_dep(req: str) -> str | None: + """PyPI requirement 'Name spec' -> 'Requires:' target, or None to drop.""" + m = re.match(r"^([A-Za-z0-9_.\-]+)\s*(.*)$", req) + if not m: + return None + name, spec = norm(m.group(1)), m.group(2).strip() + if name in REUSED: + target = REUSED[name] + elif name in BUILT: + target = f"%{{python3_pkgprefix}}-{DIRNAME.get(BUILT[name]['pypi'], name)}" + else: + return None # not in our closure (optional/extra dependency) + floor = None + for part in [p.strip() for p in spec.split(",") if p.strip()]: + mm = re.match(r"^(>=|~=|==)\s*([0-9][0-9A-Za-z.]*)$", part) + if mm: + floor = mm.group(2) + # RHEL's reused packages are older than several floors; the libraries work + # with them (spec §3), so never emit a floor against a reused package. + if floor and name not in REUSED: + return f"{target} >= {floor}" + return target + + +def spec_text(r: dict) -> str: + pypi, ver = r["pypi"], r["version"] + name = DIRNAME.get(pypi, norm(pypi)) + fam = "self-hosting" if pypi in SELF_HOSTING else r["family"] + native = r["native"] + sitedir = "python3_sitearch" if native else "python3_sitelib" + lines = [] + if not native: + lines.append("%global debug_package %{nil}\n") + lines.append( + "%if 0%{?rhel} && 0%{?rhel} >= 8\n" + "%global __ospython %{_bindir}/python3.12\n" + "%global python3_pkgprefix python3.12\n" + "%global python3_buildversion 3.12\n" + "%global __requires_exclude ^python3\\\\.12dist\n" + "%else\n" + "%global __ospython %{_bindir}/python3\n" + "%global python3_pkgprefix python3\n" + "%global python3_buildversion 3\n" + "%endif\n" + "%{expand: %%global py3ver %(echo `%{__ospython} -P -c \"import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')\" `)}\n" + ) + if native: + lines.append("%global python3_sitearch %(%{__ospython} -Esc \"import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))\")\n") + else: + lines.append("%global python3_sitelib %(%{__ospython} -Esc \"import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))\")\n") + lines.append( + f"\nName: %{{python3_pkgprefix}}-{name}\n" + f"Version: {ver}\n" + "Release: 1%{?dist}\n" + f"Summary: {r['summary'] or pypi}\n" + f"License: {spdx(pypi, r['license'])}\n" + f"URL: {r['url']}\n" + f"Source0: {r['sdist_url']}\n" + ) + if pypi == "bcrypt": + lines.append("Source1: vendor.tar.gz\n") + if not native: + lines.append("BuildArch: noarch\n") + lines.append( + "Vendor: Percona, LLC\n" + "Packager: Percona Development Team <https://jira.percona.com>\n" + "Epoch: 1\n\n" + ) + br = [ + "python%{python3_buildversion}-devel", + "python%{python3_buildversion}-pip", + "python%{python3_buildversion}-setuptools", + "python%{python3_buildversion}-wheel", + ] + if fam == "flit": + br.append("python%{python3_buildversion}-flit-core") + if fam == "poetry": + br.append("%{python3_pkgprefix}-poetry-core") + if fam == "pdm": + br.append("%{python3_pkgprefix}-pdm-backend") + if NEEDS_SCM(r) and pypi not in SELF_HOSTING: + br.append("%{python3_pkgprefix}-setuptools_scm") + br += EXTRA_BR.get(pypi, []) + reqs_preview = [rpm_dep(q) for q in r["requires"]] + # A native build dep that is also a runtime dep is listed once, in the + # runtime block below (review finding, Task 2: python3-pynacl / cffi). + br = [b for b in dict.fromkeys(br) if b not in reqs_preview] + for b in br: + lines.append(f"BuildRequires: {b}\n") + if fam == "hatchling": + lines.append( + "%if 0%{?rhel} == 8 || 0%{?rhel} == 9\n" + "BuildRequires: %{python3_pkgprefix}-hatchling\n" + "%else\n" + "BuildRequires: python3-hatchling\n" + "%endif\n" + ) + reqs = [] + for req in r["requires"]: + dep = rpm_dep(req) + if not dep: + continue + # One line per dependency: when metadata lists a name twice (e.g. "flask" and + # "Flask>=2.1.0"), keep the entry that carries a version floor. + name_only = dep.split(" >= ")[0] + existing = next((d for d in reqs if d.split(" >= ")[0] == name_only), None) + if existing is None: + reqs.append(dep) + elif " >= " in dep and " >= " not in existing: + reqs[reqs.index(existing)] = dep + no_mirror = set() + for dep, mirror in EXTRA_REQ.get(pypi, []): + if dep not in reqs: + reqs.append(dep) + if not mirror: + no_mirror.add(dep) + for b in CHECK_BR.get(pypi, []): + lines.append(f"# for the %check import\nBuildRequires: {b}\n") + if reqs: + # Runtime deps are also build deps so %check can import the module — + # except those the preamble already declares (python3.12-setuptools == + # python%{python3_buildversion}-setuptools on EL; review finding, Task 3). + base_names = {"python3.12-devel", "python3.12-pip", "python3.12-setuptools", "python3.12-wheel"} + lines.append("# runtime dependencies, also needed by the %check import test\n") + for d in reqs: + if d not in base_names and d not in no_mirror: + lines.append(f"BuildRequires: {d}\n") + lines.append("\n") + for d in reqs: + if d in no_mirror: + lines.append("# not mirrored as BuildRequires: the dependency BuildRequires this package for its %check\n") + lines.append(f"Requires: {d}\n") + for t in EXTRA_TAGS.get(pypi, []): + lines.append(t + "\n") + role = ( + "part of the pgAdmin 4 (percona-pgadmin4) dependency stack" + if r["project"] == "pgadmin" + else "a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages" + ) + lines.append(f"\n%description\n{r['summary'] or pypi}.\n\nBuilt for Python 3.12 from the PyPI sdist; {role}.\n") + # prep + lines.append(f"\n%prep\n%autosetup -p1 -n {r['sdist_top']}") + if pypi == "bcrypt": + lines[-1] += " -a1" + lines.append("\n") + if pypi == "trove-classifiers": + lines.append( + "# Build without calver (not packaged): pin the version literally.\n" + "sed -i 's/\"calver\"//; s/, *\\]/]/' pyproject.toml\n" + "sed -i 's/use_calver=\"[^\"]*\",/version=\"%{version}\",/; /setup_requires=\\[\"calver\"\\],/d' setup.py\n" + ) + if pypi in PEP639_PATCH: + lines.append( + "# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files\n" + "sed -i -e 's/^license = \"\\(.*\\)\"$/license = {text = \"\\1\"}/' -e '/^license-files = \\[$/,/^\\]$/d' -e '/^license-files = \\[.*\\]$/d' pyproject.toml\n" + ) + # build / install + if fam == "setup.py": + lines.append( + "\n%build\n%{__ospython} setup.py build\n\n" + "%install\n%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES\n" + f"find %{{buildroot}}%{{{sitedir}}} -mindepth 1 -type d | sed \"s|%{{buildroot}}||\" | sed 's/^/%dir /' >> INSTALLED_FILES\n" + ) + else: + env = "" + if pypi in SELF_HOSTING: + env = f"export PYTHONPATH=$PWD/{SELF_HOSTING[pypi]}\n" + if pypi == "bcrypt": + env += "export CARGO_NET_OFFLINE=true\nexport CARGO_HOME=$PWD/.cargo\n" + cs = CONFIG_SETTINGS.get(pypi, "") + lines.append( + f"\n%build\n{env}%{{__ospython}} -m pip wheel --no-deps --no-build-isolation --no-index {cs + ' ' if cs else ''}--wheel-dir dist .\n\n" + "%install\n%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl\n" + ) + # check + mods = MODULES.get(pypi) or [m for m in r["modules"] if not m.startswith("_")] + imports = CHECK_IMPORT.get(pypi) or "; ".join("import " + m for m in mods) + # Import from the installed buildroot (src-layout packages are not importable + # from the source directory); -P keeps the cwd off sys.path. + lines.append( + f"\n%check\nPYTHONPATH=%{{buildroot}}%{{{sitedir}}} %{{__ospython}} -P -c \"{imports}\"\n" + ) + # files + if fam == "setup.py": + lines.append("\n%files -f INSTALLED_FILES\n%defattr(-,root,root)\n") + else: + lines.append(f"\n%files\n%{{{sitedir}}}/*\n") + for s in r["scripts"]: + lines.append(f"%{{_bindir}}/{s}\n") + # changelog + what = "pgAdmin 4 dependency stack" if r["project"] == "pgadmin" else "shared Python 3.12 build stack" + if pypi == "dnspython": + what = "bump to 2.8.0 (email-validator needs >= 2.0); built with hatchling" + lines.append( + f"\n%changelog\n* {CHANGELOG_DATE} Percona Development Team <info@percona.com> - {ver}-1\n" + f"- Package {pypi} {ver} for Python 3.12 ({what})\n" + ) + if pypi in OLD_CHANGELOG: + lines.append("\n" + OLD_CHANGELOG[pypi]) + return "".join(lines) + + +def service_text(r: dict) -> str: + s = "<services>\n <service name=\"download_url\">\n" f" <param name=\"url\">{r['sdist_url']}</param>\n </service>\n" + if r["pypi"] == "bcrypt": + s += ( + " <!-- Vendor the Rust crate's dependencies (Cargo.toml lives under src/_bcrypt)\n" + " for an offline OBS build; produces vendor.tar.gz incl. .cargo/config. -->\n" + " <service mode=\"buildtime\" name=\"cargo_vendor\">\n" + f" <param name=\"src\">{r['sdist_filename']}</param>\n" + " <param name=\"cargotoml\">src/_bcrypt/Cargo.toml</param>\n" + " <param name=\"compression\">gz</param>\n" + " <param name=\"update\">false</param>\n" + " </service>\n" + ) + return s + "</services>\n" + + +def package_yaml(r: dict) -> str: + pypi, ver = r["pypi"], r["version"] + who = "pgAdmin 4 (percona-pgadmin4)" if r["project"] == "pgadmin" else "Percona PostgreSQL packages" + text = ( + f"title: {pypi} {ver} for Python 3.12\n" + "description: |\n" + f" {r['summary'] or pypi}. RPM-only python3.12-* build from the PyPI sdist,\n" + f" consumed by {who}. Bump by editing obs/_service (sdist URL) and\n" + " rpm/*.spec (Version + changelog).\n" + ) + if pypi in DISABLE_DISTRO_HATCHLING: + text += "\n# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours.\nbuild:\n" + for f in BUILD_FLAGS_DISTRO: + text += f" {f}: false\n" + return text + + +def main(argv): + repo = Path(argv[1]).resolve() + only = set(argv[argv.index("--only") + 1].split(",")) if "--only" in argv else None + for r in STACK: + if only and r["pypi"] not in only and norm(r["pypi"]) not in only: + continue + name = DIRNAME.get(r["pypi"], norm(r["pypi"])) + pkg = repo / "root" / PROJECT_DIR[r["project"]] / f"python3-{name}" + (pkg / "obs").mkdir(parents=True, exist_ok=True) + (pkg / "rpm").mkdir(parents=True, exist_ok=True) + (pkg / "package.yaml").write_text(package_yaml(r)) + (pkg / "obs" / "_service").write_text(service_text(r)) + (pkg / "rpm" / f"python3-{name}.spec").write_text(spec_text(r)) + print(pkg.relative_to(repo)) + + +if __name__ == "__main__": + main(sys.argv) +``` + +## Appendix B — `stack.json` (the 13 packages to render) + +Write verbatim to `$SCRATCH/sp4-bump/stack.json`. + +```json +[ + { + "pypi": "Flask-Security-Too", + "version": "5.8.2", + "project": "pgadmin", + "summary": "Quickly add security features to your Flask application", + "url": "https://github.com/pallets-eco/flask-security", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-Security-Too/flask_security_too-5.8.2.tar.gz", + "sdist_filename": "flask_security_too-5.8.2.tar.gz", + "sdist_top": "flask_security_too-5.8.2", + "family": "flit", + "native": false, + "requires": [ + "Flask >=3.1.1", + "Flask-Login >=0.6.3", + "Flask-Principal >=0.4.0", + "Flask-WTF >=1.1.2", + "email-validator >=2.3.0", + "markupsafe >=2.1.0", + "libpass >=1.9.3", + "wtforms >=3.0.0" + ], + "build_requires": "\"flit_core >=3.8,<5\"", + "scripts": [], + "modules": [ + "flask_security" + ] + }, + { + "pypi": "Flask-SocketIO", + "version": "5.6.1", + "project": "pgadmin", + "summary": "Socket.IO integration for Flask applications", + "url": "https://github.com/miguelgrinberg/flask-socketio", + "license": "MIT License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-SocketIO/flask_socketio-5.6.1.tar.gz", + "sdist_filename": "flask_socketio-5.6.1.tar.gz", + "sdist_top": "flask_socketio-5.6.1", + "family": "setuptools", + "native": false, + "requires": [ + "blinker", + "click", + "flask", + "Flask >=2.1.0", + "jinja2", + "python-socketio >=5.12.0", + "werkzeug" + ], + "build_requires": "\"setuptools>=61.2\",", + "scripts": [], + "modules": [ + "flask_socketio" + ] + }, + { + "pypi": "Flask-WTF", + "version": "1.3.0", + "project": "pgadmin", + "summary": "Form rendering, validation, and CSRF protection for Flask with WTForms", + "url": "https://pypi.org/project/Flask-WTF/", + "license": "BSD License", + "sdist_url": "https://files.pythonhosted.org/packages/source/F/Flask-WTF/flask_wtf-1.3.0.tar.gz", + "sdist_filename": "flask_wtf-1.3.0.tar.gz", + "sdist_top": "flask_wtf-1.3.0", + "family": "hatchling", + "native": false, + "requires": [ + "flask", + "itsdangerous", + "wtforms" + ], + "build_requires": "\"hatchling\"", + "scripts": [], + "modules": [ + "flask_wtf" + ] + }, + { + "pypi": "gssapi", + "version": "1.11.1", + "project": "pgadmin", + "summary": "Python GSSAPI Wrapper", + "url": "https://github.com/pythongssapi/python-gssapi", + "license": "ISC", + "sdist_url": "https://files.pythonhosted.org/packages/source/g/gssapi/gssapi-1.11.1.tar.gz", + "sdist_filename": "gssapi-1.11.1.tar.gz", + "sdist_top": "gssapi-1.11.1", + "family": "setuptools", + "native": true, + "requires": [ + "decorator" + ], + "build_requires": "\"Cython == 3.2.4\", \"setuptools >= 40.6.0\", # Start of PEP 517 support for setuptools", + "scripts": [], + "modules": [ + "gssapi" + ] + }, + { + "pypi": "psycopg", + "version": "3.3.4", + "project": "pgadmin", + "summary": "PostgreSQL database adapter for Python", + "url": "https://psycopg.org/", + "license": "LGPL-3.0-only", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/psycopg/psycopg-3.3.4.tar.gz", + "sdist_filename": "psycopg-3.3.4.tar.gz", + "sdist_top": "psycopg-3.3.4", + "family": "setuptools", + "native": false, + "requires": [ + "typing-extensions >=4.6" + ], + "build_requires": "\"setuptools>=80.3.1\", \"wheel>=0.37\"", + "scripts": [], + "modules": [ + "psycopg" + ] + }, + { + "pypi": "psycopg-c", + "version": "3.3.4", + "project": "pgadmin", + "summary": "PostgreSQL database adapter for Python -- C optimisation distribution", + "url": "https://psycopg.org/", + "license": "LGPL-3.0-only", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/psycopg-c/psycopg_c-3.3.4.tar.gz", + "sdist_filename": "psycopg_c-3.3.4.tar.gz", + "sdist_top": "psycopg_c-3.3.4", + "family": "setuptools", + "native": true, + "requires": [], + "build_requires": "# Note: pinning this version strictly because of the setuptools warning: # # `[tool.setuptools.ext-modules", + "scripts": [], + "modules": [] + }, + { + "pypi": "pytz", + "version": "2026.3.post1", + "project": "pgadmin", + "summary": "World timezone definitions, modern and historical", + "url": "http://pythonhosted.org/pytz", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/p/pytz/pytz-2026.3.post1.tar.gz", + "sdist_filename": "pytz-2026.3.post1.tar.gz", + "sdist_top": "pytz-2026.3.post1", + "family": "setup.py", + "native": false, + "requires": [], + "build_requires": "", + "scripts": [], + "modules": [ + "pytz" + ] + }, + { + "pypi": "typer", + "version": "0.26.8", + "project": "pgadmin", + "summary": "Typer, build great CLIs. Easy to code. Based on Python type hints", + "url": "https://github.com/fastapi/typer", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/t/typer/typer-0.26.8.tar.gz", + "sdist_filename": "typer-0.26.8.tar.gz", + "sdist_top": "typer-0.26.8", + "family": "pdm", + "native": false, + "requires": [ + "shellingham >=1.3.0", + "rich >=13.8.0", + "annotated-doc >=0.0.2" + ], + "build_requires": "\"pdm-backend\",", + "scripts": [ + "typer" + ], + "modules": [ + "typer" + ] + }, + { + "pypi": "annotated-doc", + "version": "0.0.5", + "project": "pgadmin", + "summary": "Document parameters, class attributes, return types, and variables inline, with Annotated", + "url": "https://github.com/fastapi/annotated-doc", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/a/annotated-doc/annotated_doc-0.0.5.tar.gz", + "sdist_filename": "annotated_doc-0.0.5.tar.gz", + "sdist_top": "annotated_doc-0.0.5", + "family": "pdm", + "native": false, + "requires": [], + "build_requires": "\"pdm-backend\",", + "scripts": [], + "modules": [ + "annotated_doc" + ] + }, + { + "pypi": "certifi", + "version": "2026.6.17", + "project": "pgadmin", + "summary": "Python package for providing Mozilla's CA Bundle", + "url": "https://github.com/certifi/python-certifi", + "license": "MPL-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/c/certifi/certifi-2026.6.17.tar.gz", + "sdist_filename": "certifi-2026.6.17.tar.gz", + "sdist_top": "certifi-2026.6.17", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools >= 42.0.0\"", + "scripts": [], + "modules": [ + "certifi" + ] + }, + { + "pypi": "libpass", + "version": "1.9.3", + "project": "pgadmin", + "summary": "Fork of passlib, a comprehensive password hashing framework supporting over 30 schemes", + "url": "https://github.com/notypecheck/passlib", + "license": "BSD", + "sdist_url": "https://files.pythonhosted.org/packages/source/l/libpass/libpass-1.9.3.tar.gz", + "sdist_filename": "libpass-1.9.3.tar.gz", + "sdist_top": "libpass-1.9.3", + "family": "hatchling", + "native": false, + "requires": [], + "build_requires": "\"hatchling\"", + "scripts": [], + "modules": [ + "passlib" + ] + }, + { + "pypi": "gunicorn", + "version": "26.2.0", + "project": "pgadmin", + "summary": "WSGI HTTP Server for UNIX", + "url": "https://gunicorn.org", + "license": "MIT", + "sdist_url": "https://files.pythonhosted.org/packages/source/g/gunicorn/gunicorn-26.2.0.tar.gz", + "sdist_filename": "gunicorn-26.2.0.tar.gz", + "sdist_top": "gunicorn-26.2.0", + "family": "setuptools", + "native": false, + "requires": [], + "build_requires": "\"setuptools>=61.2\"", + "scripts": [ + "gunicorn", + "gunicornc" + ], + "modules": [ + "gunicorn" + ] + }, + { + "pypi": "Cython", + "version": "3.2.4", + "project": "pgadmin", + "summary": "The Cython compiler for writing C extensions in the Python language", + "url": "https://cython.org/", + "license": "Apache-2.0", + "sdist_url": "https://files.pythonhosted.org/packages/source/C/Cython/cython-3.2.4.tar.gz", + "sdist_filename": "cython-3.2.4.tar.gz", + "sdist_top": "cython-3.2.4", + "family": "setup.py", + "native": true, + "requires": [], + "build_requires": "", + "scripts": [ + "cygdb", + "cython", + "cythonize" + ], + "modules": [ + "Cython", + "cython", + "pyximport" + ] + } +] +``` + +## Appendix C — `known.json` (names of the other 66 stack packages) + +Write verbatim to `$SCRATCH/sp4-bump/known.json`. Only the `pypi` field is read; it lets the renderer emit `Requires:` on packages outside the rendered set (`flask`, `wtforms`, `rich`, `python-socketio`, `decorator`, …). + +```json +[{"pypi": "alembic"}, {"pypi": "Authlib"}, {"pypi": "babel"}, {"pypi": "backports.zstd"}, {"pypi": "bcrypt"}, {"pypi": "bidict"}, {"pypi": "blinker"}, {"pypi": "brotli"}, {"pypi": "decorator"}, {"pypi": "email-validator"}, {"pypi": "Flask"}, {"pypi": "flask-babel"}, {"pypi": "Flask-Compress"}, {"pypi": "Flask-Login"}, {"pypi": "Flask-Mail"}, {"pypi": "Flask-Migrate"}, {"pypi": "Flask-Paranoid"}, {"pypi": "Flask-Principal"}, {"pypi": "Flask-SQLAlchemy"}, {"pypi": "greenlet"}, {"pypi": "h11"}, {"pypi": "itsdangerous"}, {"pypi": "jaraco.classes"}, {"pypi": "jaraco.context"}, {"pypi": "jaraco.functools"}, {"pypi": "jeepney"}, {"pypi": "Jinja2"}, {"pypi": "jsonformatter"}, {"pypi": "keyring"}, {"pypi": "ldap3"}, {"pypi": "libgravatar"}, {"pypi": "Mako"}, {"pypi": "markdown-it-py"}, {"pypi": "MarkupSafe"}, {"pypi": "mdurl"}, {"pypi": "more-itertools"}, {"pypi": "paramiko"}, {"pypi": "pillow"}, {"pypi": "pyasn1"}, {"pypi": "Pygments"}, {"pypi": "PyNaCl"}, {"pypi": "PyOTP"}, {"pypi": "python-engineio"}, {"pypi": "python-socketio"}, {"pypi": "qrcode"}, {"pypi": "rich"}, {"pypi": "SecretStorage"}, {"pypi": "shellingham"}, {"pypi": "simple-websocket"}, {"pypi": "SQLAlchemy"}, {"pypi": "sqlparse"}, {"pypi": "sshtunnel"}, {"pypi": "typing-extensions"}, {"pypi": "ua-parser"}, {"pypi": "user-agents"}, {"pypi": "Werkzeug"}, {"pypi": "wsproto"}, {"pypi": "WTForms"}, {"pypi": "poetry-core"}, {"pypi": "pdm-backend"}, {"pypi": "dnspython"}, {"pypi": "hatchling"}, {"pypi": "pathspec"}, {"pypi": "trove-classifiers"}, {"pypi": "flit-core"}, {"pypi": "packaging"}] +``` + +## Appendix D — `0002-make-cloud-packages-optional.patch` (openSUSE, verbatim) + +```diff +Index: pgadmin4-8.2/web/pgadmin/misc/__init__.py +=================================================================== +--- pgadmin4-8.2.orig/web/pgadmin/misc/__init__.py ++++ pgadmin4-8.2/web/pgadmin/misc/__init__.py +@@ -108,8 +108,17 @@ class MiscModule(PgAdminModule): + from .bgprocess import blueprint as module + self.submodules.append(module) + +- from .cloud import blueprint as module +- self.submodules.append(module) ++ try: ++ from .cloud import blueprint as module ++ self.submodules.append(module) ++ except ModuleNotFoundError: ++ print('\n\n') ++ print('###########################################################\n') ++ print(' IMPORTANT WARNING:\n') ++ print('Cloud packages not found, if you want to enable cloud support,') ++ print('please install the pgadmin4-cloud package') ++ print('\n###########################################################\n') ++ + + from .dependencies import blueprint as module + self.submodules.append(module) +``` + +## Appendix E — `0003-use-os-makedirs.patch` (openSUSE, verbatim) + +```diff +From: Antonio Larrosa <alarrosa@suse.com> +Subject: Use os.makedirs instead of os.mkdir + +So parent directories are created if needed + +--- + web/pgadmin/setup/data_directory.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: pgadmin4-7.4/web/pgadmin/setup/data_directory.py +=================================================================== +--- pgadmin4-7.4.orig/web/pgadmin/setup/data_directory.py ++++ pgadmin4-7.4/web/pgadmin/setup/data_directory.py +@@ -18,7 +18,7 @@ FAILED_CREATE_DIR = \ + + def _create_directory_if_not_exists(_path): + if _path and not os.path.exists(_path): +- os.mkdir(_path) ++ os.makedirs(_path) + return True + + return False +``` diff --git a/docs/superpowers/plans/2026-08-28-pgadmin4-package.md.tasks.json b/docs/superpowers/plans/2026-08-28-pgadmin4-package.md.tasks.json new file mode 100644 index 00000000..65dd07e7 --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-pgadmin4-package.md.tasks.json @@ -0,0 +1,45 @@ +{ + "planPath": "docs/superpowers/plans/2026-08-28-pgadmin4-package.md", + "tasks": [ + { + "id": 1, + "subject": "Task 1: SP3 stack changes for the 9.17 closure", + "status": "completed", + "description": "**Goal:** Bring the `python3-*` stack in `root/ppg/devel/pgadmin/` to pgAdmin 9.17's dependency closure — 9 version bumps, 4 new packages, 2 removals, 5 aggregates — with the SP3 spec inventory updated to match.\n\n**Files:**\n- Modify (re-rendered): `root/ppg/devel/pgadmin/python3-{flask-security-too,flask-socketio,flask-wtf,gssapi,psycopg,psycopg-c,pytz,typer,cython}/rpm/*.spec` (+ `obs/_service`, `package.yaml` where the sdist changed)\n- Create (rendered): `root/ppg/devel/pgadmin/python3-{annotated-doc,certifi,libpass,gunicorn}/{package.yaml,obs/_service,rpm/*.spec}`\n- Delete: `root/ppg/devel/pgadmin/python3-passlib/`, `root/ppg/devel/pgadmin/python3-importlib-resources/`\n- Create: `root/ppg/devel/pgadmin/python3-{click,six,dateutil,psutil,dns}/obs/_aggregate`\n- Modify: `docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md` §4 inventory\n- Scratch inputs (plan Appendices A–C): `$SCRATCH/sp4-bump/{render_stack.py,stack.json,known.json}`; check script `$SCRATCH/sp4-bump/check_bump.py` (Task 1 Step 4)\n\n**Acceptance Criteria:**\n- [ ] `rpmspec -q --qf '%{NAME}-%{VERSION}\\n' <spec>` succeeds for all 13 rendered specs and prints the 9.17 versions (flask-security-too 5.8.2, flask-socketio 5.6.1, flask-wtf 1.3.0, gssapi 1.11.1, psycopg 3.3.4, psycopg-c 3.3.4, pytz 2026.3.post1, typer 0.26.8, cython 3.2.4, annotated-doc 0.0.5, certifi 2026.6.17, libpass 1.9.3, gunicorn 26.2.0)\n- [ ] `python3-libpass.spec` has `Provides: %{python3_pkgprefix}-passlib = %{version}` and `Conflicts: %{python3_pkgprefix}-passlib < 1.9`; `python3-flask-security-too.spec` requires `%{python3_pkgprefix}-libpass >= 1.9.3` and no longer mentions passlib/importlib-resources\n- [ ] `python3-typer.spec` requires `annotated-doc >= 0.0.2` and `rich >= 13.8.0`, not click/typing-extensions\n- [ ] psycopg, psycopg-c, gunicorn specs carry the PEP 639 `sed` in `%prep`\n- [ ] `python3-passlib` and `python3-importlib-resources` directories are gone\n- [ ] Five `_aggregate` files exist, each naming the matching `ppg:common:deps` source package (`${OBS_ROOTPRJ}:ppg:common:deps`)\n- [ ] `venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin python3-gunicorn` and `… python3-libpass` succeed (dry-run only — isv-pr is production)\n- [ ] SP3 spec §4 table reflects the 9.17 versions, the four additions, the two removals, plus the \"9.17 update\" paragraph\n- [ ] Committed with `git commit -s` (message in plan Step 8); NOT pushed\n\n**Verify:** `venv/bin/python $SCRATCH/sp4-bump/check_bump.py` → `OK 13 specs`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin/\", \"docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md\"], \"acceptanceCriteria\": [\"13 rendered specs parse with 9.17 versions\", \"libpass Provides/Conflicts passlib; flask-security-too requires libpass\", \"typer requires annotated-doc and rich>=13.8.0\", \"PEP 639 sed in psycopg/psycopg-c/gunicorn\", \"passlib and importlib-resources removed\", \"five _aggregate files\", \"two dry-run syncs succeed\", \"SP3 spec §4 updated\", \"committed, not pushed\"], \"verifyCommand\": \"venv/bin/python $SCRATCH/sp4-bump/check_bump.py\", \"modelTier\": \"standard\"}\n```", + "lastUpdated": "2026-08-28T11:39:36.630095+00:00" + }, + { + "id": 2, + "subject": "Task 2: The percona-pgadmin4 package", + "status": "completed", + "description": "**Goal:** Add `root/ppg/devel/pgadmin/percona-pgadmin4/` — spec, service chain, support files and patches — producing `percona-pgadmin4`, `-gunicorn`, `-httpd` and `-doc`, verified as far as local tooling allows (spec parse, script syntax, patch dry-run, sync dry-run).\n\n**Files:**\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/{config_distro.py,run_pgadmin.py,gunicorn_config.py}`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/{percona-pgadmin4-gunicorn,percona-pgadmin4.service,percona-pgadmin4.sysusers,percona-pgadmin4.tmpfiles}`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/{percona-pgadmin4-httpd.conf,percona-pgadmin4-setup-web}`\n- Create: `root/ppg/devel/pgadmin/percona-pgadmin4/rpm/{0001-help-menu-online-docs.patch,0002-make-cloud-packages-optional.patch,0003-use-os-makedirs.patch}`\n- Scratch: `$SCRATCH/check_config_distro.py` (Step 3), `$SCRATCH/pgadmin-src/` (upstream clone + node_modules.spec.inc)\n\n**Acceptance Criteria:**\n- [ ] `rpmspec -P --define \"_sourcedir $SCRATCH/pgadmin-src\" …/percona-pgadmin4.spec` succeeds (real `node_modules.spec.inc` from the sync dry-run, or a stub — ledger says which) and shows four `%files` sections\n- [ ] `python3 -m py_compile` passes for the three .py support files; `bash -n` passes for the two scripts; `systemd-analyze verify --man=no` of the unit reports no syntax issue\n- [ ] All three patches apply with `patch -p1 --dry-run` against a fresh clone of tag `REL-9_17` (`0001` → `web/pgadmin/help/__init__.py` with no fuzz/offset, `0002` → `web/pgadmin/misc/__init__.py`, `0003` → `web/pgadmin/setup/data_directory.py`)\n- [ ] `config_distro.py` with `PGADMIN_CONFIG_SERVER_MODE=false PGADMIN_CONFIG_MAX_LOGIN_ATTEMPTS=7 PGADMIN_CONFIG_HELP_PATH=/x PGADMIN_CONFIG_AUTHENTICATION_SOURCES=\"['internal', 'ldap']\"` yields False / 7 (int) / '/x' / list — `check_config_distro.py` → `OK`\n- [ ] `venv/bin/python -m percona_obs -P isv-pr sync push --dry-run ppg:devel:pgadmin percona-pgadmin4` runs the whole service chain and lists `percona-pgadmin4-9.17.*`, `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc`, the spec, 9 support files, 3 patches\n- [ ] Committed with `git commit -s` (message in plan Step 9); NOT pushed\n\n**Verify:** `rpmspec -P --define \"_sourcedir $SCRATCH/pgadmin-src\" root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec | grep -c '^%files'` → `4`; `venv/bin/python $SCRATCH/check_config_distro.py` → `OK`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin/percona-pgadmin4/\"], \"acceptanceCriteria\": [\"spec parses with 4 %files sections\", \"py_compile/bash -n/systemd-analyze pass\", \"three patches dry-run apply (0001 exact)\", \"config_distro env mapping check OK\", \"sync dry-run runs the full chain\", \"committed, not pushed\"], \"verifyCommand\": \"rpmspec -P --define \\\"_sourcedir $SCRATCH/pgadmin-src\\\" root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec | grep -c '^%files'\", \"modelTier\": \"standard\"}\n```", + "lastUpdated": "2026-08-28T12:14:42.136853+00:00" + }, + { + "id": 3, + "subject": "Task 3: Documentation", + "status": "completed", + "description": "**Goal:** Record the pgadmin project's application package in the tree/tool docs so the next person finds it where the other projects are described.\n\n**Files:**\n- Modify: `root/README.md` — the `ppg/devel/pgadmin` entry\n- Modify: `docs/PERCONA_OBS_TOOL.md` — the `npm_lockfile`/`node_modules` section\n\n**Acceptance Criteria:**\n- [ ] `grep -n percona-pgadmin4 root/README.md docs/PERCONA_OBS_TOOL.md` shows at least one hit in each file\n- [ ] `root/README.md` describes `ppg/devel/pgadmin` as: python3.12 stack, aggregates of `ppg:common:deps` runtime packages, `percona-pgadmin4` (+ `-gunicorn`/`-httpd`/`-doc`), UBI_9 only\n- [ ] Diff limited to the additions (no reflow); committed `docs: describe percona-pgadmin4 and the pgadmin project layout`\n\n**Verify:** `git diff --stat HEAD~1` → exactly the two files\n\n```json:metadata\n{\"files\": [\"root/README.md\", \"docs/PERCONA_OBS_TOOL.md\"], \"acceptanceCriteria\": [\"percona-pgadmin4 mentioned in both files\", \"README describes the pgadmin project layout\", \"additions only; committed\"], \"verifyCommand\": \"grep -n percona-pgadmin4 root/README.md docs/PERCONA_OBS_TOOL.md\", \"modelTier\": \"mechanical\"}\n```", + "lastUpdated": "2026-08-28T12:18:43.071110+00:00" + }, + { + "id": 4, + "subject": "Task 4: OBS build loop via PR #12 (controller-driven)", + "status": "in_progress", + "description": "**Goal:** Get every package in `isv:percona:PR:pr-12:ppg:devel:pgadmin` (UBI_9) to `succeeded` after pushing Tasks 1–3: push (user-approved), sync, watch, fix in rounds — each push approved individually.\n\n**Files:**\n- Modify (as failures dictate): files under `root/ppg/devel/pgadmin/` only\n\n**Acceptance Criteria:**\n- [ ] Push 1 done only after the user's explicit yes; PR #12 sync workflow run `completed success`\n- [ ] `_result?repository=UBI_9&arch=x86_64` shows no `failed`/`unresolvable`/`broken`; `percona-pgadmin4` and all `python3-*` `succeeded`; repository published\n- [ ] One commit per root cause (`pgadmin: fix <package> — <cause>` with log excerpt); every push asked for and approved\n- [ ] Waits use Monitor/background scripts (no foreground sleeps); the controller waits, not the implementer\n- [ ] Ledger has the final `_result` summary, fix commits, and the published repo URL\n\n**Verify:** `curl -s 'https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result?repository=UBI_9&arch=x86_64' | grep -cE 'code=\"(failed|unresolvable|broken)\"'` → `0`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin/\"], \"acceptanceCriteria\": [\"push approved and sync green\", \"no failed/unresolvable/broken in _result; percona-pgadmin4 succeeded\", \"one commit per root cause, each push approved\", \"non-blocking waits\", \"ledger records outcome\"], \"verifyCommand\": \"curl -s 'https://api.opensuse.org/public/build/isv:percona:PR:pr-12:ppg:devel:pgadmin/_result?repository=UBI_9&arch=x86_64' | grep -cE 'code=\\\"(failed|unresolvable|broken)\\\"'\", \"modelTier\": \"standard\"}\n```", + "lastUpdated": "2026-08-28T12:18:43.071110+00:00" + }, + { + "id": 5, + "subject": "Task 5: Container smoke test (podman, UBI-9)", + "status": "pending", + "description": "**Goal:** Prove the published PR #12 repository installs and runs pgAdmin 4 the way the future container image will: `percona-pgadmin4` + `-gunicorn` in a UBI-9 container, first start creates the admin user from the environment, `GET /login` → HTTP 200 with the page identifying 9.17; `-httpd` installs and `httpd -t` accepts the shipped configuration.\n\n**USER-ORDERED GATE — NON-SKIPPABLE.** This task was requested by the user in the current conversation. It MUST NOT be closed by walking around it, by declaring it \"verified inline\", or by substituting a cheaper check. Close only after every item in `acceptanceCriteria` has been re-validated independently, with output captured.\n\n**Files:**\n- Create (scratch): `$SCRATCH/smoke/pr12.repo`, `$SCRATCH/smoke/smoke-gunicorn.sh`, `$SCRATCH/smoke/smoke-httpd.sh`; logs `$SCRATCH/smoke/gunicorn.log`, `$SCRATCH/smoke/httpd.log`\n- Modify (only on a packaging defect): `root/ppg/devel/pgadmin/percona-pgadmin4/`\n\n**Acceptance Criteria:**\n- [ ] `gunicorn.log` contains `INSTALL OK`, `SETUP OK`, `HTTP 200`, `VERSION OK 9.17`, `ENV OK 7`\n- [ ] `httpd.log` contains `INSTALL OK`, `Syntax OK`, `wsgi_module (shared)`, `SETUP-WEB OK`\n- [ ] Ledger cites both log paths, quotes the tokens, the ubi9 image digest and the installed `percona-pgadmin4` NEVRA\n- [ ] Any defect found is fixed in the package (not in the smoke script), committed, pushed via Task 4's loop (approval), and the scripts re-run green\n\n**Verify:** `grep -E 'INSTALL OK|SETUP OK|HTTP 200|VERSION OK|ENV OK 7' $SCRATCH/smoke/gunicorn.log | wc -l` → `5`; `grep -E 'INSTALL OK|Syntax OK' $SCRATCH/smoke/httpd.log | wc -l` → `2`\n\n```json:metadata\n{\"files\": [\"root/ppg/devel/pgadmin/percona-pgadmin4/\"], \"acceptanceCriteria\": [\"gunicorn.log has INSTALL OK, SETUP OK, HTTP 200, VERSION OK 9.17, ENV OK 7\", \"httpd.log has INSTALL OK, Syntax OK, wsgi_module (shared), SETUP-WEB OK\", \"ledger cites logs, digest, NEVRA\", \"defects fixed in the package, re-run green\"], \"verifyCommand\": \"grep -E 'INSTALL OK|SETUP OK|HTTP 200|VERSION OK|ENV OK 7' $SCRATCH/smoke/gunicorn.log | wc -l\", \"userGate\": true, \"tags\": [\"user-gate\"], \"requireEvidenceTokens\": [[\"INSTALL OK\"], [\"HTTP 200\"], [\"VERSION OK 9.17\"], [\"Syntax OK\"]], \"modelTier\": \"standard\"}\n```" + }, + { + "id": 6, + "subject": "Task 6: Records — spec outcomes and PR #12", + "status": "pending", + "description": "**Goal:** Close the loop: the SP4 spec's §9 gets the observed outcomes (OBS fix rounds and causes, final counts, smoke-test result, decisions changed), and PR #12's body describes SP4 as delivered.\n\n**Files:**\n- Modify: `docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` §9 (+ any §5 line a decision change affects)\n- Modify (remote, with approval): PR #12 body via `gh pr edit 12 --repo percona/obs-packaging --body-file $SCRATCH/pr12-body.md`\n\n**Acceptance Criteria:**\n- [ ] §9 ends with an \"Outcomes (<date>)\" list filled from the ledger/logs — no `<…>` placeholders left\n- [ ] Spec statements match the delivered tree (`pgadmin4-cli`, `--legacy-peer-deps`, `REL-9_17`, `Authlib`)\n- [ ] PR #12 body has an \"SP4 — percona-pgadmin4\" section (edit only after the user's yes); no Claude attribution; no `obs-sync` label\n- [ ] Committed `docs: SP4 outcomes (OBS fix rounds, smoke test)`; pushed only with approval\n\n**Verify:** `grep -n \"Outcomes\" docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md` → one hit; `gh pr view 12 --repo percona/obs-packaging --json body -q .body | grep -c SP4` → ≥ 1\n\n```json:metadata\n{\"files\": [\"docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md\"], \"acceptanceCriteria\": [\"§9 Outcomes filled, no placeholders\", \"spec matches the tree\", \"PR #12 body SP4 section after approval\", \"committed; pushed only with approval\"], \"verifyCommand\": \"grep -n Outcomes docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md\", \"modelTier\": \"mechanical\"}\n```" + } + ] +} \ No newline at end of file diff --git a/docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md b/docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md new file mode 100644 index 00000000..40643932 --- /dev/null +++ b/docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md @@ -0,0 +1,401 @@ +# pgAdmin 4 on UBI-9 — sub-project 3: the Python 3.12 dependency stack design + +**Date:** 2026-08-26 +**Status:** approved in brainstorming, awaiting spec review +**Parent:** `2026-08-26-pgadmin4-tooling-design.md` (decomposition §4; decisions §3) +**Scope of this document:** every `python3.12-*` RPM that `percona-pgadmin4` (SP4) needs +on UBI-9 and that neither RHEL 9 nor `ppg:common:deps` already provides; the changes to +`ppg:common:deps` this requires; the spec template these packages share. + +## 1. Goal + +Provide, as ordinary OBS packages in this repository, the complete Python 3.12 runtime +closure of pgAdmin 4 `REL-9_9` (cloud extras excluded, as openSUSE does), built for +`/usr/bin/python3.12` on UBI 9, so that SP4's `percona-pgadmin4.spec` can simply +`BuildRequires`/`Requires` them by RPM name. No `percona_obs` code changes. + +## 2. Findings that shaped the design + +### 2.1 The closure + +`pgadmin4/REL-9_9/requirements.txt`, minus `azure-*`, `boto3`, `google-*`, `pywinpty`, +with the `python_version > '3.9'` variants, resolved with +`uv pip compile --python-version 3.12` on 2026-08-26: **80 packages** (file +`pgadmin-closure-3.12.txt`; the `# via` edges are the source of every `Requires:` below). +A PyPI/openSUSE:Factory survey of all 80 produced, per package: canonical name, sdist, +build backend (from the sdist's `pyproject.toml`), native sources, licence and whether a +Factory `python-<Name>` package exists. Backend mix: 35 setuptools, 15 flit-core, +9 setup.py-only, 7 hatchling, 4 pyproject-without-backend (setuptools), 3 poetry-core, +1 pdm-backend, 1 `uv_build` (bidict 0.24), 1 maturin (cryptography 46), psycopg-c's +in-tree `cython_backend`, Pillow's in-tree `backend`. + +### 2.2 What UBI-9 / Rocky 9 already ship for Python 3.12 + +AppStream: `python3.12` 3.12.14, `-devel`, `-libs`, `-pip` 23.2.1, `-setuptools` 68.2.2, +`-wheel` 0.41.2, `-cffi` 1.16.0, `-cryptography` 41.0.7, `-idna` 3.4, `-pycparser` 2.20, +`-urllib3` 1.26.19, `-requests` 2.28.2, `-charset-normalizer`, `-pyyaml`, `-lxml`, +`-psycopg2`, `-mod_wsgi` 4.9.4. +CRB: `-flit-core` 3.9.0, `-packaging` 23.2, `-pluggy` 1.2.0, `-pytest` 7.4.2, +`-iniconfig`, `-setuptools-rust` 1.7.0, `-semantic_version`, `-Cython` 0.29.35, +`-pybind11`. EPEL 9: `python3.12-setuptools_scm` 8.2.1 (+`+toml`). +Rocky 8 AppStream/PowerTools carry the same set (3.12.x); Rocky 10's default `python3` +*is* 3.12 (`python3-devel` provides `python3.12-devel`) and its CRB has `python3-hatchling` +1.27, `-pathspec`, `-trove-classifiers`, `-setuptools_scm`. openSUSE Tumbleweed and Leap +16.0 ship `python31x-hatchling` (Leap: 1.27.0) and `python31x-dnspython` (Leap: 2.7.0). +Native `-devel` libraries needed by the closure are all in UBI-9 AppStream: `libpq-devel`, +`krb5-devel`, `openssl-devel`, `libffi-devel`, `openldap-devel`, `brotli-devel`, +`libzstd-devel`, `libjpeg-turbo-devel`, `zlib-devel`. Rust 1.92 / cargo resolve from +Rocky 9 on UBI_9 (verified in `cargo-pgrx`'s `_buildinfo`). + +### 2.3 Existing repo conventions + +`ppg:common:deps` already builds `python3-six` 1.17.0, `python3-dateutil` 2.9.0.post0, +`python3-psutil` 6.1.1, `python3-click` 8.1.7 and `python3-dns` (dnspython **1.15.0**) +with a shared EL spec preamble (`__ospython=/usr/bin/python3.12`, +`python3_pkgprefix=python3.12`, `__requires_exclude ^python3\.12dist`; plain `python3` on +SUSE), `setup.py build/install --record`, `Epoch: 1`, sources via `obs_scm` from GitHub +tags. `python3-dns` is consumed by `python3-etcd` and all `percona-patroni` packages +(staging 14–18, devel 19) and builds on RockyLinux 8/9/10, UBI 8/9, Leap 16, Tumbleweed. +Directory names are `python3-<name>`; the RPM name is `%{python3_pkgprefix}-<name>`. + +### 2.4 Constraints discovered + +- **setuptools ≥ 77** (PEP 639 licence metadata) is required by the current keyring, + jaraco.context, jaraco.functools, importlib-resources and Pillow releases; RHEL has 68. +- **Cython 3** is required by gssapi 1.10.1 (`Cython == 3.1.3`; 30 `.pyx`, no generated C); + psycopg-c ships its generated C and SQLAlchemy's Cython speedups are optional. +- **dnspython** ≥ 2.0 is required by `email-validator` (Flask-Security-Too); dnspython + dropped `setup.py` after 2.1.0 (2.2–2.4 poetry-core, 2.5+ hatchling). +- `bidict` 0.24 needs the Rust `uv_build` backend; `pyotp` 2.10 needs `hatch-vcs`; + `ua-parser` 1.0 needs `ua-parser-builtins` (wheel-only, no Factory package). +- `trove-classifiers` uses `calver` at build time (`use_calver=` in `setup.py`). +- **RHEL's build backends are too old for current PEP 639 metadata** (`license = "…"` + string + `license-files`, used by Flask, Werkzeug, Pygments and most 2025 releases): + flit-core 3.9.0 aborts with `license field should be dict`; hatchling < 1.27 likewise. + Current backends need `packaging >= 24.2` (RHEL: 23.2). Verified locally (2026-08-26): + **pip 23.2.1 (RHEL's) + flit-core 3.12.0 / hatchling 1.28.0 / packaging 25.0** builds + Flask 3.1.3 and Pygments 2.21.0 sdists into Metadata-Version 2.4 wheels and installs them + with `pip install --root` — so the shared stack must carry current flit-core, hatchling + and packaging, and pip itself is fine. + +## 3. Decisions taken during brainstorming + +| Topic | Decision | +|---|---| +| Version policy | **Reuse existing packages where the libraries' real minimums are met**, even where pgAdmin's `requirements.txt` pins newer: RHEL's cryptography 41.0.7, urllib3 1.26.19, setuptools 68.2.2, cffi, idna, pycparser; common:deps' psutil 6.1.1, click 8.1.7, six, dateutil. openSUSE and Debian ship pgAdmin against distro versions the same way. SP4 relaxes the pins. Consequence: no maturin/Rust for cryptography; no replacement of RHEL packages. | +| dnspython | **Bump `python3-dns` in `ppg:common:deps` to 2.8.0 for everyone**, keeping the package/RPM name, and add a **shared build-backend stack** to `ppg:common:deps` for EL8/EL9/UBI: flit-core 3.12.0, packaging 25.0, pathspec 0.12.1, trove-classifiers, hatchling 1.28.0 (§2.4: RHEL's flit-core 3.9 / packaging 23.2 cannot build PEP 639 metadata). EL10 and openSUSE use their distro `python3-hatchling`/`python3-flit-core`. `python3-etcd` and `percona-patroni` need no source change (dnspython 2 keeps `dns.resolver.query()`); they are dep-cascade rebuilt. | +| setuptools ≥ 77 packages | Pin down to the last releases that build with setuptools 68: keyring 25.2.1, jaraco.context 6.0.1, jaraco.functools 4.1.0, importlib-resources 6.5.2, Pillow 11.1.0 (all still satisfy pgAdmin's and their consumers' ranges). | +| Other backend avoidance | bidict 0.23.1 (setuptools), pyotp 2.9.0 (setuptools), ua-parser 0.18.0 (self-contained; `user-agents` needs ≥ 0.10) — `ua-parser-builtins` dropped. | +| Build tools packaged | `python3-cython` 3.1.3, `python3-poetry-core` 2.2.1, `python3-pdm-backend` 2.4.5 in `ppg:devel:pgadmin`; hatchling stack in `ppg:common:deps`. `setuptools_scm` from EPEL 9. | +| How the ~75 directories are produced | **Plain package directories, no generator or manifest committed.** A throwaway rendering script (scratchpad only) writes them consistently once; from then on they are maintained by hand like every other package. The shared template is documented in `PACKAGING_HOWTO.md`. | +| Sources | PyPI sdists via `download_url` (`https://files.pythonhosted.org/packages/source/<x>/<name>/<file>`, as openSUSE), literal versions in `_service` and spec. bcrypt adds `cargo_vendor` (`cargotoml=src/_bcrypt/Cargo.toml`). | +| `%check` | Import smoke test only (`%{__ospython} -c "import <module>"`); no pytest, to keep the closure closed. pgAdmin (SP4) is the integration test. | +| Naming | Directory/OBS package `python3-<PyPI name lower-cased, `_`/`.` → `-`>`; RPM `%{python3_pkgprefix}-<same>` (Fedora-style, e.g. `python3.12-flask-security-too`, `python3.12-jaraco-classes`). Exception kept: `python3-dns`. | + +## 4. Inventory + +### 4.1 New packages in `ppg:devel:pgadmin` (`root/ppg/devel/pgadmin/`, UBI_9 only) + +68 runtime libraries + 3 build tools. "Required by" lists the closure consumers that get a +`Requires:` on this package (direct pgAdmin requirements have none listed; SP4 adds them). + +| Directory | PyPI | Version | Build family | Arch | Required by (closure) | Notes | +|---|---|---|---|---|---|---| +| `python3-alembic` | alembic | 1.19.1 | setuptools | noarch | flask-migrate | | +| `python3-annotated-doc` | annotated-doc | 0.0.5 | pdm | noarch | typer | | +| `python3-authlib` | Authlib | 1.6.12 | setuptools | noarch | | | +| `python3-babel` | babel | 2.18.0 | setuptools | noarch | flask-babel | | +| `python3-backports-zstd` | backports.zstd | 1.7.0 | setuptools | arch | flask-compress | libzstd-devel | +| `python3-bcrypt` | bcrypt | 5.0.0 | setuptools | arch | paramiko | Rust: setuptools-rust (CRB), cargo/rust; `cargo_vendor` with `cargotoml=src/_bcrypt/Cargo.toml` | +| `python3-bidict` | bidict | 0.23.1 | setuptools | noarch | python-socketio | pinned down from 0.24.1 (uv_build) | +| `python3-blinker` | blinker | 1.9.0 | flit | noarch | flask, flask-mail, flask-principal | | +| `python3-brotli` | brotli | 1.2.0 | setuptools | arch | flask-compress | gcc-c++ | +| `python3-certifi` | certifi | 2026.6.17 | setuptools | noarch | percona-pgadmin4 (requirements.txt pins certifi directly) | | +| `python3-decorator` | decorator | 5.3.1 | setuptools | noarch | gssapi | | +| `python3-email-validator` | email-validator | 2.3.0 | setuptools | noarch | flask-security-too | Requires python3.12-dns ≥ 2.0 (common:deps) and RHEL idna | +| `python3-flask` | Flask | 3.1.3 | flit | noarch | flask-babel, flask-compress, flask-login, flask-mail, flask-migrate, flask-paranoid, flask-principal, flask-security-too, flask-socketio, flask-sqlalchemy, flask-wtf | Requires common:deps click | +| `python3-flask-babel` | flask-babel | 4.0.0 | poetry | noarch | | | +| `python3-flask-compress` | Flask-Compress | 1.24 | setuptools | noarch | | build: setuptools_scm (EPEL) | +| `python3-flask-login` | Flask-Login | 0.6.3 | setup.py | noarch | flask-security-too | | +| `python3-flask-mail` | Flask-Mail | 0.10.0 | flit | noarch | | | +| `python3-flask-migrate` | Flask-Migrate | 4.1.0 | setuptools | noarch | | | +| `python3-flask-paranoid` | Flask-Paranoid | 0.3.0 | setuptools | noarch | | | +| `python3-flask-principal` | Flask-Principal | 0.4.0 | setup.py | noarch | flask-security-too | | +| `python3-flask-security-too` | Flask-Security-Too | 5.8.2 | flit | noarch | | Factory name `python-Flask-Security` | +| `python3-flask-socketio` | Flask-SocketIO | 5.6.1 | setuptools | noarch | | | +| `python3-flask-sqlalchemy` | Flask-SQLAlchemy | 3.1.1 | flit | noarch | flask-migrate | | +| `python3-flask-wtf` | Flask-WTF | 1.3.0 | hatchling | noarch | flask-security-too | | +| `python3-greenlet` | greenlet | 3.5.5 | setuptools | arch | sqlalchemy | gcc-c++ | +| `python3-gssapi` | gssapi | 1.11.1 | setuptools | arch | | krb5-devel; build: python3.12-cython 3.2.4 (ours) | +| `python3-gunicorn` | gunicorn | 26.2.0 | setuptools | noarch | percona-pgadmin4 | container runtime (SP4); scripts gunicorn, gunicornc | +| `python3-h11` | h11 | 0.16.0 | setuptools | noarch | wsproto | | +| `python3-itsdangerous` | itsdangerous | 2.2.0 | flit | noarch | flask, flask-wtf | | +| `python3-jaraco-classes` | jaraco.classes | 3.4.0 | setuptools | noarch | keyring | build: setuptools_scm | +| `python3-jaraco-context` | jaraco.context | 6.0.1 | setuptools | noarch | keyring | pinned down from 6.1.2; build: setuptools_scm | +| `python3-jaraco-functools` | jaraco.functools | 4.1.0 | setuptools | noarch | keyring | pinned down from 4.6.0; build: setuptools_scm | +| `python3-jeepney` | jeepney | 0.9.0 | flit | noarch | keyring, secretstorage | | +| `python3-jinja2` | Jinja2 | 3.1.6 | flit | noarch | flask, flask-babel | | +| `python3-jsonformatter` | jsonformatter | 0.3.4 | setuptools | noarch | | | +| `python3-keyring` | keyring | 25.2.1 | setuptools | noarch | | pinned down from 25.7.0; build: setuptools_scm | +| `python3-ldap3` | ldap3 | 2.9.1 | setup.py | noarch | | | +| `python3-libgravatar` | libgravatar | 1.0.4 | setup.py | noarch | | | +| `python3-libpass` | libpass | 1.9.3 | hatchling | noarch | flask-security-too | replaces passlib; Provides/Conflicts python3.12-passlib | +| `python3-mako` | Mako | 1.4.1 | setuptools | noarch | alembic | | +| `python3-markdown-it-py` | markdown-it-py | 4.2.0 | flit | noarch | rich | | +| `python3-markupsafe` | MarkupSafe | 3.0.3 | setuptools | arch | flask, flask-security-too, jinja2, mako, werkzeug, wtforms | C speedups | +| `python3-mdurl` | mdurl | 0.1.2 | flit | noarch | markdown-it-py | | +| `python3-more-itertools` | more-itertools | 11.1.0 | flit | noarch | jaraco-classes, jaraco-functools | | +| `python3-paramiko` | paramiko | 3.5.1 | setup.py | noarch | sshtunnel | Requires RHEL cryptography | +| `python3-pillow` | pillow | 11.1.0 | setuptools (in-tree backend) | arch | qrcode | libjpeg-turbo-devel, zlib-devel; other codecs disabled; pinned down from 12.3.0 | +| `python3-psycopg` | psycopg | 3.3.4 | setuptools | noarch | | | +| `python3-psycopg-c` | psycopg-c | 3.3.4 | setuptools (in-tree cython_backend, ships C) | arch | psycopg | libpq-devel | +| `python3-pyasn1` | pyasn1 | 0.6.4 | setuptools | noarch | ldap3 | | +| `python3-pygments` | Pygments | 2.21.0 | hatchling | noarch | rich | | +| `python3-pynacl` | PyNaCl | 1.6.2 | setuptools | arch | paramiko | bundled libsodium; RHEL cffi | +| `python3-pyotp` | PyOTP | 2.9.0 | setuptools | noarch | | pinned down from 2.10.0 (hatch-vcs) | +| `python3-python-engineio` | python-engineio | 4.13.5 | setuptools | noarch | python-socketio | | +| `python3-python-socketio` | python-socketio | 5.16.4 | setuptools | noarch | flask-socketio | | +| `python3-pytz` | pytz | 2026.3.post1 | setup.py | noarch | flask-babel | | +| `python3-qrcode` | qrcode | 8.2 | poetry | noarch | | | +| `python3-rich` | rich | 15.0.0 | poetry | noarch | typer | | +| `python3-secretstorage` | SecretStorage | 3.5.0 | setuptools | noarch | keyring | Requires RHEL cryptography | +| `python3-shellingham` | shellingham | 1.5.4 | setuptools | noarch | typer | | +| `python3-simple-websocket` | simple-websocket | 1.1.0 | setuptools | noarch | python-engineio | | +| `python3-sqlalchemy` | SQLAlchemy | 2.0.52 | setuptools | arch | alembic, flask-sqlalchemy | C speedups via python3.12-cython (ours) | +| `python3-sqlparse` | sqlparse | 0.6.0 | hatchling | noarch | | | +| `python3-sshtunnel` | sshtunnel | 0.4.0 | setuptools | noarch | | | +| `python3-typer` | typer | 0.26.8 | pdm | noarch | | Requires rich >= 13.8.0, shellingham >= 1.3.0, annotated-doc >= 0.0.2 | +| `python3-typing-extensions` | typing-extensions | 4.16.0 | flit | noarch | alembic, psycopg, sqlalchemy, typer | | +| `python3-ua-parser` | ua-parser | 0.18.0 | setup.py | noarch | user-agents | self-contained 0.18 (replaces 1.0 + ua-parser-builtins) | +| `python3-user-agents` | user-agents | 2.2.0 | setup.py | noarch | | | +| `python3-werkzeug` | Werkzeug | 3.1.8 | flit | noarch | flask, flask-login | | +| `python3-wsproto` | wsproto | 1.3.2 | setuptools | noarch | simple-websocket | | +| `python3-wtforms` | WTForms | 3.2.2 | hatchling | noarch | flask-security-too, flask-wtf | | +| `python3-cython` | Cython | 3.2.4 | build tool (setup.py) | arch | build-time only (gssapi, sqlalchemy) | distinct from CRB's `python3.12-Cython` 0.29 (different case, different RPM); gssapi/sqlalchemy BuildRequire ours by name | +| `python3-poetry-core` | poetry-core | 2.2.1 | build tool (self-hosting) | noarch | build-time only (flask-babel, qrcode, rich) | `PYTHONPATH=src` | +| `python3-pdm-backend` | pdm-backend | 2.4.5 | build tool (self-hosting) | noarch | build-time only (typer) | `PYTHONPATH=src` | + +> **9.17 update (SP4, 2026-08-28).** pgAdmin moved from REL-9_9 to REL-9_17; the closure changed as follows: bumps Flask-Security-Too 5.8.2, Flask-SocketIO 5.6.1, Flask-WTF 1.3.0, gssapi 1.11.1 (Cython 3.2.4), psycopg/psycopg-c 3.3.4, pytz 2026.3.post1, typer 0.26.8; additions annotated-doc 0.0.5, certifi 2026.6.17, libpass 1.9.3 (replaces passlib; `Provides/Conflicts python3.12-passlib`), gunicorn 26.2.0 (container runtime, SP4 §3); removals passlib, importlib-resources. Not adopted: Authlib 1.7.x (ruling: stay on 1.6.12), joserfc 1.7.4 (needs cryptography ≥ 45; RHEL 9 ships 41). + +### 4.2 `ppg:common:deps` (`root/ppg/common/deps/`) + +| Directory | PyPI | Version | Build family | Builds on | Notes | +|---|---|---|---|---|---| +| `python3-dns` | dnspython | 2.8.0 | hatchling | all RPM repos (as today) | bump 1.15.0 → 2.8.0, name kept; `_service` moves to `download_url`; consumers `python3-etcd`, `percona-patroni` ×6 rebuilt by dep-cascade | +| `python3-flit-core` | flit-core | 3.12.0 | self-hosting (`PYTHONPATH=.`) | RockyLinux_8/9, UBI_8/9 | `build:` disables RockyLinux_10, openSUSE_Leap_16, openSUSE_Tumbleweed (distro backends there); newer than CRB's 3.9.0 by version | +| `python3-packaging` | packaging | 25.0 | flit | RockyLinux_8/9, UBI_8/9 | same build flags; newer than CRB's 23.2 by version | +| `python3-pathspec` | pathspec | 0.12.1 | flit | RockyLinux_8/9, UBI_8/9 | same build flags | +| `python3-trove-classifiers` | trove-classifiers | 2025.9.11.17 | setuptools | RockyLinux_8/9, UBI_8/9 | same build flags; patch: drop `calver`, set version literally | +| `python3-hatchling` | hatchling | 1.28.0 | self-hosting (`PYTHONPATH=src`) | RockyLinux_8/9, UBI_8/9 | same build flags; Requires packaging ≥ 24.2 (ours), pathspec, trove-classifiers, RHEL pluggy | + +### 4.3 Reused, not built + +| RPM | Provided by | Version | Closure asked for | +|---|---|---|---| +| python3.12-cffi | RHEL 9 AppStream | 1.16.0 | 2.1.1 | +| python3.12-cryptography | RHEL 9 AppStream | 41.0.7 | 46.0.7 | +| python3.12-idna | RHEL 9 AppStream | 3.4 | 3.19 | +| python3.12-pycparser | RHEL 9 AppStream | 2.20 | 3.0 | +| python3.12-setuptools | RHEL 9 AppStream | 68.2.2 | 80.10.2 | +| python3.12-urllib3 | RHEL 9 AppStream | 1.26.19 | 2.5.0 | +| python3.12-click | ppg:common:deps | 8.1.7 | 8.5.0 | +| python3.12-psutil | ppg:common:deps | 6.1.1 | 7.1.3 | +| python3.12-dateutil | ppg:common:deps | 2.9.0.post0 | 2.9.0.post0 | +| python3.12-six | ppg:common:deps | 1.17.0 | 1.17.0 | +| build-only: python3.12-pip, -wheel, -flit-core, -packaging, -pluggy, -setuptools-rust | RHEL 9 | 23.2.1, 0.41.2, 3.9.0, 23.2, 1.2.0, 1.7.0 | — | +| build-only: python3.12-setuptools_scm | EPEL 9 | 8.2.1 | — | +| — (dropped) | — | — | ua-parser-builtins 202606 | + +## 5. The spec template + +Every package follows one template; the three build families differ only in +`%build`/`%install`. + +### 5.1 Header (identical to `python3-click`) + +```rpmspec +%global debug_package %{nil} # noarch packages only + +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") +# arch packages: python3_sitearch with 'platlib' + +Name: %{python3_pkgprefix}-flask +Version: 3.1.3 +Release: 1%{?dist} +Summary: ... +License: BSD-3-Clause +URL: https://flask.palletsprojects.com +Source0: flask-%{version}.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core # family: flit +Requires: %{python3_pkgprefix}-werkzeug >= 3.1.0 # from PyPI requires_dist +Requires: %{python3_pkgprefix}-jinja2 >= 3.1.2 +Requires: %{python3_pkgprefix}-itsdangerous >= 2.2 +Requires: %{python3_pkgprefix}-click >= 8.1.3 +Requires: %{python3_pkgprefix}-blinker >= 1.9 +Requires: %{python3_pkgprefix}-markupsafe +``` + +Backend BuildRequires per family: `-flit-core` (ours on EL8/9, §4.2); `-hatchling` +behind the conditional below; `-poetry-core` / `-pdm-backend` (ours); `-setuptools_scm` +where the sdist's `[build-system] requires` lists it; `-setuptools-rust` + `cargo` + +`rust` (bcrypt); `-cython` (gssapi, sqlalchemy); `gcc`/`gcc-c++` and `-devel` libraries +for native packages. Runtime `Requires:` are the closure edges with the minimum PyPI +declares; reused distro packages are named verbatim (`python3.12-cryptography`) and +never carry a floor (RHEL's versions are older than several upstream floors but work, +§3). **Every runtime `Requires:` is also emitted as a `BuildRequires:`** so the `%check` +import test can run in the build root (standard Python packaging practice; OBS orders the +builds accordingly). One exception: `python3-psycopg` Requires `psycopg-c` without the +BuildRequires mirror, because `python3-psycopg-c` BuildRequires `psycopg` for its own +`%check` (a mirror would be a cycle). + +```rpmspec +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling # ours, ppg:common:deps +%else +BuildRequires: python3-hatchling # EL10 CRB / openSUSE +%endif +``` + +### 5.2 Build families + +1. **pyproject** (setuptools, flit, hatchling, poetry, pdm — 66 packages): + ```rpmspec + %prep + %autosetup -p1 -n flask-%{version} + %build + %{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + %install + %{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + ``` +2. **setup.py-only** (9 packages): the existing `setup.py build` / + `setup.py install --single-version-externally-managed -O1 --root ... --record INSTALLED_FILES` + recipe from `python3-click`, unchanged. +3. **self-hosting backends** (hatchling, poetry-core, pdm-backend): family 1 with + `PYTHONPATH=src` exported for the `pip wheel` step. + +`%files`: `%{python3_sitelib}/*` (or `%{python3_sitearch}/*`) plus explicit +`%{_bindir}/<script>` entries; `%license` and `%doc` where the sdist ships them. The +buildroot only ever holds this package, so the glob is exact. RHEL's +`python3-rpm-generators` add `python3.12dist(<name>) = <version>` Provides from the +`.dist-info`. + +`%check`: `PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import <module>[; import <module2>]"` +(from the installed buildroot — src-layout packages are not importable from the source +directory; `%{python3_sitearch}` for arch packages). The `-P` flag is load-bearing: without +it Python prepends the current working directory (the source tree, not the buildroot) to +`sys.path`, so native-extension packages import the pure-Python source instead of the +installed build (e.g. gssapi's `No module named gssapi.raw.creds`); this was the single +fix applied across all 77 specs in the first build round (`52d93bc`). + +`%changelog`: one entry, `* <Day Mon DD YYYY> Percona Development Team <info@percona.com> - <version>-1`. + +### 5.3 Patches + +Only what a build needs: `trove-classifiers` (two `sed`s in `%prep`: remove `calver` from +`pyproject.toml`'s `requires`, replace `use_calver=...` with `version="%{version}"`); +anything a first build reveals is fixed in that package's spec. + +Second standing patch, added during the build loop: for the 7 setuptools-family packages +(alembic, backports-zstd, greenlet, mako, markupsafe, secretstorage, wsproto) RHEL +setuptools 68 rejects PEP 639's `license = "…"` SPDX-expression form plus `license-files` +— a `%prep` `sed` rewrites `license = "…"` to the legacy `license = {text = "…"}` table +and drops the `license-files` key, rather than shipping a newer setuptools (`176d706`). + +### 5.4 `package.yaml` and `obs/_service` + +```yaml +title: Flask 3.1.3 for Python 3.12 +description: | + ... one paragraph; RPM-only, Python 3.12, consumed by percona-pgadmin4. +``` +`ppg:devel:pgadmin` packages carry no `build:` flags (the project is UBI_9-only); the +`ppg:common:deps` hatchling stack disables RockyLinux_10 and both openSUSE repos. + +```xml +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/f/flask/flask-3.1.3.tar.gz</param> + </service> +</services> +``` +bcrypt adds `<service mode="buildtime" name="cargo_vendor">` with `src`, `cargotoml` +`src/_bcrypt/Cargo.toml`, `compression gz`, `update false`; its spec unpacks +`vendor.tar.gz` with `%autosetup -a1` so cargo builds offline. + +## 6. How the directories are produced + +Plain directories under `root/ppg/devel/pgadmin/` and `root/ppg/common/deps/`, each +`{package.yaml, obs/_service, rpm/python3-<name>.spec}` (+ `rpm/*.patch` if any). A +throwaway script in the implementer's scratchpad renders them once from the survey data +so that 75 files come out consistent; it is **not committed** and leaves no trace beyond +the packages. Afterwards every package is hand-maintained like any other here: bump by +editing `_service` URL + spec `Version:` + changelog. + +Documentation committed with the packages: +- `docs/PACKAGING_HOWTO.md`: new section "Python 3.12 packages (pyproject builds)" with + the header block, the three families, the hatchling conditional, `%check`, and the + reuse-RHEL policy, so the next Python package follows the same pattern. +- `root/README.md`: `devel/pgadmin/` now holds pgAdmin's Python 3.12 stack; + `common/deps/` gains the hatchling build stack. +- `.github/copilot-instructions.md`: no change (no tooling or workflow changes in SP3). + +## 7. Testing & verification + +No `percona_obs` code changes, hence no new unit tests; `black`, `pyright`, `pytest` +still run to show nothing else moved. + +**Local, read-only** (before pushing): +- `rpmspec -P` over every generated spec (macro/syntax errors). +- `sync push --dry-run -P isv-pr` on one package per family plus bcrypt: proves + `download_url` follows PyPI's redirects and `cargo_vendor` vendors the nested crate. + +**OBS via PR #12**, bottom-up commits so failures localise: +1. `ppg:common:deps`: hatchling stack + dnspython bump → green on RockyLinux 8/9(/10 for + dns), UBI 8/9, Leap 16, Tumbleweed; `python3-etcd` and `percona-patroni` cascade + rebuilds green. +2. `ppg:devel:pgadmin`: build tools (cython, poetry-core, pdm-backend) and the leaf + libraries with no intra-stack dependencies. +3. `ppg:devel:pgadmin`: the rest (OBS orders builds by `BuildRequires`). + +**Acceptance for SP3:** all 71 pgadmin packages `succeeded` on UBI_9 x86_64 and aarch64; +the 6 common:deps packages (`python3-dns` and the five build-backend packages) `succeeded` +on every repo they build for; `local-npm-registry` and every other package in the PR +project unchanged. + +## 8. Risks and mitigations + +| Risk | Mitigation / observed outcome | +|---|---| +| RHEL pip 23.2.1 rejects Metadata-Version 2.4 wheels emitted by flit/hatchling | **Retired** — reproduced locally with pip 23.2.1 + flit-core 3.12.0 / hatchling 1.28.0: Flask and Pygments build and install fine (§2.4). | +| `setuptools_scm` (EPEL) cannot determine the version from a sdist | Did not occur: keyring, jaraco.*, importlib-resources, Flask-Compress built from PKG-INFO without `SETUPTOOLS_SCM_PRETEND_VERSION`. | +| Pillow 11.1 configure picks up codecs UBI-9 lacks | Did not occur: built with the jpeg/zlib-only `-C` flags as planned. | +| bcrypt cargo goes online | Occurred: `cargo_vendor` places `vendor/` and `.cargo/config.toml` under `src/_bcrypt/`, not the sdist root; fixed by `CARGO_HOME=$PWD/src/_bcrypt/.cargo` (`fce0164`). | +| gssapi needs exactly Cython 3.1.3 | Cython 3.1.3 resolved correctly; the failure was `%check` importing the source tree instead of the buildroot (fixed by `%{__ospython} -P -c "import ..."`, `52d93bc`). | +| dnspython 2 changes break python-etcd / patroni at runtime | Not exercised in PR #12 (label `no-dep-cascade`; PR project is UBI_9-only) — the cascade rebuild happens on merge; `python3-dns` 2.8.0 itself built successfully on UBI_9. | +| A package's first OBS build fails for a reason not foreseen here | Occurred, 12 packages failed the first build (9 on x86_64: backports-zstd, bcrypt, flask-principal, greenlet, gssapi, markupsafe, psycopg-c, qrcode, secretstorage; 3 only on aarch64: psycopg, ua-parser, wsproto); fixed by 10 commits over 3 rounds: RHEL setuptools 68 rejects PEP 639 `license = "…"` + `license-files` in 7 setuptools-family packages (alembic, backports-zstd, greenlet, mako, markupsafe, secretstorage, wsproto) → `%prep` sed to `license = {text = "…"}` (`176d706`); `%check` shadowed by the source tree for all 77 specs → `%{__ospython} -P` (`52d93bc`); flask-principal's PyPI metadata omits Flask/blinker → added as BuildRequires/Requires (`4441f17`); psycopg needs libpq at import → BuildRequires/Requires libpq (`2b65469`); psycopg-c refuses import unless psycopg is imported first → BuildRequires python3.12-psycopg, `%check` imports psycopg then psycopg_c (`3a385e0`); qrcode's `console_scripts.py` has an ambiguous shebang → removed in `%prep` (`d8ade24`); ua-parser's `setup_requires=["pyyaml"]` tries a live PyPI fetch → BuildRequires python3.12-pyyaml, drop `setup_requires` (`9836542`); bcrypt cargo_vendor path (see above, `fce0164`); greenlet's wheel installs an unpackaged `greenlet.h` header → added to `%files` (`abdc615`); WTForms' hatch build hook needs Babel to compile translations → BuildRequires python3.12-babel (`5498b3e`). (wsproto's failure was the arch-independent PEP 639 error; it showed only on aarch64 in the first snapshot because the x86_64 scheduler was behind.) | +| OBS rebuild storms from download-on-demand path-repo refreshes ("meta change") on a slow x86_64 scheduler | New: after the fixes, both PR projects rebuilt three more times with `_jobhistory` reason "meta change" (Rocky 9 / EPEL 9 DoD repos refreshing) — roughly 4 hours from first push to a settled board, not caused by our pushes (every sync logged `= project meta/config`); expect the same behaviour on merge, nothing to fix in our packages. | + +## 9. Out of scope + +`percona-pgadmin4` itself (SP4); Debian/Ubuntu; pytest-based `%check`; any other +product's Python dependencies; replacing RHEL `python3.12-*` packages. diff --git a/docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md b/docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md new file mode 100644 index 00000000..3ec2e439 --- /dev/null +++ b/docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md @@ -0,0 +1,354 @@ +# pgAdmin 4 on UBI-9 — sub-project 1: tooling (`node_modules` support) design + +**Date:** 2026-08-26 +**Status:** approved in brainstorming, awaiting spec review +**Scope of this document:** the overall decomposition of the pgAdmin 4 effort, and the +full design of **sub-project 1 (tooling)**. Sub-projects 2–4 get their own specs. + +## 1. Goal + +Package pgAdmin 4 (https://github.com/pgadmin-org/pgadmin4) as `percona-pgadmin4` for +**UBI-9 only**, using openSUSE's `server:database:postgresql/pgadmin4` packaging as the +basis, in a new OBS project **`ppg:devel:pgadmin`** that holds every pgAdmin-related +package. The `percona-obs` tool must work with the OBS `node_modules` source service, +which vendors the frontend (npm) dependencies for offline builds. + +## 2. Findings that shaped the design + +### 2.1 How openSUSE's `pgadmin4` package works + +- Source: release tarball; a hand-generated `package-lock.json` (upstream ships only + `yarn.lock`) is committed next to the spec. +- `_service` declares a single `node_modules` service in `mode="manual"`. Run locally it + reads `*package-lock.json` from the cwd and produces `node_modules.obscpio` + (~217 MB, every npm tarball) and `node_modules.spec.inc` (~1 400 `SourceNNNNN:` lines, + `%include`d by the spec). +- At build time obs-build unpacks `*.obscpio` into `SOURCES` + (`/usr/lib/build/build:1918`); `BuildRequires: local-npm-registry` serves the tarballs + to `npm install` offline; `npx webpack` builds the frontend. +- Python runtime: ~45 distro `python-*` modules (`%python_module` macros), then + `bdist_wheel` + `%pyproject_install`. + +### 2.2 Why the Python side cannot be transplanted as-is + +EPEL 9 / RHEL 9 have Flask 2.0.3 (pgAdmin 9.x needs 3.1), Werkzeug 2.0, Flask-Babel 2.0, +SQLAlchemy 1.4, and nothing at all for Flask-Security-Too, Flask-SocketIO, Flask-Login, +Flask-Migrate, Flask-SQLAlchemy, Flask-Compress, Flask-Paranoid, libgravatar, user-agents, +jsonformatter, qrcode. RHEL 9 does ship a `python3.12` stack (`python3.12`, `-devel`, +`-pip`, `-setuptools`, `-wheel`, `-cffi`, `-cryptography` 41, `-idna`, `-pycparser`, +`-urllib3`, `-mod_wsgi`) and `nodejs` module streams 18/20/22/24. + +**Decision (user):** build a full `python3.12-*` module stack, each package ported from +its openSUSE:Factory `python-*` package, following the existing `python3-psutil`-style +EL spec template already used in `ppg:common:deps` (which targets `/usr/bin/python3.12` +on RHEL). + +Resolved closure for CPython 3.12 (cloud extras — azure/boto3/google — excluded, as +openSUSE does): **81 packages**. All but two map 1:1 to an openSUSE:Factory package +(`Flask-Security-Too` → `python-Flask-Security`; `ua-parser-builtins` has no Factory +package yet). About 6 are provided by RHEL 9's `python3.12-*` set (if present in UBI-9), +4 already exist in `ppg:common:deps` (`six`, `dateutil`, `psutil`, `click`), ~14 have C +extensions (`bcrypt` needs Rust → `cargo_vendor`). Net: ~70 new `python3.12-*` RPMs. + +### 2.3 Where `percona-obs` breaks today (`percona_obs/services.py`) + +1. Every `*.obscpio` in the service workdir is extracted and deleted before upload — + `node_modules.obscpio` would be exploded into ~1 400 loose `.tgz` files and dropped. +2. The branch-decision content check compares artifacts byte-for-byte; npm-vendored + output is a function of registry state at generation time (same class of drift as + `cargo_vendor`'s `vendor.tar.gz`, which already has a special case). +3. `/usr/lib/obs/service/node_modules` is installed neither locally nor in the obs-tools + image used by every GitHub workflow. +4. `local-npm-registry` does not exist in EPEL 9. + +## 3. Decisions taken during brainstorming + +| Topic | Decision | +|---|---| +| Python runtime | Full `python3.12-*` stack ported from openSUSE:Factory (option 2) | +| OBS project | New `ppg:devel:pgadmin` (`root/ppg/devel/pgadmin/`) holding all pgAdmin packages | +| Package name | `percona-pgadmin4` (+ `Provides: pgadmin4`); subpackages `percona-pgadmin4`, `percona-pgadmin4-doc`; no `-desktop`, `-uwsgi`, `-cloud` | +| Python deps naming | `python3.12-<name>` (existing convention via `%{python3_pkgprefix}`) | +| Interpreter | Python 3.12 (RHEL 9 AppStream), as existing EL deps already use | +| pgAdmin source | `obs_scm` from git tag `REL-9_9` (repo convention); replicate what the release tarball adds (`web/commit_hash`, neutralised `git:hash` npm script) | +| Targets | RPM only, `UBI_9` only — enforced by the project's repository list, not per-package flags | +| Node.js | `ExpandFlags: module:nodejs:22` on UBI_9; Node 22 in the obs-tools image | +| `package-lock.json` | **Generated by percona-obs at sync time** (option 2, user decision) via a repo-owned OBS-style service `npm_lockfile`; drift is tolerated in the content check and bounded by the commit-keyed service cache | +| `local-npm-registry` home | `root/common/deps/build/local-npm-registry` (generic build tool per `root/README.md`), UBI_9 only via `build:` flags | + +## 4. Decomposition + +| # | Sub-project | Deliverable | Depends on | +|---|---|---|---| +| **1** | **Tooling (this spec)** | percona-obs handles `node_modules`, generates the lockfile; obs-tools image gets the services; `root/ppg/devel/pgadmin/project.yaml`; tests; docs | — | +| 2 | `local-npm-registry` | noarch build-dep RPM ported from openSUSE `devel:languages:javascript/local-npm-registry` (release tarball via `download_url`; `dist/` + `node_modules/` are pre-bundled upstream) | 1 | +| 3 | Python 3.12 stack | ~70 `python3.12-*` RPMs ported from openSUSE:Factory, layered bottom-up; `cargo_vendor` for `bcrypt` | 1 | +| 4 | `percona-pgadmin4` | spec ported from openSUSE `pgadmin4.spec` to EL9/py3.12; `_service` = `obs_scm`(REL-9_9) → `npm_lockfile` → `node_modules`; systemd unit; httpd `mod_wsgi` snippet (`python3.12-mod_wsgi`); `-doc` | 1, 2, 3 | + +## 5. Sub-project 1 — architecture + +``` +root/ppg/devel/pgadmin/<pkg>/obs/_service + obs_scm (packaging debian/rpm) ─┐ skipped locally, copied from rpm/ + obs_scm (upstream, REL-9_9) ─┤ Phase 1 → percona-pgadmin4.obscpio + .obsinfo + npm_lockfile mode=manual ─┤ Phase 2 → package-lock.json (NEW, this repo) + node_modules mode=manual ─┤ Phase 2 → node_modules.obscpio + node_modules.spec.inc + tar / recompress / set_version ─┘ Phase 3 (buildtime, unchanged) +``` + +Components: + +1. **`tools/obs-services/npm_lockfile`** (+ `npm_lockfile.service`) — standalone service + script implementing the OBS service CLI contract. Makes "percona-obs generates the + lockfile" true while `services.py` keeps treating manual services generically. +2. **`percona_obs/services.py` / `cmd_sync.py`** — two generic fixes: manual-service + `.obscpio` outputs survive into the upload set; the `ignore_vendor` special case + becomes a per-service drift-tolerant artifact table. +3. **`.github/docker/obs-tools/Dockerfile`** — `obs-service-node-modules`, Node.js 22 + + npm, `cpio`, our `npm_lockfile` service. +4. **`root/ppg/devel/pgadmin/project.yaml`** — the new subproject, `UBI_9` only. + +Data flow for `sync push ppg:devel:pgadmin percona-pgadmin4` (once SP4 exists): Phase 1 +fetches upstream → obsinfo commit → level-2 cache lookup `.cache/services/<commit>/`; on +miss `npm_lockfile` unpacks the `.obscpio`, runs npm in `web/`, writes +`package-lock.json`; `node_modules` reads it from the shared workdir, downloads the +tarballs, writes cpio + spec.inc; all three are cached and uploaded alongside the +packaging files. OBS unpacks the cpio into `SOURCES` at build time; `local-npm-registry` +serves it to `npm` offline. + +Out of scope for SP1: package content (SP2–4), Debian support, running `node_modules` +server-side. + +## 6. The `npm_lockfile` service + +**Location:** `tools/obs-services/npm_lockfile` (Python 3, stdlib only; runs under the +image venv Python like `go_modules`, or `/usr/bin/python3` on a dev box) and +`tools/obs-services/npm_lockfile.service` (XML descriptor). + +**Parameters** + +| Param | Required | Default | Meaning | +|---|---|---|---| +| `archive` | yes | — | Glob (relative to cwd) matching the upstream source archive: `.obscpio`, `.tar`, `.tar.gz`/`.tgz`, `.tar.xz`, `.tar.zst`. Exactly one file must match. | +| `subdir` | no | `.` | Directory inside the unpacked source holding `package.json` (pgAdmin: `web`). | +| `npm-flags` | no | `--legacy-peer-deps --ignore-scripts` | Flags appended to `npm install --package-lock-only`; split on whitespace. When given, the value *replaces* the default set. | +| `outdir` | yes (OBS) | — | Standard service output directory. | + +**Behaviour** + +1. Resolve `archive`; zero or more than one match → exit 1 with a one-line stderr cause. +2. Unpack into a `tempfile.mkdtemp()` — `cpio -idm` for `.obscpio`, `tarfile` for + tarballs. Require exactly one top-level directory (the `name-version` dir that + `obs_scm`/`tar_scm` produce). +3. `cd <topdir>/<subdir>`; require `package.json`. +4. Run `npm install --package-lock-only <npm-flags>` with `npm_config_update_notifier=false`, + `npm_config_fund=false`, `npm_config_audit=false` and a fresh temporary + `npm_config_cache`, so a maintainer's `~/.npm` cannot influence resolution. Stream + stderr through; non-zero npm exit → non-zero service exit. +5. Leave any `yarn.lock` in place: npm ≥ 7 uses it as a resolution hint, pinning + upstream's tested versions — the main lever against drift. Whether npm parses + pgAdmin's Yarn-berry lockfile is a plan verification item; correctness does not + depend on it (fallback is a valid `^`-range resolution). +6. Copy `package-lock.json` to `<outdir>/package-lock.json`; require + `lockfileVersion >= 2` (what `node_modules` supports), else fail. +7. Always remove temp dirs. + +**Outputs:** exactly one file, `package-lock.json`. percona-obs uploads it, caches it +under the upstream commit, and `node_modules` — which globs `*package-lock.json` in its +cwd — finds it in the shared workdir in the next Phase-2 step. + +**Error handling:** every failure is a one-line `SystemExit`; `services.py` already turns a +non-zero manual-service exit into `error: service 'npm_lockfile' exited with N: <stderr>` +and aborts the sync, so no partial artifacts reach OBS. + +**Not doing:** honouring `packageManager`/corepack (the RPM `%prep` strips the field as +openSUSE does); pruning devDependencies (the build needs them); yarn-native lockfile +conversion. + +## 7. `percona_obs` changes + +All generic; no `pgadmin`/`node_modules` string appears in `services.py` control flow +beyond the artifact table. + +### 7.1 `_run_local_services` — manual `.obscpio` survives + +- Extraction step: `for obscpio in sorted(workdir.glob("*.obscpio"))` gains + `if obscpio.name in manual_artifacts: continue`. `manual_artifacts` already lists the + Phase-2 outputs (or the level-2 cache restore), so it is correct on hit and miss. +- Cleanup step: the `elif child.suffix == ".obscpio": child.unlink()` branch gets the + same guard. +- Phase ordering, `_copy_local_packaging` position and buildtime services are untouched. + The `tar` buildtime service keys off `*.obsinfo`, so it ignores `node_modules.obscpio` + (confirmed on a real run as a plan verification item). + +### 7.2 Drift-tolerant artifacts — generalising `ignore_vendor` + +- Replace `_has_cargo_vendor_service()` + `_VENDOR_TAR_RE` with + `services.drift_tolerant_patterns(service_file) -> list[re.Pattern]`: parses + `_service` and, for each declared service found in a small table, returns the patterns + for its outputs: + - `cargo_vendor` → `^vendor\.tar\.[a-z0-9]+$` (existing behaviour, unchanged); + - `node_modules` → the service's `cpio` and `output` param values (defaults + `node_modules.obscpio` / `node_modules.spec.inc`) and `*package-lock.json`. + Docstring rationale: these files are functions of registry state at generation time, + not of any input under git; a byte difference must not flip a branch decision to + *promote*, but absence on either side still must. +- `cmd_sync._content_mismatches(local_md5s, obs_md5s, tolerant)` takes the pattern list + instead of the bool; `_content_matches_branch` passes + `drift_tolerant_patterns(service_file) if run_services else []`. +- `_has_cargo_vendor_service` is removed (its only caller is `cmd_sync.py`). + +### 7.3 Cache — no change in keying + +- Level 2 stays `.cache/services/<upstream_commit>/`; it now also holds + `package-lock.json`, `node_modules.obscpio` (~220 MB), `node_modules.spec.inc`. + `prune_cache` (7-day unused) bounds growth; CI's `actions/cache` for `.cache/services` + grows by one such entry per pgAdmin commit — acceptable, documented. +- `--no-cache` forces regeneration and is the documented way to deliberately refresh the + vendored npm set. + +### 7.4 Upload path + +`_upload_obs_files` streams each file via `osc.connection.http_PUT(url, file=…)`. Plan +item: verify on the dev OBS that a ~220 MB PUT completes under the current +throttle/timeout settings; if not, raise the timeout for that call only. + +### 7.5 Docs + +- `docs/PERCONA_OBS_TOOL.md` "Local service execution": `npm_lockfile`/`node_modules` + in the manual-services description, the manual-`.obscpio` rule, the drift-tolerant + artifact table, local prerequisites. +- `.github/copilot-instructions.md`: extend the "Local service execution" bullets and the + key-files table (`tools/obs-services/`). + +Non-goals: changing `_copy_local_packaging` order; running `node_modules` server-side; +touching `--skip-unchanged`/manifest logic (it never inspects artifacts). + +## 8. obs-tools image and the `ppg:devel:pgadmin` project + +### 8.1 `.github/docker/obs-tools/Dockerfile` + +- Add `obs-service-node-modules` to the existing openSUSE:Tools `apt-get install` line + (package name verified in the `xUbuntu_24.04` repo index). It installs + `/usr/lib/obs/service/node_modules` with its `python3-lxml` dependency via apt; the + script runs under `/usr/bin/python3`, not the venv. +- New `RUN` block: Node.js 22 from NodeSource (`deb.nodesource.com/node_22.x`, keyring + pinned like the gh CLI block), providing `node` + `npm`. Major 22 matches the + `nodejs:22` module the packages build against; a comment says a bump here should + accompany a bump of the `ExpandFlags` module in the project config. +- `COPY tools/obs-services/npm_lockfile tools/obs-services/npm_lockfile.service + /usr/lib/obs/service/`, script mode 755. +- Add `cpio` to the base apt line (not in `ubuntu:24.04` by default; `services.py` + already shells out to it — latent gap). +- `build-obs-image.yml` `paths:` trigger gains `tools/obs-services/**`. + +### 8.2 `root/ppg/devel/pgadmin/project.yaml` + +```yaml +title: pgAdmin 4 for Percona Distribution for PostgreSQL +description: | + pgAdmin 4 and its Python 3.12 / Node.js build and runtime dependency + stack, built for UBI 9 only. Independent of the PostgreSQL major version. +repositories: + - name: UBI_9 + paths: + - subproject: ppg:common:deps # python3.12-six/dateutil/psutil/click + repository: UBI_9 + - subproject: common:deps:build # local-npm-registry (SP2) + repository: UBI_9 + - project: ${REMOTE_OBS_ORG_INTERCONNECT}Fedora:EPEL:9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RedHat:UBI-9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RockyLinux:9 + repository: devel + archs: [x86_64, aarch64] +project-config: | + %if "%_repository" == "UBI_9" + ExpandFlags: module:llvm-toolset-rhel9 + ExpandFlags: module:nodejs:22 + Prefer: hdf-libs + %endif +``` + +- Declaring only `UBI_9` enforces "UBI-9 only" for every package in SP2–4 without + per-package `build:` flags. +- `project-config` is inherited only when absent, never merged — hence the UBI_9 block is + restated with the nodejs flag added. +- Ancestor path injection adds `ppg:devel` and `ppg` UBI_9 paths automatically; both have + a UBI_9 repository via inheritance from root. +- `root/README.md`: a paragraph under the devel tier describing `devel/pgadmin/` as a + version-independent devel project. Nothing in `percona_obs` parses `devel/<name>` as an + integer (grepped), so no code impact. + +## 9. Testing & verification + +### 9.1 Unit tests (`tests/`, `venv/bin/pytest`) + +1. `tests/test_npm_lockfile.py` — the service script imported as a module (exposes + `main(argv)`): archive glob errors (0 / >1 matches); `.obscpio` and `.tar.gz` fixtures + unpack; missing `package.json` → error; monkeypatched `subprocess.run` asserts argv + (`npm install --package-lock-only --legacy-peer-deps --ignore-scripts`), cwd and env + (`npm_config_cache` in a temp dir, notifier/fund/audit off); stub npm writing a + lockfile → lands in `--outdir`; `lockfileVersion: 1` → error. No network, no real npm. +2. `tests/test_service_cache.py` (extend) — `_run_local_services` with fake service + binaries: a manual service emitting `foo.obscpio` → present in the returned workdir, + not extracted, not deleted; a Phase-1 `.obscpio` still extracted and removed; the same + guarantees on a level-2 cache hit restoring `foo.obscpio`. +3. `tests/test_vendor_content_check.py` (extend) — `drift_tolerant_patterns()`: + `cargo_vendor` regression (unchanged behaviour); `node_modules` with default and + explicit `cpio`/`output` params → the three artifacts tolerated when present on both + sides, missing on one side still a mismatch, unrelated differing file still a mismatch; + neither service → empty list. +4. `venv/bin/black percona_obs/` and `venv/bin/pyright` clean. + +### 9.2 Integration verification (manual, dev profile only — never `-P isv` without `--dry-run`) + +- Build the obs-tools image locally (`docker build` with the `GO_VERSION` arg); confirm + `/usr/lib/obs/service/{node_modules,npm_lockfile}` exist and `npm --version` works. +- With a throwaway `_service` (obs_scm → pgAdmin `REL-9_9`, `npm_lockfile`, + `node_modules`) under a scratch package dir, run `sync push --dry-run -P dev`: expect + `package-lock.json`, `node_modules.obscpio`, `node_modules.spec.inc` listed as `+`, and + a second run fully `(cached)`. This confirms the Yarn-lock hint, `tar`-service + behaviour and (with a real push to the dev OBS) the ~220 MB PUT; findings feed SP4. +- `project verify -P dev` for the new `project.yaml`; `sync push --project-only + ppg:devel:pgadmin -P dev`; check repository and flags in the dev OBS UI. + +### 9.3 Acceptance for SP1 + +Unit tests green; dry-run shows the three artifacts; image builds; project exists on the +dev OBS with only `UBI_9`. + +## 10. Risks and open verification items (carried into the plan) + +| Item | Where verified | +|---|---| +| npm honours pgAdmin's Yarn-berry `yarn.lock` as a resolution hint | **No** — verified 2026-08-26 against the real pgAdmin `REL-9_9` (commit `91ad54d17bbf`) and the real npm registry via `isv-pr --dry-run`. Only 1/5 sampled packages matched `web/yarn.lock`: `react` 18.3.1==18.3.1; `webpack` 5.109.2 vs 5.102.0; `@babel/core` 7.29.7 vs 7.28.4; `eslint` 9.39.5 vs 9.37.0; `axios` 1.20.0 vs 1.12.2 (yarn↔generated). Root cause: `npm_lockfile` runs plain `npm install` against `web/package.json`'s caret ranges (`^5.101.0`, `^18.2.0`, `^7.28.3`, `^9.37.0`, `^1.12.0`) with no reference to `yarn.lock` at all — every mismatched version still satisfies its caret range, so npm is resolving fresh against the registry, not reading the Yarn lockfile. This is a genuine reproducibility gap (vendored versions drift from what upstream tested with Yarn), not a tooling bug — flagged for SP4 to decide whether to pin via `--npm-flags`/an explicit resolutions step or accept the drift. | +| `tar` buildtime service ignores `node_modules.obscpio` | Yes — verified 2026-08-26: the `tar` service's own output was exactly `['percona-pgadmin4-9.9.tar']`; `node_modules.obscpio` was routed through the "keeping manual artifact" path and never touched by `tar`. | +| ~220 MB `http_PUT` completes under current timeouts | **7b pending** — dev OBS (`http://192.168.1.103:3000`) was unreachable on 2026-08-26 (`No route to host`); real push deferred. Local artifact confirmed present and correctly sized: `node_modules.obscpio` = 206,769,648 bytes (~197 MB), md5 `b8f0fcdd8cdf5e2251362a835ce00e07`, cached at `.cache/services/91ad54d17bbf82ae0efa277ceb88f30a3189ce4e/`. | +| UBI-9 AppStream subset actually carries `python3.12-*` extras and `nodejs:22` | SP3/SP4 first builds | +| `ua-parser-builtins` has no openSUSE:Factory package | SP3 | + +**SP1 verification run (2026-08-26, isv-pr `--dry-run` only, per the "never write ops with `-P isv` without `--dry-run`" rule; dev OBS was down so the brief's `-P dev` command was substituted with `-P isv-pr --dry-run`):** + +- Scratch `_service` (obs_scm → pgAdmin `REL-9_9` / npm_lockfile / node_modules) under + `root/ppg/devel/pgadmin/percona-pgadmin4/obs/` ran end to end against the real upstream + and the real npm registry: `obs_scm` resolved `REL-9_9` (annotated tag `8308c7f7`) to + commit `91ad54d17bbf82ae0efa277ceb88f30a3189ce4e`, `versionrewrite-pattern + REL-(\d+)_(\d+)` correctly produced `version: 9.9` in the obsinfo; `npm_lockfile` + produced `package-lock.json` (`lockfileVersion` 3, 1550 `packages` entries); + `node_modules` produced exactly `node_modules.obscpio` (207 MB) and + `node_modules.spec.inc` (1439 `Source` lines) — cloning one git-hosted dependency + (`react-data-grid`) directly rather than downloading a tarball. Final dry-run file + list was exactly the 5 expected files, all `+`, no `error:` lines. +- Artifact-set gate (`_node_modules_patterns` in `percona_obs/services.py`): the + `node_modules` service produced **only** the three tolerated names + (`node_modules.obscpio`, `node_modules.spec.inc`, and `package-lock.json` matching + the `*<input>` suffix pattern) — no `node_modules.sums`, no loose `.tgz`, no extra + manifest. The drift-tolerant table needs no correction. +- `/usr/lib/obs/service/node_modules --help` confirms the parameter names the table's + `_node_modules_patterns` comment assumes: `-i/--input FILE` (lockfile, default + `package-lock.json`), `-o/--output FILE`, `--cpio ARCHIVE` — all match. +- Second dry-run showed `= service npm_lockfile … (cached)` and `= service node_modules + … (cached)` and finished in ~37 s total (vs. the first run's several minutes for the + ~1400-tarball/git-clone download), confirming the cache path. diff --git a/docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md b/docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md new file mode 100644 index 00000000..1179242e --- /dev/null +++ b/docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md @@ -0,0 +1,434 @@ +# pgAdmin 4 on UBI-9 — sub-project 4: the `percona-pgadmin4` package design + +**Date:** 2026-08-28 +**Status:** approved in brainstorming, awaiting spec review +**Parent:** `2026-08-26-pgadmin4-tooling-design.md` (decomposition §4); depends on SP2 +(`local-npm-registry`) and SP3 (`2026-08-26-pgadmin4-py312-stack-design.md`). +**Scope of this document:** the `percona-pgadmin4` source package in `ppg:devel:pgadmin` +(UBI 9 only), the SP3 stack changes needed to move from `REL-9_9` to `REL-9_17`, and the +package's shape as the basis of a pgAdmin container image (SP5, separate spec). + +## 1. Goal + +Package pgAdmin 4 **9.17** as RPMs built from the git tag with the `node_modules` service +chain from SP1 and the Python 3.12 stack from SP3, such that (a) a UBI-9 host can run it +behind Apache httpd + mod_wsgi, (b) a UBI-9 container image (SP5) can run it under +gunicorn exactly like upstream's official image, and (c) configuration works through +`/etc/pgadmin/config_system.py` on hosts and `PGADMIN_CONFIG_*` environment variables in +containers. No `percona_obs` code changes. + +## 2. Findings that shaped the design + +### 2.1 Upstream and openSUSE packaging (facts checked 2026-08-27/28) + +- **Release artefacts:** `pkg/src/build.sh` builds `pgadmin4-<ver>.tar.gz` (the git files + plus `web/commit_hash`, with the `git:hash` npm script neutralised) and a separate + `pgadmin4-<ver>-docs.tar.gz` (Sphinx HTML). Only the source tarball + `.asc` are + published, and the download server keeps just the last seven releases (v9.11–v9.17 on + 2026-08-27) — **9.9 is no longer downloadable**. Building from the git tag via `obs_scm` + (SP1 decision) is what makes any given version reproducible. +- **openSUSE Factory `pgadmin4` 9.17:** builds from the tarball; `local-npm-registry + %{_sourcedir} install --legacy-peer-deps --ignore-scripts` in `web/`, `npx eslint` + + `npx webpack` (production), wheel via `pkg/pip/setup_pip.py bdist_wheel` with a + `config_distro.py` written in `%build`, `%pyproject_install`; subpackages `doc` + (copies `docs/*` — **the reStructuredText sources, no HTML**; its `HTML_HELP` setting is + not a pgAdmin option, so the in-app help link is dead there), `web-uwsgi`, `desktop`, + `system-user-pgadmin` (sysusers), optional `cloud`. Patches: `use-os-makedirs`, + `make-cloud-packages-optional`, `fix-reproducible-builds`, `package_git_local` + (pins `react-data-grid` to a published beta instead of the git URL), two stale ones. + Runtime: systemd unit running `pgAdmin4.py` (Flask's built-in server) as user `pgadmin`, + httpd `conf.d` snippet behind `<IfDefine PGADMIN>`, tmpfiles `/run/pgadmin4`, dirs + `/var/lib/pgadmin{,/storage,/sessions}`, `/var/log/pgadmin`, `/etc/pgadmin/config_system.py`. +- **Upstream RHEL RPMs** (`pkg/redhat/build.sh`): a venv under `/usr/pgadmin4` (rejected + by the SP1 decision), `pgadmin4-web` = httpd conf (`WSGIDaemonProcess pgadmin + processes=1 threads=25`, `WSGIScriptAlias /pgadmin4 …/pgAdmin4.wsgi`) + + `setup-web.sh` (runs `setup.py setup-db`, chowns dirs to `apache`, sets SELinux booleans + `httpd_tmp_exec`, `httpd_can_network_connect`, `httpd_can_network_connect_db` and + fcontexts `httpd_var_lib_t` for `/var/lib/pgadmin`, `httpd_log_t` for `/var/log/pgadmin`, + enables httpd). +- **Upstream container** (`pkg/docker/`): runs **gunicorn** (`-w 1 --threads 25 + --bind [::]:80|443 --timeout <session expiry> --access-logfile - -c gunicorn_config.py + run_pgadmin:app`, TLS via `/certs/server.{key,cert}` when `PGADMIN_ENABLE_TLS`), + `run_pgadmin.py` = `builtins.SERVER_MODE = True; from pgAdmin4 import app`, + `gunicorn_config.py` (JSON logging to stdout when `JSON_LOGGER`), entrypoint that + appends every `PGADMIN_CONFIG_<NAME>=<value>` env var as `<NAME> = <value>` to + `config_distro.py` (`true`/`false` normalised to Python), maps + `PGADMIN_DEFAULT_EMAIL/PASSWORD` → pgAdmin's `PGADMIN_SETUP_EMAIL/PASSWORD` for + `setup-db`, optional `servers.json` import, same `/var/lib/pgadmin` data dir. +- **pgAdmin internals (9.17):** `web/config.py` in `SERVER_MODE` uses `DATA_DIR + /var/lib/pgadmin`, `LOG_FILE /var/log/pgadmin/pgadmin4.log`, `SQLITE_PATH + <DATA_DIR>/pgadmin4.db`; `HELP_PATH` is a Flask *static folder* (a local directory — + it cannot be a URL); the Help menu's "Online Help" entry is `url_for('help.static', + filename='index.html')`; `DEFAULT_BINARY_PATHS` keys `pg`, `pg-13`…`pg-18`, + `ppas*`; `config.py` imports `pgadmin.utils` and, via `evaluate_config`, `keyring`; + `setup-db` is non-interactive when `PGADMIN_SETUP_EMAIL` and `PGADMIN_SETUP_PASSWORD` + are set; `web/package.json` has `"packageManager": "yarn@4.15.0"` (must go for npm) and + the `bundle` script ends with `git:hash`. `python3.12-mod_wsgi` (UBI-9 AppStream 4.9.4) + installs `/usr/lib64/httpd/modules/mod_wsgi_python3.so` and + `/etc/httpd/conf.modules.d/10-wsgi-python3.conf` (no `LoadModule` needed). + +### 2.2 REL-9_9 → REL-9_17 closure delta (uv, Python 3.12, cloud extras excluded) + +83 packages vs 80. **New:** `annotated-doc` 0.0.5 (typer), `certifi` 2026.6.17 (direct +requirement), `joserfc` 1.7.4 (Authlib 1.7; needs `cryptography >= 45.0.1`), `libpass` +1.9.3 (Flask-Security-Too 5.8; a passlib fork that installs the **`passlib`** module). +**Gone:** `importlib-resources`. **Bumped:** Authlib 1.7.2, Flask-Security-Too 5.8.2, +Flask-SocketIO 5.6.1, Flask-WTF 1.3.0, gssapi 1.11.1 (pins `Cython == 3.2.4`), +psycopg/psycopg-c 3.3.4, pytz 2026.3.post1, typer 0.26.8, and — reused from RHEL, not +built — cryptography 49, setuptools 83, urllib3 2.7, psutil 7.2. + +### 2.3 Sphinx cost (rejected option, kept for the record) + +Building the HTML docs needs Sphinx on Python 3.12 (docs `conf.py` imports pgAdmin's +`config`): 13 pure-Python build-only packages (sphinx 9.1, docutils, alabaster, imagesize, +roman-numerals, snowballstemmer, six `sphinxcontrib-*` helpers, `sphinxcontrib-youtube`). + +## 3. Decisions taken during brainstorming + +| Topic | Decision | +|---|---| +| Version | **REL-9_17** (current). SP3 packages are bumped first (§6). Monthly bumps become routine follow-ups. | +| Docs / help | **openSUSE-style:** `-doc` ships the `.rst` sources; a patch points the Help menu's "Online Help" at `https://www.pgadmin.org/docs/pgadmin4/<release>.<revision>/`; `HELP_PATH` points at the `-doc` directory so the static route serves something. No Sphinx stack. | +| Primary runtime | **Containers (SP5): gunicorn**, like upstream's image. **Hosts: httpd + mod_wsgi** (upstream's RHEL packaging) or the systemd unit — which now runs **gunicorn**, not Flask's dev server (refinement of the earlier "systemd unit shipped disabled" idea: same shipped-disabled unit, production-grade server). | +| Package split (container-driven) | `percona-pgadmin4` (code, user, dirs, config; no web-server dependency) + `percona-pgadmin4-gunicorn` (launcher, gunicorn config, systemd unit; `Requires: python3.12-gunicorn`) + `percona-pgadmin4-httpd` (conf.d snippet, setup helper; `Requires: httpd, python3.12-mod_wsgi`) + `percona-pgadmin4-doc`. An image installs base + `-gunicorn`; a host installs base + `-httpd` or `-gunicorn`. | +| Configuration | `config_distro.py` (Percona defaults, §5.3) **also applies `PGADMIN_CONFIG_<NAME>` environment variables at import time** (upstream's container semantics, done in Python so it works for services too); `/etc/pgadmin/config_system.py` (`%config(noreplace)`) for host overrides. | +| Authlib | **Stay on 1.6.12** (no `joserfc`, which would need cryptography ≥ 45 — RHEL has 41 and building cryptography means Rust + maturin, avoided by the SP3 decision). pgAdmin's `Authlib==1.7.*` pin is relaxed like its cryptography/urllib3/setuptools pins. Recorded deviation; revisit when RHEL's cryptography moves. | +| passlib → libpass | Flask-Security-Too 5.8 requires `libpass`, which installs the `passlib` module: package `python3-libpass` with `Provides: %{python3_pkgprefix}-passlib` and `Conflicts: %{python3_pkgprefix}-passlib < 1.9`, and remove `python3-passlib`. | +| Frontend lockfile drift | SP1 ruling stands: `package-lock.json` is generated at sync time; npm's resolution drift from upstream's `yarn.lock` is accepted (bounded by the per-commit service cache). | +| Not shipped | `-desktop`, `-uwsgi`, cloud support (azure/boto3/google), Debian, HTML docs, upgrading cryptography. | +| Names | `Name: percona-pgadmin4`, `Provides: pgadmin4 = %{version}`; user/group `pgadmin`; URL path `/pgadmin4`; unit `percona-pgadmin4.service`. | + +## 4. Package layout + +``` +root/ppg/devel/pgadmin/percona-pgadmin4/ +├── package.yaml +├── obs/_service +└── rpm/ + ├── percona-pgadmin4.spec + ├── config_distro.py # Percona defaults + PGADMIN_CONFIG_* env overrides + ├── run_pgadmin.py # WSGI entry for gunicorn (SERVER_MODE, app) + ├── gunicorn_config.py # upstream's, adapted (stdout logging, JSON option) + ├── percona-pgadmin4-gunicorn # launcher script (/usr/bin) + ├── percona-pgadmin4.service # systemd unit → the launcher, user pgadmin + ├── percona-pgadmin4.sysusers # u pgadmin - "pgAdmin 4" /var/lib/pgadmin + ├── percona-pgadmin4.tmpfiles # d /run/pgadmin4 0755 pgadmin pgadmin - + ├── percona-pgadmin4-httpd.conf # /etc/httpd/conf.d snippet + ├── percona-pgadmin4-setup-web # setup-db + SELinux + httpd helper (/usr/bin) + ├── 0001-help-menu-online-docs.patch + ├── 0002-make-cloud-packages-optional.patch # from openSUSE + └── 0003-use-os-makedirs.patch # from openSUSE +``` + +### 4.1 `obs/_service` + +```xml +<services> + <service name="obs_scm"> + <param name="url">https://github.com/pgadmin-org/pgadmin4.git</param> + <param name="scm">git</param> + <param name="revision">REL-9_17</param> + <param name="versionformat">@PARENT_TAG@</param> + <param name="versionrewrite-pattern">REL-(\d+)_(\d+)</param> + <param name="versionrewrite-replacement">\1.\2</param> + <param name="filename">percona-pgadmin4</param> + </service> + <service name="npm_lockfile" mode="manual"> + <param name="archive">percona-pgadmin4-*.obscpio</param> + <param name="subdir">web</param> + <param name="npm-flags">--legacy-peer-deps</param> + </service> + <service name="node_modules" mode="manual"> + <param name="cpio">node_modules.obscpio</param> + <param name="output">node_modules.spec.inc</param> + <param name="source-offset">10000</param> + </service> + <service mode="buildtime" name="tar"/> + <service mode="buildtime" name="recompress"><param name="file">*.tar</param><param name="compression">gz</param></service> + <service mode="buildtime" name="set_version"/> +</services> +``` +The exact `archive`/`npm-flags` parameter values are those SP1 verified end to end against +`REL-9_9`; the plan re-runs the dry run at `REL-9_17` before anything is pushed. Uploaded +set: `percona-pgadmin4-9.17.obscpio` + `.obsinfo`, `package-lock.json`, +`node_modules.obscpio` (~210 MB), `node_modules.spec.inc` — the last three are +drift-tolerant in the branch content check (SP1). + +### 4.2 `package.yaml` + +Title/description only (UBI_9-only project, no `build:` flags); description states the +four subpackages and that the image (SP5) consumes base + `-gunicorn`. + +## 5. The spec + +### 5.1 Header + +`Name: percona-pgadmin4`, `Version: 1.0.0` (placeholder rewritten by `set_version` from +the obsinfo — repo convention for `obs_scm` packages), `Release: 1%{?dist}`, +`Summary: pgAdmin 4 — management tool for PostgreSQL (server mode)`, `License: PostgreSQL`, +`URL: https://www.pgadmin.org`, `BuildArch: noarch`, `Provides: pgadmin4 = %{version}`. +`Source0: percona-pgadmin4-%{version}.tar.gz`; `Source1..12` the files in `rpm/`; +`Source20: package-lock.json`; `Source100: node_modules.spec.inc`; +`%include %{_sourcedir}/node_modules.spec.inc` (brings the `Source10000+` npm tarballs +that OBS unpacks from `node_modules.obscpio` into `%{_sourcedir}`). + +The same `python3.12` preamble as the SP3 template (`__ospython`, `python3_pkgprefix`, +`python3_sitelib`) so `%{python3_pkgprefix}-<name>` resolves the stack by name. + +**BuildRequires:** `python%{python3_buildversion}-devel/-pip/-setuptools/-wheel`, +`local-npm-registry`, `nodejs >= 20`, `npm`, `systemd-rpm-macros`, plus every runtime +`Requires` below (so the `%check` import works). **Requires** (base package): +`%{python3_pkgprefix}-{authlib,bcrypt,certifi,flask,flask-babel,flask-compress,flask-login, +flask-mail,flask-migrate,flask-paranoid,flask-security-too,flask-socketio,flask-sqlalchemy, +flask-wtf,gssapi,jsonformatter,keyring,ldap3,libgravatar,libpass,paramiko,psycopg,psycopg-c, +pyotp,pytz,qrcode,sqlalchemy,sqlparse,sshtunnel,typer,user-agents,werkzeug,wtforms,psutil, +dateutil}` and `python3.12-{cryptography,urllib3,setuptools}` (RHEL, no floors); +`Requires(pre): shadow-utils` (sysusers compat). `Suggests: percona-pgadmin4-doc`. + +### 5.2 Subpackages + +| Subpackage | Contents | Requires | +|---|---|---| +| `percona-pgadmin4-gunicorn` | `/usr/bin/percona-pgadmin4-gunicorn`, `%{python3_sitelib}/pgadmin4/{run_pgadmin,gunicorn_config}.py`, `%{_unitdir}/percona-pgadmin4.service` (not enabled), `%{_tmpfilesdir}/percona-pgadmin4.conf` | `percona-pgadmin4 = %{version}-%{release}`, `%{python3_pkgprefix}-gunicorn`; `%systemd_post/preun/postun_with_restart` scriptlets | +| `percona-pgadmin4-httpd` | `%config(noreplace) /etc/httpd/conf.d/percona-pgadmin4.conf`, `/usr/bin/percona-pgadmin4-setup-web` | `percona-pgadmin4 = %{version}-%{release}`, `httpd`, `python3.12-mod_wsgi`; `Recommends: policycoreutils-python-utils` | +| `percona-pgadmin4-doc` | `/usr/share/doc/percona-pgadmin4/en_US/` (the `docs/en_US` tree: `*.rst`, `images/`, `theme/`) | — | + +### 5.3 `%prep` + +`%autosetup -p1 -n percona-pgadmin4-%{version}`, then: +- write `web/commit_hash` from the obsinfo (`grep ^commit: %{_sourcedir}/*.obsinfo`) and + `sed` the `git:hash` script in `web/package.json` to `exit 0` (what the release tarball + does); delete the `"packageManager": "yarn@…"` line; +- `sed -i 's,/usr/bin/env python3,%{__ospython},' web/pgacloud/pgacloud.py`; `chmod -x` + fonts, `docs/en_US/theme/pgadmin4/static/style.css`, `theme.conf`, + `web/pgadmin/misc/bgprocess/process_executor.py`, `web/*/*/*/*.js` (openSUSE's rpmlint set); +- `cd web && local-npm-registry %{_sourcedir} install --legacy-peer-deps --ignore-scripts` + (offline `npm install` from the unpacked tarballs; if the git-sourced `react-data-grid` + cannot be served this way, carry openSUSE's `package_git_local.patch`). + +### 5.4 `%build` + +- `cd web && NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072 npx webpack + --config webpack.config.js` (eslint pass skipped — lint, not build); + `rm -rf node_modules package-lock.json yarn.lock`. +- Wheel as openSUSE: `mkdir -p pip-build/pgadmin4 && cp -a web/* pip-build/pgadmin4`, + `echo 'recursive-include pgadmin4 *' > pip-build/MANIFEST.in`, delete `.gitignore`/ + `.coverage*`, copy `%{SOURCE_config_distro}` to `pip-build/pgadmin4/config_distro.py`, + `%{SOURCE_run_pgadmin}` and `%{SOURCE_gunicorn_config}` next to it, + `cd pip-build && %{__ospython} ../pkg/pip/setup_pip.py bdist_wheel`. + +**`config_distro.py` (installed as `%{python3_sitelib}/pgadmin4/config_distro.py`):** +```python +import ast, os +SERVER_MODE = True +MINIFY_HTML = False +UPGRADE_CHECK_ENABLED = False +HELP_PATH = '/usr/share/doc/percona-pgadmin4/en_US' +DEFAULT_BINARY_PATHS = { + "pg": "/usr/pgsql-18/bin", + "pg-13": "/usr/pgsql-13/bin", "pg-14": "/usr/pgsql-14/bin", "pg-15": "/usr/pgsql-15/bin", + "pg-16": "/usr/pgsql-16/bin", "pg-17": "/usr/pgsql-17/bin", "pg-18": "/usr/pgsql-18/bin", +} +# Container-style overrides: PGADMIN_CONFIG_<NAME>=<python literal>; true/false accepted. +for _k, _v in os.environ.items(): + if _k.startswith('PGADMIN_CONFIG_'): + _lit = {'true': 'True', 'false': 'False'}.get(_v.strip().lower(), _v) + try: + globals()[_k[len('PGADMIN_CONFIG_'):]] = ast.literal_eval(_lit) + except (ValueError, SyntaxError): + globals()[_k[len('PGADMIN_CONFIG_'):]] = _v +``` +Precedence (pgAdmin's own): `config.py` < `config_distro.py` < `config_local.py` < +`/etc/pgadmin/config_system.py`. Env overrides therefore sit at the distro level; host +admins still win via `config_system.py`, which is what a host wants and what a container +never has. + +### 5.5 `%install` + +`%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} +pip-build/dist/*.whl` → `%{python3_sitelib}/pgadmin4/` (incl. `pgAdmin4.wsgi`, +`config_distro.py`, `run_pgadmin.py`, `gunicorn_config.py`), `%{_bindir}/pgadmin4`, +`%{_bindir}/pgadmin4-cli`. Then: `install -m 0755` the two scripts to `%{_bindir}`; +unit → `%{_unitdir}`; tmpfiles → `%{_tmpfilesdir}`; sysusers → `%{_sysusersdir}`; httpd +conf → `%{_sysconfdir}/httpd/conf.d/`; `install -d` `/var/lib/pgadmin` (0750), +`/var/lib/pgadmin/storage` (0755), `/var/lib/pgadmin/sessions` (0700), `/var/log/pgadmin` +(0750), `/etc/pgadmin` (0750) with `config_system.py` containing only +`# Site overrides for pgAdmin 4 (see config.py); SERVER_MODE is set in config_distro.py`; +docs: `cp -pr docs/en_US` → `%{_docdir}/percona-pgadmin4/en_US` (`.gitignore` removed); +`%{__ospython} -P -c` byte-compile is left to brp. + +### 5.6 Scriptlets, users, directories + +- `%pre` (base): `%sysusers_create_compat %{SOURCE_sysusers}` → `pgadmin` system user/group, + home `/var/lib/pgadmin`, no shell login (works without systemd — plain `useradd` — so + container image builds succeed). +- `%files` (base): `%license LICENSE`, `%doc README.md`, `%{_bindir}/pgadmin4{,-cli}`, + `%{python3_sitelib}/pgadmin4/` (excluding the two gunicorn files, which belong to + `-gunicorn`), `%{python3_sitelib}/pgadmin4-*.dist-info/`, + `%dir %attr(0750,root,pgadmin) /etc/pgadmin`, `%config(noreplace) %attr(0640,root,pgadmin) + /etc/pgadmin/config_system.py`, `%dir %attr(0750,pgadmin,pgadmin) /var/lib/pgadmin`, + `%dir %attr(0755,pgadmin,pgadmin) /var/lib/pgadmin/storage`, `%dir %attr(0700,pgadmin,pgadmin) + /var/lib/pgadmin/sessions`, `%dir %attr(0750,pgadmin,pgadmin) /var/log/pgadmin`, + `%{_sysusersdir}/percona-pgadmin4.conf`. +- `-gunicorn`: `%post %systemd_post percona-pgadmin4.service` + `%tmpfiles_create`; + `%preun %systemd_preun`; `%postun %systemd_postun_with_restart`; `%ghost %dir + %attr(0755,pgadmin,pgadmin) /run/pgadmin4`. +- No scriptlet runs `setup-db`, touches SELinux or enables httpd (admin- or entrypoint-driven). + +### 5.7 The gunicorn launcher (`/usr/bin/percona-pgadmin4-gunicorn`) + +POSIX sh, mirrors upstream's entrypoint exec line so SP5's entrypoint can just call it: +```sh +#!/bin/sh +# Serve pgAdmin 4 with gunicorn. Environment (all optional, upstream-compatible names): +# PGADMIN_LISTEN_ADDRESS (default 127.0.0.1), PGADMIN_LISTEN_PORT (default 5050), +# PGADMIN_ENABLE_TLS (set → bind 0.0.0.0:443 with /certs/server.key + server.cert), +# GUNICORN_THREADS (25), GUNICORN_ACCESS_LOGFILE (-), GUNICORN_LIMIT_REQUEST_LINE (8190), +# PGADMIN_CONFIG_* (see config_distro.py). +set -e +APP=/usr/lib/python3.12/site-packages/pgadmin4 +cd "$APP" +TIMEOUT=$(/usr/bin/python3.12 -P -c 'import config; print(config.SESSION_EXPIRATION_TIME * 86400)') +if [ -n "${PGADMIN_ENABLE_TLS}" ]; then + BIND="${PGADMIN_LISTEN_ADDRESS:-0.0.0.0}:${PGADMIN_LISTEN_PORT:-443}" + TLS="--keyfile /certs/server.key --certfile /certs/server.cert" +else + BIND="${PGADMIN_LISTEN_ADDRESS:-127.0.0.1}:${PGADMIN_LISTEN_PORT:-5050}"; TLS="" +fi +exec /usr/bin/gunicorn-3.12 --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" \ + --limit-request-fields "${GUNICORN_LIMIT_REQUEST_FIELDS:-100}" \ + --limit-request-field_size "${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:-8190}" \ + --timeout "$TIMEOUT" --bind "$BIND" -w 1 --threads "${GUNICORN_THREADS:-25}" \ + --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" $TLS -c gunicorn_config.py run_pgadmin:app +``` +(`gunicorn-3.12` is the console script name the `python3-gunicorn` package installs; +the plan fixes the exact name from the built RPM.) Host defaults bind `127.0.0.1:5050` +(no privileged port for user `pgadmin`); the image (SP5) sets address/port via env. +`percona-pgadmin4.service`: `User=pgadmin`, `Group=pgadmin`, `ExecStart=/usr/bin/percona-pgadmin4-gunicorn`, +`Environment=PYTHONDONTWRITEBYTECODE=1`, `Restart=on-failure`, `RuntimeDirectory=pgadmin4`, +`WantedBy=multi-user.target`. `gunicorn_config.py` = upstream's (`SERVER_SOFTWARE`, access +log format, JSON logging when `JSON_LOGGER`), `run_pgadmin.py` = upstream's two lines. + +### 5.8 httpd (`-httpd`) + +`/etc/httpd/conf.d/percona-pgadmin4.conf`: +```apache +WSGIDaemonProcess pgadmin user=pgadmin group=pgadmin processes=1 threads=25 python-path=/usr/lib/python3.12/site-packages/pgadmin4 +WSGIScriptAlias /pgadmin4 /usr/lib/python3.12/site-packages/pgadmin4/pgAdmin4.wsgi +<Directory /usr/lib/python3.12/site-packages/pgadmin4/> + WSGIProcessGroup pgadmin + WSGIApplicationGroup %{GLOBAL} + Require all granted +</Directory> +``` +`percona-pgadmin4-setup-web` (root): (1) `runuser -u pgadmin -- /usr/bin/pgadmin4-cli +setup-db` (non-interactive with `PGADMIN_SETUP_EMAIL`/`PGADMIN_SETUP_PASSWORD`, else +prompts); (2) if `selinuxenabled`: `setsebool -P httpd_can_network_connect on +httpd_can_network_connect_db on httpd_tmp_exec on`, `semanage fcontext -a -t httpd_var_lib_t +'/var/lib/pgadmin(/.*)?'`, `-t httpd_log_t '/var/log/pgadmin(/.*)?'`, `restorecon -R` +(skips with a notice if `semanage` is missing); (3) if systemd is running (`[ -d /run/systemd/system ]`) +and `--no-service` was not given: `systemctl enable --now httpd` (or `reload`); prints the URL +`http://<hostname>/pgadmin4/`. Because both httpd (via `WSGIDaemonProcess user=pgadmin`) and +gunicorn run as `pgadmin`, the data directory never changes owner between modes. + +### 5.9 Patches + +1. `0001-help-menu-online-docs.patch`: in `web/pgadmin/help/__init__.py` the "Online Help" + item's `url` becomes `'https://www.pgadmin.org/docs/pgadmin4/%s.%s/' % (config.APP_RELEASE, + config.APP_REVISION)`; the `help.static` blueprint stays (serving `HELP_PATH`). +2. openSUSE `make-cloud-packages-optional.patch` (cloud module import guarded — we ship no + azure/boto3/google). +3. openSUSE `use-os-makedirs.patch`. +Refreshed or dropped during the build loop if they no longer apply to 9.17. Not carried: +`fix-python3-crypto-call` (obsolete), `fix-reproducible-builds` (optional determinism), +`support-new-azure-mgmt-rdbms` (cloud), `package_git_local` (only if §5.3's fallback triggers). + +### 5.10 `%check` + +`PYTHONPATH=%{buildroot}%{python3_sitelib}/pgadmin4 %{__ospython} -P -c "import config, +run_pgadmin"` — imports pgAdmin's `config` (→ `pgadmin.utils`, `keyring`, `branding`, +`config_distro`) and the WSGI entry (→ `pgAdmin4.app`, the whole Flask app graph). This +proves the stack (with Authlib 1.6.12) loads, without a database or network. If +`run_pgadmin` needs a writable data dir at import, the check sets +`PGADMIN_CONFIG_DATA_DIR=%{_builddir}/pgadmin-data` through the new env mechanism. + +## 6. SP3 stack changes for REL-9_17 (first plan task) + +| Change | Package | Detail | +|---|---|---| +| keep | `python3-authlib` 1.6.12 | see §3 (Authlib ruling) | +| bump | `python3-flask-security-too` | 5.6.2 → 5.8.2; `Requires` gain `%{python3_pkgprefix}-libpass >= 1.9.3`, `flask >= 3.1.1`, `email-validator >= 2.3.0`; drop `passlib`, `importlib-resources` | +| bump | `python3-flask-socketio` | 5.5.1 → 5.6.1 | +| bump | `python3-flask-wtf` | 1.2.2 → 1.3.0 | +| bump | `python3-gssapi` | 1.10.1 → 1.11.1 (`Cython == 3.2.4`) | +| bump | `python3-cython` | 3.1.3 → 3.2.4 | +| bump | `python3-psycopg`, `python3-psycopg-c` | 3.2.10 → 3.3.4 (lock-step) | +| bump | `python3-pytz` | 2025.2 → 2026.3.post1 | +| bump | `python3-typer` | 0.19.2 → 0.26.8; `Requires` gain `annotated-doc >= 0.0.2`, `rich >= 13.8.0` | +| new | `python3-annotated-doc` 0.0.5 | pure, pdm-backend | +| new | `python3-certifi` 2026.6.17 | pure, setuptools (MPL-2.0); direct pgAdmin requirement | +| new | `python3-libpass` 1.9.3 | pure, hatchling; `Provides: %{python3_pkgprefix}-passlib = 1.9.3`, `Conflicts: %{python3_pkgprefix}-passlib < 1.9`; `%check` imports `passlib` | +| new | `python3-gunicorn` 26.2.0 | pure, setuptools (MIT); no runtime deps; ships the `gunicorn` console script | +| remove | `python3-passlib`, `python3-importlib-resources` | superseded / unused | +| reuse (unchanged) | cryptography 41, setuptools 68, urllib3 1.26, psutil 6.1.1 | pgAdmin pins 49 / 83 / 2.7 / 7.2 relaxed as in SP3 | + +All follow the SP3 template and rules (`python -P` in `%check`, PEP 639 `%prep` sed where +the new sdist needs it, runtime deps mirrored as BuildRequires, PyPI sdist via +`download_url`). Result: 74 packages in `ppg:devel:pgadmin` before `percona-pgadmin4`. +The SP3 spec's §4 inventory is updated in the same commit. + +## 7. Documentation + +- `root/README.md`: the `devel/pgadmin/` paragraph names `percona-pgadmin4` and its + subpackages, the three-service source chain, and the container relationship (SP5). +- `docs/PERCONA_OBS_TOOL.md` "Vendoring npm dependencies": one paragraph of real-package + facts (artifact sizes, first-sync duration, cache behaviour) once measured. +- The `percona-pgadmin4` spec header comment documents the bump procedure (change + `revision`; re-check `requirements.txt` against the stack). +- SP3 spec §4/§8 updated for the 9.17 changes; this spec gets a §9 outcomes section after + the build loop; PR #12 body gains an SP4 section. + +## 8. Verification + +**Local, read-only:** `rpmspec -P` on the rendered spec; `sync push --dry-run -P isv-pr +ppg:devel:pgadmin percona-pgadmin4` (runs `obs_scm` → `npm_lockfile` → `node_modules` for +real at `REL-9_17`; expect the 5-file upload set; first run downloads ~1 400 npm tarballs, +later runs are cached); dry runs of two bumped SP3 packages. + +**OBS via PR #12 (each step its own push, approved individually):** +1. SP3 changes (§6) → `ppg:devel:pgadmin` green again (74 packages × 2 archs). +2. `percona-pgadmin4` → `succeeded` on UBI_9 (noarch; both archs build). RPM content + checks (from the public `_binary` listing or `rpm -qlp` of the downloaded RPM): + `%{python3_sitelib}/pgadmin4/pgAdmin4.wsgi`, `/usr/bin/pgadmin4`, `/usr/bin/pgadmin4-cli`, + `/usr/bin/percona-pgadmin4-gunicorn`, `/usr/bin/percona-pgadmin4-setup-web`, the unit, + sysusers, tmpfiles, httpd conf, `-doc`'s `en_US/` tree. + +**Smoke test with OBS-built RPMs (proposed; consumes OBS artefacts, runs outside OBS):** +in a UBI-9 container with the PR project's UBI_9 repo enabled: install `percona-pgadmin4 +percona-pgadmin4-gunicorn`, `PGADMIN_SETUP_EMAIL=… PGADMIN_SETUP_PASSWORD=… runuser -u pgadmin +-- pgadmin4-cli setup-db`, `PGADMIN_LISTEN_ADDRESS=0.0.0.0 PGADMIN_LISTEN_PORT=8080 runuser -u +pgadmin -- percona-pgadmin4-gunicorn &`, `curl -s -o /dev/null -w '%{http_code}' +http://localhost:8080/login` → `200`; and a second container with `-httpd`: +`percona-pgadmin4-setup-web --no-service`, `httpd -DFOREGROUND &`, `curl …/pgadmin4/login` +→ `200`. This is the acceptance for "the wiring works"; it is also the dry run of SP5's +entrypoint. + +## 9. Risks + +| Risk | Mitigation | +|---|---| +| `local-npm-registry install` cannot serve the git-sourced `react-data-grid` | carry openSUSE's `package_git_local.patch` (published beta); SP1 saw the `node_modules` service clone it, so the tarball is in the cpio. | +| webpack memory/time on OBS workers | `NODE_OPTIONS=--max-old-space-size=3072`; workers have ≥ 4 GB; nodejs:22 stream. | +| Authlib 1.6.12 vs pgAdmin 9.17 | `%check` imports the app graph; the container smoke test logs in. If 1.7-only APIs are used: bump Authlib, add `joserfc`, and decide cryptography (49 via Rust) then. | +| `import run_pgadmin` in `%check` wants a writable `DATA_DIR`/log | `PGADMIN_CONFIG_DATA_DIR`/`LOG_FILE` env overrides in `%check` (the new mechanism). | +| Gunicorn console-script name (`gunicorn` vs `gunicorn-3.12`) | fixed from the built `python3-gunicorn` RPM before the launcher is final. | +| 210 MB `node_modules.obscpio` upload/time | measured in SP1's dry run; first real push happens in step 2 — a timeout makes SP1 §10's deferred chunked-md5/upload work a task. | +| SELinux on bare-metal hosts | the helper applies upstream's booleans/fcontexts; containers are permissive; an enforcing-host test is a QA follow-up. | +| OBS rebuild storms (DoD path-repo refreshes) | expected, as in SP3; nothing to do. | + +## 10. Out of scope + +`-desktop`, `-uwsgi`, cloud support, Debian/Ubuntu, HTML documentation, upgrading +cryptography/setuptools/urllib3 beyond RHEL's, the container image itself (SP5: Dockerfile, +entrypoint mapping `PGADMIN_DEFAULT_EMAIL/PASSWORD` → `setup-db`, `servers.json` import, +TLS certificate handling). diff --git a/percona_obs/cmd_sync.py b/percona_obs/cmd_sync.py index 0e2220d0..68ca030f 100644 --- a/percona_obs/cmd_sync.py +++ b/percona_obs/cmd_sync.py @@ -90,9 +90,9 @@ ) from .services import ( _copy_local_packaging, - _has_cargo_vendor_service, _has_runnable_services, _run_local_services, + drift_tolerant_patterns, upstream_scm_refs, ) from .targets import ( @@ -120,9 +120,6 @@ # layout ("…:containers") intentionally does not match and is never filtered. _CONTAINER_SUBPROJ_RE = re.compile(r":containers:([^:]+)$") -# Matches the vendored-crates archive produced by the cargo_vendor service. -_VENDOR_TAR_RE = re.compile(r"^vendor\.tar\.[a-z0-9]+$") - _OBS_SUBSTITUTABLE = {"_service", "_aggregate", "_link"} @@ -442,27 +439,26 @@ def _is_link_package(obs_dir: Path) -> bool: def _content_mismatches( local_md5s: dict[str, str], obs_md5s: dict[str, str], - ignore_vendor: bool, + tolerant: list[re.Pattern[str]], ) -> list[str]: """Return the file names whose content differs between local and OBS. - With *ignore_vendor*, a vendor.tar.* present on both sides is compared by - presence only, never by bytes. cargo_vendor output is a function of the - other uploaded files plus the crates.io state at generation time: upstream - projects without a committed Cargo.lock (e.g. pgvectorscale) re-resolve - the crate graph on every run, so its bytes drift between machines and - dates even when no real input changed. A vendor-only difference must - therefore not flip a branch decision to promote. A vendor archive + Files matching a *tolerant* pattern (see services.drift_tolerant_patterns) + and present on both sides are compared by presence only, never by bytes: + their content is a function of an external registry's state at generation + time — cargo_vendor re-resolving crates for a project without Cargo.lock, + npm re-resolving ranges for node_modules — so byte drift between machines + and dates must not flip a branch decision to promote. A tolerant file missing from either side is still a mismatch. """ mismatches = [name for name, md5 in local_md5s.items() if obs_md5s.get(name) != md5] mismatches += [name for name in obs_md5s if name not in local_md5s] - if ignore_vendor: + if tolerant: both = set(local_md5s) & set(obs_md5s) mismatches = [ name for name in mismatches - if not (_VENDOR_TAR_RE.match(name) and name in both) + if not (name in both and any(p.search(name) for p in tolerant)) ] return sorted(mismatches) @@ -577,7 +573,7 @@ def _content_matches_branch( mismatches = _content_mismatches( local_md5s, obs_md5s, - ignore_vendor=run_services and _has_cargo_vendor_service(service_file), + tolerant=drift_tolerant_patterns(service_file) if run_services else [], ) for fname in mismatches: detail = "differs" if fname in local_md5s else "in OBS but not local" diff --git a/percona_obs/services.py b/percona_obs/services.py index ad4cec56..804b82f3 100644 --- a/percona_obs/services.py +++ b/percona_obs/services.py @@ -5,6 +5,7 @@ import subprocess import tempfile import time +from typing import Callable import xml.etree.ElementTree as ET from pathlib import Path @@ -76,13 +77,57 @@ def _has_runnable_services(service_file: Path) -> bool: ) -def _has_cargo_vendor_service(service_file: Path) -> bool: - """Return True if *service_file* declares a cargo_vendor service.""" +def _cargo_vendor_patterns(svc: ET.Element) -> list[re.Pattern[str]]: + """vendor.tar.<compression> — the crate tree cargo_vendor downloads.""" + return [re.compile(r"^vendor\.tar\.[a-z0-9]+$")] + + +def _node_modules_patterns(svc: ET.Element) -> list[re.Pattern[str]]: + """The npm tarball cpio, its Source-line include, and the npm lockfile. + + Names follow the service's own params (openSUSE convention: + cpio=node_modules.obscpio, output=node_modules.spec.inc, + input=package-lock.json). The lockfile is matched by suffix because the + node_modules service itself globs ``*<input>``. + """ + params = {p.get("name", ""): (p.text or "").strip() for p in svc.findall("param")} + cpio = params.get("cpio") or "node_modules.obscpio" + output = params.get("output") or "node_modules.spec.inc" + lockfile = params.get("input") or "package-lock.json" + return [ + re.compile("^" + re.escape(cpio) + "$"), + re.compile("^" + re.escape(output) + "$"), + re.compile(re.escape(lockfile) + "$"), + ] + + +# Services whose outputs are a function of an external registry's state at +# generation time (crates.io, registry.npmjs.org) rather than of any input +# under git. The branch content check compares those outputs by presence +# only: a byte difference must not flip a decision to "promote", but a file +# missing on either side still must. Keyed by service name. +_DRIFT_TOLERANT_SERVICES: dict[str, Callable[[ET.Element], list[re.Pattern[str]]]] = { + "cargo_vendor": _cargo_vendor_patterns, + "node_modules": _node_modules_patterns, +} + + +def drift_tolerant_patterns(service_file: Path) -> list[re.Pattern[str]]: + """Return filename regexes for drift-tolerant outputs declared in *service_file*. + + Returns an empty list when the file is missing or unparsable, or when it + declares none of the services in _DRIFT_TOLERANT_SERVICES. + """ try: root = ET.parse(service_file).getroot() except (ET.ParseError, OSError): - return False - return any(svc.get("name") == "cargo_vendor" for svc in root.findall("service")) + return [] + patterns: list[re.Pattern[str]] = [] + for svc in root.findall("service"): + factory = _DRIFT_TOLERANT_SERVICES.get(svc.get("name", "")) + if factory is not None: + patterns.extend(factory(svc)) + return patterns def _get_upstream_obs_scm_info( @@ -749,7 +794,9 @@ def _run_local_services( are restored from cache. Phase 2 — manual services (go_modules etc.): run on a cache miss. Results are stored to the service cache on success. - Extraction — ``.obscpio`` archives are extracted via ``cpio``. + Extraction — ``.obscpio`` archives produced by Phase 1 are extracted via + ``cpio``; ``.obscpio`` files produced by manual services + (e.g. node_modules.obscpio) are kept for upload. Local packaging — debian/ and rpm/ files are copied from the local package directory. Phase 3 — buildtime services (tar, recompress, set_version): always @@ -983,6 +1030,12 @@ def _run_one(svc: ET.Element) -> list[str]: # directories, not .obscpio files. The OBS build script extracts # these before running buildtime services; we replicate that here. for obscpio in sorted(workdir.glob("*.obscpio")): + if obscpio.name in manual_artifacts: + # Produced by a mode="manual" service (e.g. node_modules.obscpio). + # It is a source artifact in its own right — OBS unpacks it into + # SOURCES at build time — so it must reach the upload set intact. + logger.debug(f"keeping manual artifact {obscpio.name}") + continue logger.debug(f"extracting {obscpio.name}") result = subprocess.run( [ @@ -1060,7 +1113,7 @@ def _run_one(svc: ET.Element) -> list[str]: for child in list(workdir.iterdir()): if child.is_dir(): shutil.rmtree(child, ignore_errors=True) - elif child.suffix == ".obscpio": + elif child.suffix == ".obscpio" and child.name not in manual_artifacts: child.unlink() return workdir diff --git a/root/README.md b/root/README.md index 2d7901d8..9ec148a8 100644 --- a/root/README.md +++ b/root/README.md @@ -42,7 +42,7 @@ Additional products (e.g. `psmdb/` for Percona Server for MongoDB) follow the sa ## Common projects -- **`common/deps/build/`** — build-time dependencies shared across all products (Go toolchain, OBS source +- **`common/deps/build/`** — build-time dependencies shared across all products (Go toolchain, `local-npm-registry`, OBS source services such as `obs-service-tar_scm`). Product projects declare a path dependency on this project so its packages are available at build time. - **`common/deps/runtime/`** — runtime packages required by product packages across all products @@ -173,6 +173,29 @@ Graduation: when a development cycle concludes and a new release tag is cut, the `obs/_service` revision is bumped to the new tag (staging always builds from tags), and the devel entry (Class A or B) is removed — or repointed at the next development branch if work continues. +### `devel/pgadmin/` — a version-independent devel project + +`ppg:devel:pgadmin` is the one devel project that is not a PG-major subset: it holds +`percona-pgadmin4` and its Python 3.12 / Node.js dependency stack, which do not depend on +the PostgreSQL major version. Its `project.yaml` declares a single `UBI_9` repository +(pgAdmin is built for UBI 9 only), so no per-package `build:` flags are needed, and adds +`ExpandFlags: module:nodejs:22` for the frontend build. Packages there follow the +normal `rpm/` + `obs/_service` layout. The Python side is the ~70 `python3-*` directories +(pgAdmin's Python 3.12 closure, built from PyPI sdists — see "Python 3.12 packages" in +`docs/PACKAGING_HOWTO.md`); the npm dependencies are vendored at sync time by the +`npm_lockfile` → `node_modules` service pair (see `docs/PERCONA_OBS_TOOL.md`). The shared +Python build-backend stack (`python3-flit-core`, `python3-packaging`, `python3-pathspec`, +`python3-trove-classifiers`, `python3-hatchling`) lives in `ppg/common/deps/` because +other products' Python packages need it too. + +`percona-pgadmin4` is the application package (pgAdmin 4 in server mode, built from the +upstream git tag through `obs_scm` → `npm_lockfile` → `node_modules`, with webpack served by +`local-npm-registry`). It ships `-gunicorn` (launcher + systemd unit; the runtime a container +image uses), `-httpd` (Apache + `python3.12-mod_wsgi`) and `-doc` (rst sources). The +`python3-*` packages are its Python 3.12 dependency stack; `python3-click`, `python3-six`, +`python3-dateutil`, `python3-psutil` and `python3-dns` are `_aggregate`s of `ppg:common:deps` +so the project repository installs on its own. UBI_9 only. + ## project.yaml Each project directory may contain a `project.yaml` file that defines the OBS project metadata, diff --git a/root/common/deps/build/local-npm-registry/obs/_service b/root/common/deps/build/local-npm-registry/obs/_service new file mode 100644 index 00000000..1beb1a5e --- /dev/null +++ b/root/common/deps/build/local-npm-registry/obs/_service @@ -0,0 +1,7 @@ +<services> + <!-- Upstream release tarball: dist/ (compiled TypeScript) and node_modules/ + (the runtime dependency `ssri`) are pre-bundled, so nothing is built. --> + <service name="download_url"> + <param name="url">https://github.com/openSUSE/npm-localhost-proxy/releases/download/v%!{LOCAL_NPM_REGISTRY_VERSION}/local_npm_registry-v%!{LOCAL_NPM_REGISTRY_VERSION}.tar.gz</param> + </service> +</services> diff --git a/root/common/deps/build/local-npm-registry/package.yaml b/root/common/deps/build/local-npm-registry/package.yaml new file mode 100644 index 00000000..937c0b1c --- /dev/null +++ b/root/common/deps/build/local-npm-registry/package.yaml @@ -0,0 +1,23 @@ +title: local-npm-registry build tool +description: | + local-npm-registry %!{LOCAL_NPM_REGISTRY_VERSION} — openSUSE's localhost-only + npm registry (github.com/openSUSE/npm-localhost-proxy). At build time it + serves the npm tarballs unpacked from a node_modules.obscpio so that + `npm install` works inside the network-less OBS build root. Consumed by + percona-pgadmin4 (ppg:devel:pgadmin) via `BuildRequires: local-npm-registry`. + RPM-only, noarch; built for UBI 9 only because pgAdmin 4 is a UBI-9-only + product and the package needs the nodejs:22 module stream. + +build: + RockyLinux_8: false + RockyLinux_9: false + RockyLinux_10: false + UBI_8: false + Debian_11: false + Debian_12: false + Debian_13: false + Ubuntu_22.04: false + Ubuntu_24.04: false + Ubuntu_26.04: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/common/deps/build/local-npm-registry/rpm/local-npm-registry.spec b/root/common/deps/build/local-npm-registry/rpm/local-npm-registry.spec new file mode 100644 index 00000000..ece87bd2 --- /dev/null +++ b/root/common/deps/build/local-npm-registry/rpm/local-npm-registry.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} + +Name: local-npm-registry +Version: %!{LOCAL_NPM_REGISTRY_VERSION} +Release: 1%{?dist} +Summary: Localhost-only npm registry for offline builds +License: GPL-3.0-or-later +URL: https://github.com/openSUSE/npm-localhost-proxy +Source0: local_npm_registry-v%!{LOCAL_NPM_REGISTRY_VERSION}.tar.gz + +BuildArch: noarch + +# The launcher runs on /usr/bin/node; both come from the nodejs:22 module +# stream enabled in the consuming projects (EL has no npm-default package). +Requires: nodejs >= 20 +Requires: npm + +%description +local-npm-registry serves npm packages on a localhost address so that +`npm install` can run in a non-networked environment such as an OBS build +root. The package tarballs are taken from the node_modules.obscpio archive +that the OBS `node_modules` source service produces for the consuming +package (e.g. percona-pgadmin4). + +Ported from openSUSE devel:languages:javascript/local-npm-registry. + +%prep +%autosetup -n local_npm_registry-v%{version} + +%build +# dist/ and node_modules/ are pre-bundled in the release tarball — nothing to +# build (building would require the very registry this package provides). + +%install +mkdir -p %{buildroot}%{_datadir}/%{name} +mkdir -p %{buildroot}%{_bindir} +cp -r dist node_modules %{buildroot}%{_datadir}/%{name} +cat > %{buildroot}%{_bindir}/local-npm-registry << EOF +#!/usr/bin/node +(async () => { + const registry = await import("%{_datadir}/%{name}/dist/index.js"); + registry.mainEntryFunction(); +})(); +EOF + +%files +%license COPYING +%doc README.md +%attr(755,root,root) %{_bindir}/local-npm-registry +%{_datadir}/%{name} + +%changelog +* Wed Aug 26 2026 Percona Development Team <info@percona.com> - %!{LOCAL_NPM_REGISTRY_VERSION}-1 +- Package local-npm-registry %!{LOCAL_NPM_REGISTRY_VERSION} (openSUSE npm-localhost-proxy) for UBI 9, + the offline npm registry used by the percona-pgadmin4 build diff --git a/root/macros.yaml b/root/macros.yaml index 978a700c..70771165 100644 --- a/root/macros.yaml +++ b/root/macros.yaml @@ -1,2 +1,3 @@ - GOLANG_VERSION: 1.26.6 +- LOCAL_NPM_REGISTRY_VERSION: 1.1.0 - TELEMETRY_AGENT_VERSION: 1.0.15 \ No newline at end of file diff --git a/root/ppg/common/deps/python3-dns/obs/_service b/root/ppg/common/deps/python3-dns/obs/_service index d2088350..7b4f4f38 100644 --- a/root/ppg/common/deps/python3-dns/obs/_service +++ b/root/ppg/common/deps/python3-dns/obs/_service @@ -1,16 +1,5 @@ <services> - <service name="obs_scm"> - <param name="url">https://github.com/rthalley/dnspython.git</param> - <param name="scm">git</param> - <param name="revision">v1.15.0</param> - <param name="versionformat">@PARENT_TAG@</param> - <param name="versionrewrite-pattern">v(.*)</param> - <param name="filename">python3-dns</param> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/d/dnspython/dnspython-2.8.0.tar.gz</param> </service> - <service mode="buildtime" name="tar" /> - <service mode="buildtime" name="recompress"> - <param name="file">*.tar</param> - <param name="compression">gz</param> - </service> - <service mode="buildtime" name="set_version" /> </services> diff --git a/root/ppg/common/deps/python3-dns/package.yaml b/root/ppg/common/deps/python3-dns/package.yaml new file mode 100644 index 00000000..b13998db --- /dev/null +++ b/root/ppg/common/deps/python3-dns/package.yaml @@ -0,0 +1,5 @@ +title: dnspython 2.8.0 for Python 3.12 +description: | + DNS toolkit. RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/common/deps/python3-dns/rpm/python3-dns.spec b/root/ppg/common/deps/python3-dns/rpm/python3-dns.spec index 07cd0138..d7de3111 100644 --- a/root/ppg/common/deps/python3-dns/rpm/python3-dns.spec +++ b/root/ppg/common/deps/python3-dns/rpm/python3-dns.spec @@ -1,5 +1,4 @@ %global debug_package %{nil} - %if 0%{?rhel} && 0%{?rhel} >= 8 %global __ospython %{_bindir}/python3.12 %global python3_pkgprefix python3.12 @@ -10,47 +9,54 @@ %global python3_pkgprefix python3 %global python3_buildversion 3 %endif -%{expand: %%global py3ver %(echo `%{__ospython} -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} %global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") -%global srcname python3-dns - -Summary: DNS toolkit for Python Name: %{python3_pkgprefix}-dns -Version: 1.0.0 +Version: 2.8.0 Release: 1%{?dist} -Source0: %{srcname}-%{version}.tar.gz +Summary: DNS toolkit License: ISC +URL: https://pypi.org/project/dnspython/ +Source0: https://files.pythonhosted.org/packages/source/d/dnspython/dnspython-2.8.0.tar.gz BuildArch: noarch Vendor: Percona, LLC Packager: Percona Development Team <https://jira.percona.com> -Url: https://www.dnspython.org/ Epoch: 1 BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif %description -dnspython is a DNS toolkit for Python. It supports almost all record types. It can -be used for queries, zone transfers, and dynamic updates. It supports TSIG -authenticated messages and EDNS0. +DNS toolkit. + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. %prep -%setup -n %{srcname}-%{version} +%autosetup -p1 -n dnspython-2.8.0 %build -%{__ospython} setup.py build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . %install -%{__ospython} setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES -# Own egg-info directories (not recorded by --record) -find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" >> INSTALLED_FILES +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import dns" -%files -f INSTALLED_FILES -%defattr(-,root,root) -%dir %{python3_sitelib}/dns -%dir %{python3_sitelib}/dns/__pycache__ +%files +%{python3_sitelib}/* %changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.8.0-1 +- Package dnspython 2.8.0 for Python 3.12 (bump to 2.8.0 (email-validator needs >= 2.0); built with hatchling) + * Mon Mar 30 2026 Percona Build/Release Team <eng-build@percona.com> - 1.15.0-1 - Initial build of python3-dns 1.15.0 diff --git a/root/ppg/common/deps/python3-flit-core/obs/_service b/root/ppg/common/deps/python3-flit-core/obs/_service new file mode 100644 index 00000000..2a0e8172 --- /dev/null +++ b/root/ppg/common/deps/python3-flit-core/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/f/flit-core/flit_core-3.12.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/common/deps/python3-flit-core/package.yaml b/root/ppg/common/deps/python3-flit-core/package.yaml new file mode 100644 index 00000000..ad37b7b7 --- /dev/null +++ b/root/ppg/common/deps/python3-flit-core/package.yaml @@ -0,0 +1,11 @@ +title: flit-core 3.12.0 for Python 3.12 +description: | + Distribution-building parts of Flit. See flit package for more information. RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). + +# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours. +build: + RockyLinux_10: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/ppg/common/deps/python3-flit-core/rpm/python3-flit-core.spec b/root/ppg/common/deps/python3-flit-core/rpm/python3-flit-core.spec new file mode 100644 index 00000000..952ab13f --- /dev/null +++ b/root/ppg/common/deps/python3-flit-core/rpm/python3-flit-core.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flit-core +Version: 3.12.0 +Release: 1%{?dist} +Summary: Distribution-building parts of Flit. See flit package for more information +License: BSD-3-Clause +URL: https://github.com/pypa/flit +Source0: https://files.pythonhosted.org/packages/source/f/flit-core/flit_core-3.12.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Distribution-building parts of Flit. See flit package for more information. + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. + +%prep +%autosetup -p1 -n flit_core-3.12.0 + +%build +export PYTHONPATH=$PWD/. +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flit_core.buildapi" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.12.0-1 +- Package flit-core 3.12.0 for Python 3.12 (shared Python 3.12 build stack) diff --git a/root/ppg/common/deps/python3-hatchling/obs/_service b/root/ppg/common/deps/python3-hatchling/obs/_service new file mode 100644 index 00000000..4f129764 --- /dev/null +++ b/root/ppg/common/deps/python3-hatchling/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/h/hatchling/hatchling-1.28.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/common/deps/python3-hatchling/package.yaml b/root/ppg/common/deps/python3-hatchling/package.yaml new file mode 100644 index 00000000..b0af53a6 --- /dev/null +++ b/root/ppg/common/deps/python3-hatchling/package.yaml @@ -0,0 +1,11 @@ +title: hatchling 1.28.0 for Python 3.12 +description: | + Modern, extensible Python build backend. RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). + +# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours. +build: + RockyLinux_10: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/ppg/common/deps/python3-hatchling/rpm/python3-hatchling.spec b/root/ppg/common/deps/python3-hatchling/rpm/python3-hatchling.spec new file mode 100644 index 00000000..831f10cc --- /dev/null +++ b/root/ppg/common/deps/python3-hatchling/rpm/python3-hatchling.spec @@ -0,0 +1,66 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-hatchling +Version: 1.28.0 +Release: 1%{?dist} +Summary: Modern, extensible Python build backend +License: MIT +URL: https://hatch.pypa.io/latest/ +Source0: https://files.pythonhosted.org/packages/source/h/hatchling/hatchling-1.28.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-packaging >= 24.2 +BuildRequires: %{python3_pkgprefix}-pathspec >= 0.10.1 +BuildRequires: python3.12-pluggy +BuildRequires: %{python3_pkgprefix}-trove-classifiers + +Requires: %{python3_pkgprefix}-packaging >= 24.2 +Requires: %{python3_pkgprefix}-pathspec >= 0.10.1 +Requires: python3.12-pluggy +Requires: %{python3_pkgprefix}-trove-classifiers + +%description +Modern, extensible Python build backend. + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. + +%prep +%autosetup -p1 -n hatchling-1.28.0 + +%build +export PYTHONPATH=$PWD/src +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import hatchling" + +%files +%{python3_sitelib}/* +%{_bindir}/hatchling + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.28.0-1 +- Package hatchling 1.28.0 for Python 3.12 (shared Python 3.12 build stack) diff --git a/root/ppg/common/deps/python3-packaging/obs/_service b/root/ppg/common/deps/python3-packaging/obs/_service new file mode 100644 index 00000000..a9dc29ce --- /dev/null +++ b/root/ppg/common/deps/python3-packaging/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/packaging/packaging-25.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/common/deps/python3-packaging/package.yaml b/root/ppg/common/deps/python3-packaging/package.yaml new file mode 100644 index 00000000..16166f30 --- /dev/null +++ b/root/ppg/common/deps/python3-packaging/package.yaml @@ -0,0 +1,11 @@ +title: packaging 25.0 for Python 3.12 +description: | + Core utilities for Python packages. RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). + +# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours. +build: + RockyLinux_10: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/ppg/common/deps/python3-packaging/rpm/python3-packaging.spec b/root/ppg/common/deps/python3-packaging/rpm/python3-packaging.spec new file mode 100644 index 00000000..b85d614a --- /dev/null +++ b/root/ppg/common/deps/python3-packaging/rpm/python3-packaging.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-packaging +Version: 25.0 +Release: 1%{?dist} +Summary: Core utilities for Python packages +License: Apache-2.0 +URL: https://github.com/pypa/packaging +Source0: https://files.pythonhosted.org/packages/source/p/packaging/packaging-25.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Core utilities for Python packages. + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. + +%prep +%autosetup -p1 -n packaging-25.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import packaging" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 25.0-1 +- Package packaging 25.0 for Python 3.12 (shared Python 3.12 build stack) diff --git a/root/ppg/common/deps/python3-pathspec/obs/_service b/root/ppg/common/deps/python3-pathspec/obs/_service new file mode 100644 index 00000000..067e34c9 --- /dev/null +++ b/root/ppg/common/deps/python3-pathspec/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/pathspec/pathspec-0.12.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/common/deps/python3-pathspec/package.yaml b/root/ppg/common/deps/python3-pathspec/package.yaml new file mode 100644 index 00000000..63d9bd7c --- /dev/null +++ b/root/ppg/common/deps/python3-pathspec/package.yaml @@ -0,0 +1,11 @@ +title: pathspec 0.12.1 for Python 3.12 +description: | + Utility library for gitignore style pattern matching of file paths. RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). + +# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours. +build: + RockyLinux_10: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/ppg/common/deps/python3-pathspec/rpm/python3-pathspec.spec b/root/ppg/common/deps/python3-pathspec/rpm/python3-pathspec.spec new file mode 100644 index 00000000..e20bb860 --- /dev/null +++ b/root/ppg/common/deps/python3-pathspec/rpm/python3-pathspec.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pathspec +Version: 0.12.1 +Release: 1%{?dist} +Summary: Utility library for gitignore style pattern matching of file paths +License: MPL-2.0 +URL: https://pypi.org/project/pathspec/ +Source0: https://files.pythonhosted.org/packages/source/p/pathspec/pathspec-0.12.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Utility library for gitignore style pattern matching of file paths. + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. + +%prep +%autosetup -p1 -n pathspec-0.12.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pathspec" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.12.1-1 +- Package pathspec 0.12.1 for Python 3.12 (shared Python 3.12 build stack) diff --git a/root/ppg/common/deps/python3-trove-classifiers/obs/_service b/root/ppg/common/deps/python3-trove-classifiers/obs/_service new file mode 100644 index 00000000..bc8b657c --- /dev/null +++ b/root/ppg/common/deps/python3-trove-classifiers/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/t/trove-classifiers/trove_classifiers-2025.9.11.17.tar.gz</param> + </service> +</services> diff --git a/root/ppg/common/deps/python3-trove-classifiers/package.yaml b/root/ppg/common/deps/python3-trove-classifiers/package.yaml new file mode 100644 index 00000000..18c46112 --- /dev/null +++ b/root/ppg/common/deps/python3-trove-classifiers/package.yaml @@ -0,0 +1,11 @@ +title: trove-classifiers 2025.9.11.17 for Python 3.12 +description: | + Canonical source for classifiers on PyPI (pypi.org). RPM-only python3.12-* build from the PyPI sdist, + consumed by Percona PostgreSQL packages. Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). + +# EL10 and openSUSE ship python3-hatchling & friends; only EL8/EL9/UBI need ours. +build: + RockyLinux_10: false + openSUSE_Leap_16: false + openSUSE_Tumbleweed: false diff --git a/root/ppg/common/deps/python3-trove-classifiers/rpm/python3-trove-classifiers.spec b/root/ppg/common/deps/python3-trove-classifiers/rpm/python3-trove-classifiers.spec new file mode 100644 index 00000000..a8b61f48 --- /dev/null +++ b/root/ppg/common/deps/python3-trove-classifiers/rpm/python3-trove-classifiers.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-trove-classifiers +Version: 2025.9.11.17 +Release: 1%{?dist} +Summary: Canonical source for classifiers on PyPI (pypi.org) +License: Apache-2.0 +URL: https://github.com/pypa/trove-classifiers +Source0: https://files.pythonhosted.org/packages/source/t/trove-classifiers/trove_classifiers-2025.9.11.17.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Canonical source for classifiers on PyPI (pypi.org). + +Built for Python 3.12 from the PyPI sdist; a Python 3.12 build/runtime dependency shared by Percona PostgreSQL packages. + +%prep +%autosetup -p1 -n trove_classifiers-2025.9.11.17 +# Build without calver (not packaged): pin the version literally. +sed -i 's/"calver"//; s/, *\]/]/' pyproject.toml +sed -i 's/use_calver="[^"]*",/version="%{version}",/; /setup_requires=\["calver"\],/d' setup.py + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import trove_classifiers" + +%files +%{python3_sitelib}/* +%{_bindir}/trove-classifiers + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2025.9.11.17-1 +- Package trove-classifiers 2025.9.11.17 for Python 3.12 (shared Python 3.12 build stack) diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service b/root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service new file mode 100644 index 00000000..57b27972 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/obs/_service @@ -0,0 +1,26 @@ +<services> + <service name="obs_scm"> + <param name="scm">git</param> + <param name="url">https://github.com/pgadmin-org/pgadmin4.git</param> + <param name="revision">REL-9_17</param> + <param name="versionformat">@PARENT_TAG@</param> + <param name="versionrewrite-pattern">REL-(\d+)_(\d+)</param> + <param name="versionrewrite-replacement">\1.\2</param> + <param name="filename">percona-pgadmin4</param> + </service> + <service name="npm_lockfile" mode="manual"> + <param name="archive">percona-pgadmin4*.obscpio</param> + <param name="subdir">web</param> + </service> + <service name="node_modules" mode="manual"> + <param name="cpio">node_modules.obscpio</param> + <param name="output">node_modules.spec.inc</param> + <param name="source-offset">10000</param> + </service> + <service name="tar" mode="buildtime"/> + <service name="recompress" mode="buildtime"> + <param name="file">*.tar</param> + <param name="compression">gz</param> + </service> + <service name="set_version" mode="buildtime"/> +</services> diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml b/root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml new file mode 100644 index 00000000..ec2936b0 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/package.yaml @@ -0,0 +1,7 @@ +title: pgAdmin 4 (server mode) for UBI-9 +description: | + pgAdmin 4 built from the upstream git tag (REL-9_17) with the SP1 npm vendoring + chain (obs_scm -> npm_lockfile -> node_modules) and the Python 3.12 stack of this + project. Subpackages: -gunicorn (launcher + systemd unit; the container runtime), + -httpd (Apache httpd + mod_wsgi integration for hosts), -doc (documentation + sources). Design: docs/superpowers/specs/2026-08-28-pgadmin4-package-design.md diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch new file mode 100644 index 00000000..8a5c3788 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0001-help-menu-online-docs.patch @@ -0,0 +1,18 @@ +From: Percona Development Team <https://jira.percona.com> +Subject: Help menu: link "Online Help" to the upstream docs for this release + +The package ships the documentation sources only (-doc); the rendered +manual lives at https://www.pgadmin.org/docs/pgadmin4/<release>/. + +--- a/web/pgadmin/help/__init__.py ++++ b/web/pgadmin/help/__init__.py +@@ -34,7 +34,8 @@ + priority=100, + target='pgadmin_help', + icon='fa fa-question', +- url=url_for('help.static', filename='index.html')), ++ url='https://www.pgadmin.org/docs/pgadmin4/%s.%s/' % ( ++ config.APP_RELEASE, config.APP_REVISION)), + + MenuItem(name='mnu_pgadmin_website', + label=gettext('pgAdmin Website'), diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0002-make-cloud-packages-optional.patch b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0002-make-cloud-packages-optional.patch new file mode 100644 index 00000000..82cd1556 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0002-make-cloud-packages-optional.patch @@ -0,0 +1,24 @@ +Index: pgadmin4-8.2/web/pgadmin/misc/__init__.py +=================================================================== +--- pgadmin4-8.2.orig/web/pgadmin/misc/__init__.py ++++ pgadmin4-8.2/web/pgadmin/misc/__init__.py +@@ -108,8 +108,17 @@ class MiscModule(PgAdminModule): + from .bgprocess import blueprint as module + self.submodules.append(module) + +- from .cloud import blueprint as module +- self.submodules.append(module) ++ try: ++ from .cloud import blueprint as module ++ self.submodules.append(module) ++ except ModuleNotFoundError: ++ print('\n\n') ++ print('###########################################################\n') ++ print(' IMPORTANT WARNING:\n') ++ print('Cloud packages not found, if you want to enable cloud support,') ++ print('please install the pgadmin4-cloud package') ++ print('\n###########################################################\n') ++ + + from .dependencies import blueprint as module + self.submodules.append(module) diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0003-use-os-makedirs.patch b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0003-use-os-makedirs.patch new file mode 100644 index 00000000..1f596788 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/0003-use-os-makedirs.patch @@ -0,0 +1,22 @@ +From: Antonio Larrosa <alarrosa@suse.com> +Subject: Use os.makedirs instead of os.mkdir + +So parent directories are created if needed + +--- + web/pgadmin/setup/data_directory.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: pgadmin4-7.4/web/pgadmin/setup/data_directory.py +=================================================================== +--- pgadmin4-7.4.orig/web/pgadmin/setup/data_directory.py ++++ pgadmin4-7.4/web/pgadmin/setup/data_directory.py +@@ -18,7 +18,7 @@ FAILED_CREATE_DIR = \ + + def _create_directory_if_not_exists(_path): + if _path and not os.path.exists(_path): +- os.mkdir(_path) ++ os.makedirs(_path) + return True + + return False diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py new file mode 100644 index 00000000..a614b36b --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/config_distro.py @@ -0,0 +1,53 @@ +# Distribution configuration for percona-pgadmin4 (server mode). +# +# Precedence (lowest to highest): config.py (upstream defaults) < this file < +# config_local.py < /etc/pgadmin/config_system.py. Container images and unit +# files override any setting through the environment: every PGADMIN_CONFIG_<NAME> +# variable becomes the setting <NAME>; the value is parsed as a Python literal +# (numbers, True/False/None, quoted strings, lists, dicts) and kept as a plain +# string when it is not one — the same contract as the upstream container image. +import ast +import os + +SERVER_MODE = True +MINIFY_HTML = False +UPGRADE_CHECK_ENABLED = False +HELP_PATH = '/usr/share/doc/percona-pgadmin4/en_US' +LOG_FILE = '/var/log/pgadmin/pgadmin4.log' +SQLITE_PATH = '/var/lib/pgadmin/pgadmin4.db' +SESSION_DB_PATH = '/var/lib/pgadmin/sessions' +STORAGE_DIR = '/var/lib/pgadmin/storage' +AZURE_CREDENTIAL_CACHE_DIR = '/var/lib/pgadmin/azurecredentialcache' +KERBEROS_CCACHE_DIR = '/var/lib/pgadmin/krbccache' +DEFAULT_BINARY_PATHS = { + "pg": "/usr/pgsql-18/bin", + "pg-13": "/usr/pgsql-13/bin", + "pg-14": "/usr/pgsql-14/bin", + "pg-15": "/usr/pgsql-15/bin", + "pg-16": "/usr/pgsql-16/bin", + "pg-17": "/usr/pgsql-17/bin", + "pg-18": "/usr/pgsql-18/bin", +} + + +def _apply_env_overrides(): + """Apply every PGADMIN_CONFIG_<NAME> environment variable as setting <NAME>. + + Kept in a function (rather than bare module-level statements) so that no + loop variable is ever left unbound: with zero matching variables the + ``for`` body never executes and there is nothing to clean up, so the + module always imports successfully. + """ + prefix = 'PGADMIN_CONFIG_' + for key, value in os.environ.items(): + if not key.startswith(prefix) or len(key) == len(prefix): + continue + literal = {'true': 'True', 'false': 'False'}.get(value.strip().lower(), value) + try: + globals()[key[len(prefix):]] = ast.literal_eval(literal) + except (ValueError, SyntaxError): + globals()[key[len(prefix):]] = value + + +_apply_env_overrides() +del _apply_env_overrides diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/gunicorn_config.py b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/gunicorn_config.py new file mode 100644 index 00000000..f42c39d0 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/gunicorn_config.py @@ -0,0 +1,33 @@ +# gunicorn configuration for percona-pgadmin4-gunicorn. +# Log to stdout/stderr (container-friendly); the launcher passes bind/workers/TLS +# on the command line so environment variables stay the single knob. +import logging + +logconfig_dict = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'generic': { + 'format': '%(asctime)s [%(process)d] [%(levelname)s] %(message)s', + 'datefmt': '[%Y-%m-%d %H:%M:%S %z]', + 'class': 'logging.Formatter', + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'generic', + 'stream': 'ext://sys.stdout', + }, + 'error_console': { + 'class': 'logging.StreamHandler', + 'formatter': 'generic', + 'stream': 'ext://sys.stderr', + }, + }, + 'loggers': { + 'gunicorn.error': {'level': 'INFO', 'handlers': ['error_console'], 'propagate': False}, + 'gunicorn.access': {'level': 'INFO', 'handlers': ['console'], 'propagate': False}, + }, + 'root': {'level': logging.INFO, 'handlers': ['console']}, +} diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn new file mode 100755 index 00000000..0c4398b4 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-gunicorn @@ -0,0 +1,54 @@ +#!/bin/bash +# percona-pgadmin4-gunicorn — run pgAdmin 4 under gunicorn. +# +# Environment (all optional): +# PGADMIN_LISTEN_ADDRESS bind address (default 127.0.0.1) +# PGADMIN_LISTEN_PORT bind port (default 5050) +# PGADMIN_ENABLE_TLS "true" to serve HTTPS using /certs/server.cert + /certs/server.key +# GUNICORN_ACCESS_LOGFILE access log target (default "-" = stdout) +# GUNICORN_THREADS threads per worker (default 25) +# GUNICORN_LIMIT_REQUEST_LINE (default 8190) +# GUNICORN_TIMEOUT worker silence timeout, seconds (default 86400; pgAdmin +# runs long requests, e.g. backups/restores, in the same +# threaded worker, so gunicorn's 30s default would kill it) +# PGADMIN_CONFIG_<SETTING> any pgAdmin setting, applied by config_distro.py +# PGADMIN_DEFAULT_EMAIL / PGADMIN_DEFAULT_PASSWORD +# create the initial admin user when no configuration +# database exists yet (same names as the upstream image). +# The systemd unit runs this script as user pgadmin; containers run it as their +# unprivileged user with /var/lib/pgadmin and /var/log/pgadmin writable. +# Containers must set PGADMIN_LISTEN_ADDRESS=0.0.0.0 (or the pod address) to be +# reachable; the default here stays 127.0.0.1 for the systemd/host case. +set -euo pipefail + +PGADMIN_DIR=/usr/lib/python3.12/site-packages/pgadmin4 +: "${PGADMIN_LISTEN_ADDRESS:=127.0.0.1}" +: "${PGADMIN_LISTEN_PORT:=5050}" +: "${GUNICORN_ACCESS_LOGFILE:=-}" +: "${GUNICORN_THREADS:=25}" +: "${GUNICORN_LIMIT_REQUEST_LINE:=8190}" +: "${GUNICORN_TIMEOUT:=86400}" + +cd "${PGADMIN_DIR}" + +# First start: create the configuration database and the initial user. +if [ ! -e "${PGADMIN_CONFIG_SQLITE_PATH:-/var/lib/pgadmin/pgadmin4.db}" ] && \ + [ -n "${PGADMIN_DEFAULT_EMAIL:-}" ] && [ -n "${PGADMIN_DEFAULT_PASSWORD:-}" ]; then + PGADMIN_SETUP_EMAIL="${PGADMIN_DEFAULT_EMAIL}" \ + PGADMIN_SETUP_PASSWORD="${PGADMIN_DEFAULT_PASSWORD}" \ + /usr/bin/python3.12 setup.py setup-db +fi + +TLS_ARGS=() +if [ "${PGADMIN_ENABLE_TLS:-}" = "true" ]; then + TLS_ARGS=(--certfile /certs/server.cert --keyfile /certs/server.key) +fi + +exec /usr/bin/gunicorn \ + --bind "${PGADMIN_LISTEN_ADDRESS}:${PGADMIN_LISTEN_PORT}" \ + --workers 1 --threads "${GUNICORN_THREADS}" \ + --timeout "${GUNICORN_TIMEOUT}" \ + --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE}" \ + --access-logfile "${GUNICORN_ACCESS_LOGFILE}" \ + "${TLS_ARGS[@]}" \ + -c gunicorn_config.py run_pgadmin:app diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-httpd.conf b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-httpd.conf new file mode 100644 index 00000000..bc2e9e22 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-httpd.conf @@ -0,0 +1,15 @@ +# pgAdmin 4 under Apache httpd + mod_wsgi (python3.12-mod_wsgi). +# URL: http://<host>/pgadmin4/ Run `percona-pgadmin4-setup-web` once after install. +# python3.12-mod_wsgi's own conf.modules.d/10-wsgi-python3.conf already loads +# the module; no LoadModule line is needed (or wanted) here. + +WSGIDaemonProcess pgadmin user=pgadmin group=pgadmin processes=1 threads=25 \ + python-path=/usr/lib/python3.12/site-packages/pgadmin4 \ + home=/var/lib/pgadmin lang=en_US.UTF-8 locale=en_US.UTF-8 +WSGIScriptAlias /pgadmin4 /usr/lib/python3.12/site-packages/pgadmin4/pgAdmin4.wsgi + +<Directory /usr/lib/python3.12/site-packages/pgadmin4/> + WSGIProcessGroup pgadmin + WSGIApplicationGroup %{GLOBAL} + Require all granted +</Directory> diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-setup-web b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-setup-web new file mode 100755 index 00000000..a877ffd7 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4-setup-web @@ -0,0 +1,62 @@ +#!/bin/bash +# percona-pgadmin4-setup-web — one-time host setup for the httpd (mod_wsgi) deployment. +# +# 1. creates the pgAdmin configuration database and the initial admin user +# (prompts unless PGADMIN_SETUP_EMAIL and PGADMIN_SETUP_PASSWORD are set) +# 2. fixes ownership of /var/lib/pgadmin and /var/log/pgadmin +# 3. on SELinux hosts, allows httpd network access and labels the data directories +# 4. enables and restarts httpd (skip with --no-service or when not booted with systemd) +set -euo pipefail + +NO_SERVICE=0 +for arg in "$@"; do + case "$arg" in + --no-service) NO_SERVICE=1 ;; + -h|--help) sed -n '2,8p' "$0"; exit 0 ;; + *) echo "unknown option: $arg" >&2; exit 2 ;; + esac +done + +if [ "$(id -u)" -ne 0 ]; then + echo "percona-pgadmin4-setup-web must run as root" >&2 + exit 1 +fi + +PGADMIN_DIR=/usr/lib/python3.12/site-packages/pgadmin4 +DATA_DIR=/var/lib/pgadmin +LOG_DIR=/var/log/pgadmin + +echo "Setting up pgAdmin 4 in web mode (httpd + mod_wsgi)..." +mkdir -p "${DATA_DIR}/sessions" "${DATA_DIR}/storage" "${LOG_DIR}" +chown -R pgadmin:pgadmin "${DATA_DIR}" "${LOG_DIR}" + +if [ ! -e "${DATA_DIR}/pgadmin4.db" ]; then + echo "Creating configuration database..." + (cd "${PGADMIN_DIR}" && runuser -u pgadmin -- /usr/bin/python3.12 setup.py setup-db) + chmod 0600 "${DATA_DIR}/pgadmin4.db" +fi + +if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then + echo "Configuring SELinux..." + setsebool -P httpd_can_network_connect 1 || true + setsebool -P httpd_can_network_connect_db 1 || true + if command -v semanage >/dev/null 2>&1; then + semanage fcontext -a -t httpd_var_lib_t "${DATA_DIR}(/.*)?" 2>/dev/null || \ + semanage fcontext -m -t httpd_var_lib_t "${DATA_DIR}(/.*)?" + semanage fcontext -a -t httpd_log_t "${LOG_DIR}(/.*)?" 2>/dev/null || \ + semanage fcontext -m -t httpd_log_t "${LOG_DIR}(/.*)?" + restorecon -R "${DATA_DIR}" "${LOG_DIR}" + else + echo "warning: semanage not found (install policycoreutils-python-utils); skipping file contexts" >&2 + fi +fi + +if [ "${NO_SERVICE}" -eq 0 ] && [ -d /run/systemd/system ]; then + echo "Enabling and restarting httpd..." + systemctl enable --now httpd + systemctl restart httpd +else + echo "Skipping the httpd service step (--no-service or no systemd)." +fi + +echo "pgAdmin 4 is available at http://$(hostname -f 2>/dev/null || hostname)/pgadmin4" diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service new file mode 100644 index 00000000..6bd0f36b --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.service @@ -0,0 +1,23 @@ +[Unit] +Description=pgAdmin 4 (gunicorn) +Documentation=https://www.pgadmin.org/docs/ +After=network.target + +[Service] +Type=exec +User=pgadmin +Group=pgadmin +RuntimeDirectory=pgadmin4 +Environment=PGADMIN_LISTEN_ADDRESS=127.0.0.1 +Environment=PGADMIN_LISTEN_PORT=5050 +EnvironmentFile=-/etc/sysconfig/percona-pgadmin4 +ExecStart=/usr/bin/percona-pgadmin4-gunicorn +Restart=on-failure +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/pgadmin /var/log/pgadmin +PrivateTmp=true +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec new file mode 100644 index 00000000..02960ca5 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.spec @@ -0,0 +1,347 @@ +# Ported from openSUSE's pgadmin4.spec (Factory) to EL9 + python3.12 for UBI-9. +# Bumps: change <param name="revision"> in obs/_service (REL-<major>_<minor>) and +# re-check web/requirements.txt against the python3-* packages of this project. +%global debug_package %{nil} +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") +%global pgadmin_dir %{python3_sitelib}/pgadmin4 +%global pgadmin_user pgadmin +%global pgadmin_data %{_sharedstatedir}/pgadmin +%global pgadmin_log %{_localstatedir}/log/pgadmin +%global pgadmin_etc %{_sysconfdir}/pgadmin + +Name: percona-pgadmin4 +Version: 1.0.0 +Release: 1%{?dist} +Summary: Management tool for PostgreSQL (pgAdmin 4, server mode) +License: PostgreSQL +URL: https://www.pgadmin.org +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> + +Source0: %{name}-%{version}.tar.gz +Source1: config_distro.py +Source2: run_pgadmin.py +Source3: gunicorn_config.py +Source4: percona-pgadmin4-gunicorn +Source5: percona-pgadmin4.service +Source6: percona-pgadmin4.sysusers +Source7: percona-pgadmin4.tmpfiles +Source8: percona-pgadmin4-httpd.conf +Source9: percona-pgadmin4-setup-web +Source20: package-lock.json +# SourceNNNNN: npm tarballs vendored by the node_modules service (offset 10000) +Source100: node_modules.spec.inc +%include %{_sourcedir}/node_modules.spec.inc + +# Help > Online Help opens the upstream documentation for the running version +# (no local Sphinx build; -doc ships the rst sources). +Patch1: 0001-help-menu-online-docs.patch +# openSUSE: do not fail at import time when the cloud SDKs are missing +Patch2: 0002-make-cloud-packages-optional.patch +# openSUSE: create data directories with os.makedirs (parents) instead of os.mkdir +Patch3: 0003-use-os-makedirs.patch + +BuildArch: noarch + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: local-npm-registry +BuildRequires: nodejs >= 20 +BuildRequires: npm +BuildRequires: systemd-rpm-macros +# runtime stack, needed for the %%check import (cloud SDKs — boto3, azure-*, google-* — are +# not packaged; Patch2 makes the cloud deployment module tolerate their absence) +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-flask-babel +BuildRequires: %{python3_pkgprefix}-flask-compress +BuildRequires: %{python3_pkgprefix}-flask-login +BuildRequires: %{python3_pkgprefix}-flask-mail +BuildRequires: %{python3_pkgprefix}-flask-migrate +BuildRequires: %{python3_pkgprefix}-flask-paranoid +BuildRequires: %{python3_pkgprefix}-flask-security-too +BuildRequires: %{python3_pkgprefix}-flask-socketio +BuildRequires: %{python3_pkgprefix}-flask-sqlalchemy +BuildRequires: %{python3_pkgprefix}-flask-wtf +BuildRequires: %{python3_pkgprefix}-wtforms +BuildRequires: %{python3_pkgprefix}-werkzeug +BuildRequires: %{python3_pkgprefix}-sqlalchemy +BuildRequires: %{python3_pkgprefix}-sqlparse +BuildRequires: %{python3_pkgprefix}-psycopg +BuildRequires: %{python3_pkgprefix}-psycopg-c +BuildRequires: %{python3_pkgprefix}-libpass +BuildRequires: %{python3_pkgprefix}-bcrypt +BuildRequires: %{python3_pkgprefix}-authlib +BuildRequires: %{python3_pkgprefix}-pyotp +BuildRequires: %{python3_pkgprefix}-qrcode +BuildRequires: %{python3_pkgprefix}-ldap3 +BuildRequires: %{python3_pkgprefix}-gssapi +BuildRequires: %{python3_pkgprefix}-sshtunnel +BuildRequires: %{python3_pkgprefix}-paramiko +BuildRequires: %{python3_pkgprefix}-keyring +BuildRequires: %{python3_pkgprefix}-typer +BuildRequires: %{python3_pkgprefix}-typing-extensions +BuildRequires: %{python3_pkgprefix}-jsonformatter +BuildRequires: %{python3_pkgprefix}-libgravatar +BuildRequires: %{python3_pkgprefix}-user-agents +BuildRequires: %{python3_pkgprefix}-pytz +BuildRequires: %{python3_pkgprefix}-certifi +BuildRequires: %{python3_pkgprefix}-dateutil +BuildRequires: %{python3_pkgprefix}-psutil +BuildRequires: python%{python3_buildversion}-cryptography +BuildRequires: python%{python3_buildversion}-urllib3 + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-flask-babel +Requires: %{python3_pkgprefix}-flask-compress +Requires: %{python3_pkgprefix}-flask-login +Requires: %{python3_pkgprefix}-flask-mail +Requires: %{python3_pkgprefix}-flask-migrate +Requires: %{python3_pkgprefix}-flask-paranoid +Requires: %{python3_pkgprefix}-flask-security-too +Requires: %{python3_pkgprefix}-flask-socketio +Requires: %{python3_pkgprefix}-flask-sqlalchemy +Requires: %{python3_pkgprefix}-flask-wtf +Requires: %{python3_pkgprefix}-wtforms +Requires: %{python3_pkgprefix}-werkzeug +Requires: %{python3_pkgprefix}-sqlalchemy +Requires: %{python3_pkgprefix}-sqlparse +Requires: %{python3_pkgprefix}-psycopg +Requires: %{python3_pkgprefix}-psycopg-c +Requires: %{python3_pkgprefix}-libpass +Requires: %{python3_pkgprefix}-bcrypt +Requires: %{python3_pkgprefix}-authlib +Requires: %{python3_pkgprefix}-pyotp +Requires: %{python3_pkgprefix}-qrcode +Requires: %{python3_pkgprefix}-ldap3 +Requires: %{python3_pkgprefix}-gssapi +Requires: %{python3_pkgprefix}-sshtunnel +Requires: %{python3_pkgprefix}-paramiko +Requires: %{python3_pkgprefix}-keyring +Requires: %{python3_pkgprefix}-typer +Requires: %{python3_pkgprefix}-typing-extensions +Requires: %{python3_pkgprefix}-jsonformatter +Requires: %{python3_pkgprefix}-libgravatar +Requires: %{python3_pkgprefix}-user-agents +Requires: %{python3_pkgprefix}-pytz +Requires: %{python3_pkgprefix}-certifi +Requires: %{python3_pkgprefix}-dateutil +Requires: %{python3_pkgprefix}-psutil +Requires: python%{python3_buildversion}-cryptography +Requires: python%{python3_buildversion}-setuptools +Requires: python%{python3_buildversion}-urllib3 +Requires(pre): shadow-utils +Provides: pgadmin4 = %{version}-%{release} +Suggests: %{name}-doc + +%description +pgAdmin 4 is the leading open source management tool for PostgreSQL. This +package installs the web application in server mode under +%{pgadmin_dir}, with distribution defaults in config_distro.py (data in +%{pgadmin_data}, logs in %{pgadmin_log}, site overrides in +%{pgadmin_etc}/config_system.py). Any setting can also be overridden with a +PGADMIN_CONFIG_<SETTING> environment variable. Install %{name}-gunicorn to run +it stand-alone (containers, systemd) or %{name}-httpd to serve it from Apache. + +%package gunicorn +Summary: Run pgAdmin 4 stand-alone under gunicorn +Requires: %{name} = %{version}-%{release} +Requires: %{python3_pkgprefix}-gunicorn +%{?systemd_requires} + +%description gunicorn +Launcher script (%{_bindir}/percona-pgadmin4-gunicorn) and systemd unit that +serve pgAdmin 4 with gunicorn. Configured through the environment +(PGADMIN_LISTEN_ADDRESS/PORT, PGADMIN_ENABLE_TLS, PGADMIN_DEFAULT_EMAIL/PASSWORD, +PGADMIN_CONFIG_<SETTING>); this is the runtime used by container images. The +service is not enabled by default. + +%package httpd +Summary: Serve pgAdmin 4 from Apache httpd with mod_wsgi +Requires: %{name} = %{version}-%{release} +Requires: httpd +Requires: python%{python3_buildversion}-mod_wsgi + +%description httpd +Apache configuration (/pgadmin4 via mod_wsgi, one daemon process with 25 +threads running as the pgadmin user) and the percona-pgadmin4-setup-web helper +that creates the configuration database, applies SELinux settings and +restarts httpd. + +%package doc +Summary: Documentation sources for pgAdmin 4 +BuildArch: noarch + +%description doc +The reStructuredText sources of the pgAdmin 4 documentation +(%{_docdir}/%{name}/en_US). The rendered manual for the installed release is +online at https://www.pgadmin.org/docs/pgadmin4/; the application's Help menu +links there. + +%prep +%autosetup -p1 -n %{name}-%{version} + +# The git tag is exported without .git: record the upstream commit the way +# pkg/src/build.sh does. (The "git:hash" npm script is never invoked -- %build +# runs npx webpack directly, not the bundle/git:hash scripts -- so it is left +# alone rather than rewritten with a sed that cannot safely match upstream's +# escaped-quote value.) +awk '/^commit:/ {print $2}' %{_sourcedir}/%{name}.obsinfo > web/commit_hash +# Upstream pins Yarn via "packageManager"; npm refuses to run with it set. +sed -i -z 's/,\n *"packageManager": "[^"]*"//' web/package.json +# Executable bits and shebangs on files that end up in site-packages +chmod -x web/pgadmin/misc/cloud/*.py web/pgadmin/misc/cloud/utils/*.py 2>/dev/null || : +sed -i '1{/^#!/d}' web/pgadmin/misc/cloud/*.py web/pgadmin/misc/cloud/utils/*.py 2>/dev/null || : +find web/pgadmin -name '*.py' -perm /111 -exec chmod -x {} + +# Vendored npm dependencies: package-lock.json + tarballs served by local-npm-registry +cp %{SOURCE20} web/package-lock.json + +%build +# Frontend +pushd web +local-npm-registry %{_sourcedir} install --legacy-peer-deps --ignore-scripts --no-audit --no-fund +NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072 npx webpack --config webpack.config.js +rm -rf node_modules +popd + +# Wheel (upstream's pip packaging helper; produces pgadmin4-<ver>-py3-none-any.whl). +# pkg/pip/setup_pip.py must run from a pip-build/ staging directory at the tree +# root -- it reads ../requirements.txt and ../web/ relative to its own cwd, and +# packages "pgadmin4" as a copy of web/ living next to that cwd (see +# pkg/pip/build.sh, which drives it the same way). build.sh itself needs .git, +# yarn and syft, none of which are available/needed here, so its file +# operations are reproduced directly instead of invoking the script: +# build.sh:44-58 stage web/ (minus regression/) as pip-build/pgadmin4/ -- our +# cp -a already picks up commit_hash (build.sh:65-67) and the +# webpack-generated pgadmin/static/js/generated/* files +# (build.sh:69-73), since both already exist on disk here, +# unlike a bare git checkout where git ls-files would miss them +# build.sh:75-89 also stages docs/ into the wheel -- skipped: the -doc +# subpackage already ships docs/en_US and duplicating ~39 MB +# of rst inside %{pgadmin_dir} is pointless +# build.sh:92-97 copy LICENSE/README.md from the tree root into pip-build/pgadmin4/ +# build.sh:100-105 SBOM generation via syft -- skipped: not available/needed +# build.sh:108-110 write a stub config_distro.py -- skipped: %install installs +# our own config_distro.py over whatever setup_pip.py packaged +# build.sh:113-114 MANIFEST.in -- needed, so setuptools includes the non-.py +# package data (html/css/js/rst) in the wheel +mkdir -p pip-build/pgadmin4 +cp -a web/. pip-build/pgadmin4/ +rm -rf pip-build/pgadmin4/regression +cp -a LICENSE README.md pip-build/pgadmin4/ +echo 'recursive-include pgadmin4 *' > pip-build/MANIFEST.in +pushd pip-build +%{__ospython} ../pkg/pip/setup_pip.py bdist_wheel +popd + +%install +%{__ospython} -m pip install --root %{buildroot} --no-deps --no-index --no-warn-script-location \ + pip-build/dist/pgadmin4-*.whl + +# The launcher and httpd conf hard-code the site-packages path: assert it. +test "%{python3_sitelib}" = "%{_prefix}/lib/python%{python3_buildversion}/site-packages" + +# distribution config + gunicorn entry points +install -m 0644 %{SOURCE1} %{buildroot}%{pgadmin_dir}/config_distro.py +install -m 0644 %{SOURCE2} %{buildroot}%{pgadmin_dir}/run_pgadmin.py +install -m 0644 %{SOURCE3} %{buildroot}%{pgadmin_dir}/gunicorn_config.py + +# The wheel installs two console scripts (pkg/pip/setup_pip.py entry_points): +# pgadmin4 (pgadmin4.pgAdmin4:main) and pgadmin4-cli (pgadmin4.setup:main); both +# are packaged below in %files. +# Data, log and configuration directories +install -d -m 0750 %{buildroot}%{pgadmin_data} +install -d -m 0755 %{buildroot}%{pgadmin_data}/storage +install -d -m 0700 %{buildroot}%{pgadmin_data}/sessions +install -d -m 0750 %{buildroot}%{pgadmin_log} +install -d -m 0750 %{buildroot}%{pgadmin_etc} +cat > %{buildroot}%{pgadmin_etc}/config_system.py <<'EOF' +# Site-specific pgAdmin 4 settings (highest precedence; imported after config_distro.py +# and config_local.py). Any setting from config.py may be set here, e.g. +# DEFAULT_SERVER = '0.0.0.0' +# MAX_LOGIN_ATTEMPTS = 3 +EOF + +# users and runtime dirs +install -D -m 0644 %{SOURCE6} %{buildroot}%{_sysusersdir}/%{name}.conf +install -D -m 0644 %{SOURCE7} %{buildroot}%{_tmpfilesdir}/%{name}.conf + +# -gunicorn +install -D -m 0755 %{SOURCE4} %{buildroot}%{_bindir}/percona-pgadmin4-gunicorn +install -D -m 0644 %{SOURCE5} %{buildroot}%{_unitdir}/%{name}.service +install -d -m 0755 %{buildroot}/run/pgadmin4 + +# -httpd +install -D -m 0644 %{SOURCE8} %{buildroot}%{_sysconfdir}/httpd/conf.d/%{name}.conf +install -D -m 0755 %{SOURCE9} %{buildroot}%{_bindir}/percona-pgadmin4-setup-web + +# -doc: rst sources (drop build tooling and editor artefacts) +install -d -m 0755 %{buildroot}%{_docdir}/%{name} +cp -a docs/en_US %{buildroot}%{_docdir}/%{name}/en_US +rm -f %{buildroot}%{_docdir}/%{name}/en_US/Makefile.sphinx \ + %{buildroot}%{_docdir}/%{name}/en_US/conf.py \ + %{buildroot}%{_docdir}/%{name}/en_US/build_code_snippet.py \ + %{buildroot}%{_docdir}/%{name}/en_US/.gitignore +find %{buildroot}%{_docdir}/%{name}/en_US -name '*.excalidraw' -delete +# LICENSE/README.md are not installed separately under %{_docdir}: both are +# already covered by %license/%doc from the tree root in %files (a second copy +# under %{_docdir}/%{name}/ would match no %files entry and fail as unpackaged). + +# byte-compile with the interpreter that runs the app +%{__ospython} -m compileall -q -s %{buildroot} -p / %{buildroot}%{pgadmin_dir} + +%check +PYTHONPATH=%{buildroot}%{pgadmin_dir} PGADMIN_CONFIG_HELP_PATH=/nonexistent \ + %{__ospython} -P -c "import config, config_distro; assert config.SERVER_MODE is True; assert config.HELP_PATH == '/nonexistent'" + +%pre +%sysusers_create_compat %{SOURCE6} + +%post gunicorn +%systemd_post %{name}.service +%tmpfiles_create %{_tmpfilesdir}/%{name}.conf + +%preun gunicorn +%systemd_preun %{name}.service + +%postun gunicorn +%systemd_postun_with_restart %{name}.service + +%files +%license LICENSE +%doc README.md +%{pgadmin_dir}/ +%{python3_sitelib}/pgadmin4-*.dist-info/ +%{_bindir}/pgadmin4 +%{_bindir}/pgadmin4-cli +%{_sysusersdir}/%{name}.conf +%dir %attr(0750,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data} +%dir %attr(0755,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data}/storage +%dir %attr(0700,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_data}/sessions +%dir %attr(0750,%{pgadmin_user},%{pgadmin_user}) %{pgadmin_log} +%dir %attr(0750,root,%{pgadmin_user}) %{pgadmin_etc} +%config(noreplace) %attr(0640,root,%{pgadmin_user}) %{pgadmin_etc}/config_system.py + +%files gunicorn +%{_bindir}/percona-pgadmin4-gunicorn +%{_unitdir}/%{name}.service +%{_tmpfilesdir}/%{name}.conf +%dir %attr(0755,%{pgadmin_user},%{pgadmin_user}) /run/pgadmin4 + +%files httpd +%config(noreplace) %{_sysconfdir}/httpd/conf.d/%{name}.conf +%{_bindir}/percona-pgadmin4-setup-web + +%files doc +%docdir %{_docdir}/%{name}/en_US +%{_docdir}/%{name}/en_US/ + +%changelog +* Fri Aug 28 2026 Percona Development Team <https://jira.percona.com> - 1.0.0-1 +- Initial percona-pgadmin4 package (pgAdmin 4, server mode) for UBI-9, ported + from openSUSE's pgadmin4.spec: gunicorn and httpd/mod_wsgi runtimes, rst docs. diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.sysusers b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.sysusers new file mode 100644 index 00000000..a1bd00b6 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.sysusers @@ -0,0 +1 @@ +u pgadmin - "pgAdmin 4" /var/lib/pgadmin /sbin/nologin diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.tmpfiles b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.tmpfiles new file mode 100644 index 00000000..76b2809d --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/percona-pgadmin4.tmpfiles @@ -0,0 +1 @@ +d /run/pgadmin4 0755 pgadmin pgadmin - diff --git a/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/run_pgadmin.py b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/run_pgadmin.py new file mode 100644 index 00000000..a80b35f9 --- /dev/null +++ b/root/ppg/devel/pgadmin/percona-pgadmin4/rpm/run_pgadmin.py @@ -0,0 +1,15 @@ +# WSGI entry point for gunicorn: `gunicorn … run_pgadmin:app`. +# Mirrors upstream's container run_pgadmin.py; the app module is pgAdmin4.py +# in this directory, which builds the Flask application on import. +import builtins +import os +import sys + +# Set SERVER_MODE explicitly for the builtin check performed by config.py. +builtins.SERVER_MODE = True + +_here = os.path.dirname(os.path.abspath(__file__)) +if _here not in sys.path: + sys.path.insert(0, _here) + +from pgAdmin4 import app # noqa: E402,F401 diff --git a/root/ppg/devel/pgadmin/project.yaml b/root/ppg/devel/pgadmin/project.yaml new file mode 100644 index 00000000..bd749c5f --- /dev/null +++ b/root/ppg/devel/pgadmin/project.yaml @@ -0,0 +1,31 @@ +title: pgAdmin 4 for Percona Distribution for PostgreSQL +description: | + pgAdmin 4 (percona-pgadmin4) and its Python 3.12 / Node.js build and runtime + dependency stack, built for UBI 9 only. Independent of the PostgreSQL major + version, hence a version-less devel project rather than a ppg:devel:<V> entry. + Design: docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md +repositories: + - name: UBI_9 + paths: + # python3.12-six / -dateutil / -psutil / -click already built there + - subproject: ppg:common:deps + repository: UBI_9 + # local-npm-registry (npm offline registry used at build time) + - subproject: common:deps:build + repository: UBI_9 + - project: ${REMOTE_OBS_ORG_INTERCONNECT}Fedora:EPEL:9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RedHat:UBI-9 + repository: standard + - project: ${REMOTE_OBS_ORG_INTERCONNECT}RockyLinux:9 + repository: devel + archs: [x86_64, aarch64] +project-config: | + %if "%_repository" == "UBI_9" + ExpandFlags: module:llvm-toolset-rhel9 + # pgAdmin's webpack build and local-npm-registry need Node.js >= 20; the + # obs-tools image pins the same major for npm_lockfile — bump both together. + ExpandFlags: module:nodejs:22 + # hdf-libs (EPEL split package) and hdf both provide libdf.so.0 and libmfhdf.so.0 + Prefer: hdf-libs + %endif diff --git a/root/ppg/devel/pgadmin/python3-alembic/obs/_service b/root/ppg/devel/pgadmin/python3-alembic/obs/_service new file mode 100644 index 00000000..f15a1417 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-alembic/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/a/alembic/alembic-1.19.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-alembic/package.yaml b/root/ppg/devel/pgadmin/python3-alembic/package.yaml new file mode 100644 index 00000000..d417ce33 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-alembic/package.yaml @@ -0,0 +1,5 @@ +title: alembic 1.19.1 for Python 3.12 +description: | + A database migration tool for SQLAlchemy. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-alembic/rpm/python3-alembic.spec b/root/ppg/devel/pgadmin/python3-alembic/rpm/python3-alembic.spec new file mode 100644 index 00000000..0fb98fbb --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-alembic/rpm/python3-alembic.spec @@ -0,0 +1,65 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-alembic +Version: 1.19.1 +Release: 1%{?dist} +Summary: A database migration tool for SQLAlchemy +License: MIT +URL: https://alembic.sqlalchemy.org +Source0: https://files.pythonhosted.org/packages/source/a/alembic/alembic-1.19.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-sqlalchemy >= 1.4.23 +BuildRequires: %{python3_pkgprefix}-mako +BuildRequires: %{python3_pkgprefix}-typing-extensions >= 4.12 + +Requires: %{python3_pkgprefix}-sqlalchemy >= 1.4.23 +Requires: %{python3_pkgprefix}-mako +Requires: %{python3_pkgprefix}-typing-extensions >= 4.12 + +%description +A database migration tool for SQLAlchemy. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n alembic-1.19.1 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import alembic" + +%files +%{python3_sitelib}/* +%{_bindir}/alembic + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.19.1-1 +- Package alembic 1.19.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-annotated-doc/obs/_service b/root/ppg/devel/pgadmin/python3-annotated-doc/obs/_service new file mode 100644 index 00000000..934839a2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-annotated-doc/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/a/annotated-doc/annotated_doc-0.0.5.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-annotated-doc/package.yaml b/root/ppg/devel/pgadmin/python3-annotated-doc/package.yaml new file mode 100644 index 00000000..d303dba2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-annotated-doc/package.yaml @@ -0,0 +1,5 @@ +title: annotated-doc 0.0.5 for Python 3.12 +description: | + Document parameters, class attributes, return types, and variables inline, with Annotated. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-annotated-doc/rpm/python3-annotated-doc.spec b/root/ppg/devel/pgadmin/python3-annotated-doc/rpm/python3-annotated-doc.spec new file mode 100644 index 00000000..fb79aeeb --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-annotated-doc/rpm/python3-annotated-doc.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-annotated-doc +Version: 0.0.5 +Release: 1%{?dist} +Summary: Document parameters, class attributes, return types, and variables inline, with Annotated +License: MIT +URL: https://github.com/fastapi/annotated-doc +Source0: https://files.pythonhosted.org/packages/source/a/annotated-doc/annotated_doc-0.0.5.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-pdm-backend + +%description +Document parameters, class attributes, return types, and variables inline, with Annotated. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n annotated_doc-0.0.5 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import annotated_doc" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.0.5-1 +- Package annotated-doc 0.0.5 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-authlib/obs/_service b/root/ppg/devel/pgadmin/python3-authlib/obs/_service new file mode 100644 index 00000000..45e2b871 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-authlib/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/A/Authlib/authlib-1.6.12.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-authlib/package.yaml b/root/ppg/devel/pgadmin/python3-authlib/package.yaml new file mode 100644 index 00000000..5c084488 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-authlib/package.yaml @@ -0,0 +1,5 @@ +title: Authlib 1.6.12 for Python 3.12 +description: | + The ultimate Python library in building OAuth and OpenID Connect servers and clients. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-authlib/rpm/python3-authlib.spec b/root/ppg/devel/pgadmin/python3-authlib/rpm/python3-authlib.spec new file mode 100644 index 00000000..5c4c898b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-authlib/rpm/python3-authlib.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-authlib +Version: 1.6.12 +Release: 1%{?dist} +Summary: The ultimate Python library in building OAuth and OpenID Connect servers and clients +License: BSD-3-Clause +URL: https://github.com/authlib/authlib +Source0: https://files.pythonhosted.org/packages/source/A/Authlib/authlib-1.6.12.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: python3.12-cryptography + +Requires: python3.12-cryptography + +%description +The ultimate Python library in building OAuth and OpenID Connect servers and clients. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n authlib-1.6.12 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import authlib" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.6.12-1 +- Package Authlib 1.6.12 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-babel/obs/_service b/root/ppg/devel/pgadmin/python3-babel/obs/_service new file mode 100644 index 00000000..47380868 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-babel/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/babel/babel-2.18.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-babel/package.yaml b/root/ppg/devel/pgadmin/python3-babel/package.yaml new file mode 100644 index 00000000..e9de8a43 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-babel/package.yaml @@ -0,0 +1,5 @@ +title: babel 2.18.0 for Python 3.12 +description: | + Internationalization utilities. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-babel/rpm/python3-babel.spec b/root/ppg/devel/pgadmin/python3-babel/rpm/python3-babel.spec new file mode 100644 index 00000000..303b410f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-babel/rpm/python3-babel.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-babel +Version: 2.18.0 +Release: 1%{?dist} +Summary: Internationalization utilities +License: BSD-3-Clause +URL: https://babel.pocoo.org/ +Source0: https://files.pythonhosted.org/packages/source/b/babel/babel-2.18.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Internationalization utilities. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n babel-2.18.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import babel" + +%files +%{python3_sitelib}/* +%{_bindir}/pybabel + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.18.0-1 +- Package babel 2.18.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-backports-zstd/obs/_service b/root/ppg/devel/pgadmin/python3-backports-zstd/obs/_service new file mode 100644 index 00000000..e888bd4d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-backports-zstd/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/backports.zstd/backports_zstd-1.7.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-backports-zstd/package.yaml b/root/ppg/devel/pgadmin/python3-backports-zstd/package.yaml new file mode 100644 index 00000000..5cd9c027 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-backports-zstd/package.yaml @@ -0,0 +1,5 @@ +title: backports.zstd 1.7.0 for Python 3.12 +description: | + Backport of compression.zstd. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-backports-zstd/rpm/python3-backports-zstd.spec b/root/ppg/devel/pgadmin/python3-backports-zstd/rpm/python3-backports-zstd.spec new file mode 100644 index 00000000..a136d945 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-backports-zstd/rpm/python3-backports-zstd.spec @@ -0,0 +1,56 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-backports-zstd +Version: 1.7.0 +Release: 1%{?dist} +Summary: Backport of compression.zstd +License: PSF-2.0 +URL: https://github.com/rogdham/backports.zstd +Source0: https://files.pythonhosted.org/packages/source/b/backports.zstd/backports_zstd-1.7.0.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: libzstd-devel +BuildRequires: gcc + +%description +Backport of compression.zstd. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n backports_zstd-1.7.0 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import backports.zstd" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.7.0-1 +- Package backports.zstd 1.7.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-bcrypt/obs/_service b/root/ppg/devel/pgadmin/python3-bcrypt/obs/_service new file mode 100644 index 00000000..1ad719ad --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bcrypt/obs/_service @@ -0,0 +1,13 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/bcrypt/bcrypt-5.0.0.tar.gz</param> + </service> + <!-- Vendor the Rust crate's dependencies (Cargo.toml lives under src/_bcrypt) + for an offline OBS build; produces vendor.tar.gz incl. .cargo/config. --> + <service mode="buildtime" name="cargo_vendor"> + <param name="src">bcrypt-5.0.0.tar.gz</param> + <param name="cargotoml">src/_bcrypt/Cargo.toml</param> + <param name="compression">gz</param> + <param name="update">false</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-bcrypt/package.yaml b/root/ppg/devel/pgadmin/python3-bcrypt/package.yaml new file mode 100644 index 00000000..01332e68 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bcrypt/package.yaml @@ -0,0 +1,5 @@ +title: bcrypt 5.0.0 for Python 3.12 +description: | + Modern password hashing for your software and your servers. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-bcrypt/rpm/python3-bcrypt.spec b/root/ppg/devel/pgadmin/python3-bcrypt/rpm/python3-bcrypt.spec new file mode 100644 index 00000000..bffaf0c5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bcrypt/rpm/python3-bcrypt.spec @@ -0,0 +1,60 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-bcrypt +Version: 5.0.0 +Release: 1%{?dist} +Summary: Modern password hashing for your software and your servers +License: Apache-2.0 +URL: https://pypi.org/project/bcrypt/ +Source0: https://files.pythonhosted.org/packages/source/b/bcrypt/bcrypt-5.0.0.tar.gz +Source1: vendor.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools-rust +BuildRequires: cargo +BuildRequires: rust +BuildRequires: gcc + +%description +Modern password hashing for your software and your servers. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n bcrypt-5.0.0 -a1 + +%build +export CARGO_NET_OFFLINE=true +# cargo_vendor put vendor/ and .cargo/config.toml next to src/_bcrypt/Cargo.toml +export CARGO_HOME=$PWD/src/_bcrypt/.cargo +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import bcrypt" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 5.0.0-1 +- Package bcrypt 5.0.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-bidict/obs/_service b/root/ppg/devel/pgadmin/python3-bidict/obs/_service new file mode 100644 index 00000000..a7728fdb --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bidict/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/bidict/bidict-0.23.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-bidict/package.yaml b/root/ppg/devel/pgadmin/python3-bidict/package.yaml new file mode 100644 index 00000000..cc028506 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bidict/package.yaml @@ -0,0 +1,5 @@ +title: bidict 0.23.1 for Python 3.12 +description: | + The bidirectional mapping library for Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-bidict/rpm/python3-bidict.spec b/root/ppg/devel/pgadmin/python3-bidict/rpm/python3-bidict.spec new file mode 100644 index 00000000..b1ada870 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-bidict/rpm/python3-bidict.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-bidict +Version: 0.23.1 +Release: 1%{?dist} +Summary: The bidirectional mapping library for Python +License: MPL-2.0 +URL: https://pypi.org/project/bidict/ +Source0: https://files.pythonhosted.org/packages/source/b/bidict/bidict-0.23.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +The bidirectional mapping library for Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n bidict-0.23.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import bidict" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.23.1-1 +- Package bidict 0.23.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-blinker/obs/_service b/root/ppg/devel/pgadmin/python3-blinker/obs/_service new file mode 100644 index 00000000..be825ef0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-blinker/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/blinker/blinker-1.9.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-blinker/package.yaml b/root/ppg/devel/pgadmin/python3-blinker/package.yaml new file mode 100644 index 00000000..5db0ac48 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-blinker/package.yaml @@ -0,0 +1,5 @@ +title: blinker 1.9.0 for Python 3.12 +description: | + Fast, simple object-to-object and broadcast signaling. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-blinker/rpm/python3-blinker.spec b/root/ppg/devel/pgadmin/python3-blinker/rpm/python3-blinker.spec new file mode 100644 index 00000000..9e073b90 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-blinker/rpm/python3-blinker.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-blinker +Version: 1.9.0 +Release: 1%{?dist} +Summary: Fast, simple object-to-object and broadcast signaling +License: MIT +URL: https://github.com/pallets-eco/blinker/ +Source0: https://files.pythonhosted.org/packages/source/b/blinker/blinker-1.9.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Fast, simple object-to-object and broadcast signaling. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n blinker-1.9.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import blinker" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.9.0-1 +- Package blinker 1.9.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-brotli/obs/_service b/root/ppg/devel/pgadmin/python3-brotli/obs/_service new file mode 100644 index 00000000..2716abc1 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-brotli/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/b/brotli/brotli-1.2.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-brotli/package.yaml b/root/ppg/devel/pgadmin/python3-brotli/package.yaml new file mode 100644 index 00000000..e05beebd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-brotli/package.yaml @@ -0,0 +1,5 @@ +title: brotli 1.2.0 for Python 3.12 +description: | + Python bindings for the Brotli compression library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-brotli/rpm/python3-brotli.spec b/root/ppg/devel/pgadmin/python3-brotli/rpm/python3-brotli.spec new file mode 100644 index 00000000..fe2601db --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-brotli/rpm/python3-brotli.spec @@ -0,0 +1,53 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-brotli +Version: 1.2.0 +Release: 1%{?dist} +Summary: Python bindings for the Brotli compression library +License: MIT +URL: https://github.com/google/brotli +Source0: https://files.pythonhosted.org/packages/source/b/brotli/brotli-1.2.0.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: gcc-c++ + +%description +Python bindings for the Brotli compression library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n brotli-1.2.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import brotli" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.2.0-1 +- Package brotli 1.2.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-certifi/obs/_service b/root/ppg/devel/pgadmin/python3-certifi/obs/_service new file mode 100644 index 00000000..1d2e0ce2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-certifi/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/c/certifi/certifi-2026.6.17.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-certifi/package.yaml b/root/ppg/devel/pgadmin/python3-certifi/package.yaml new file mode 100644 index 00000000..d1352e18 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-certifi/package.yaml @@ -0,0 +1,5 @@ +title: certifi 2026.6.17 for Python 3.12 +description: | + Python package for providing Mozilla's CA Bundle. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-certifi/rpm/python3-certifi.spec b/root/ppg/devel/pgadmin/python3-certifi/rpm/python3-certifi.spec new file mode 100644 index 00000000..b3b63d75 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-certifi/rpm/python3-certifi.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-certifi +Version: 2026.6.17 +Release: 1%{?dist} +Summary: Python package for providing Mozilla's CA Bundle +License: MPL-2.0 +URL: https://github.com/certifi/python-certifi +Source0: https://files.pythonhosted.org/packages/source/c/certifi/certifi-2026.6.17.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Python package for providing Mozilla's CA Bundle. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n certifi-2026.6.17 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import certifi" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2026.6.17-1 +- Package certifi 2026.6.17 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-click/obs/_aggregate b/root/ppg/devel/pgadmin/python3-click/obs/_aggregate new file mode 100644 index 00000000..2f3a8334 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-click/obs/_aggregate @@ -0,0 +1,5 @@ +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-click</package> + </aggregate> +</aggregatelist> diff --git a/root/ppg/devel/pgadmin/python3-cython/obs/_service b/root/ppg/devel/pgadmin/python3-cython/obs/_service new file mode 100644 index 00000000..7c646461 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-cython/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/C/Cython/cython-3.2.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-cython/package.yaml b/root/ppg/devel/pgadmin/python3-cython/package.yaml new file mode 100644 index 00000000..ff3ef0e3 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-cython/package.yaml @@ -0,0 +1,5 @@ +title: Cython 3.2.4 for Python 3.12 +description: | + The Cython compiler for writing C extensions in the Python language. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-cython/rpm/python3-cython.spec b/root/ppg/devel/pgadmin/python3-cython/rpm/python3-cython.spec new file mode 100644 index 00000000..69db844c --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-cython/rpm/python3-cython.spec @@ -0,0 +1,57 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-cython +Version: 3.2.4 +Release: 1%{?dist} +Summary: The Cython compiler for writing C extensions in the Python language +License: Apache-2.0 +URL: https://cython.org/ +Source0: https://files.pythonhosted.org/packages/source/C/Cython/cython-3.2.4.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: gcc + +%description +The Cython compiler for writing C extensions in the Python language. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n cython-3.2.4 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitearch} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import Cython" + +%files -f INSTALLED_FILES +%defattr(-,root,root) +%{_bindir}/cygdb +%{_bindir}/cython +%{_bindir}/cythonize + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.2.4-1 +- Package Cython 3.2.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-dateutil/obs/_aggregate b/root/ppg/devel/pgadmin/python3-dateutil/obs/_aggregate new file mode 100644 index 00000000..7f2c3cc4 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-dateutil/obs/_aggregate @@ -0,0 +1,5 @@ +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-dateutil</package> + </aggregate> +</aggregatelist> diff --git a/root/ppg/devel/pgadmin/python3-decorator/obs/_service b/root/ppg/devel/pgadmin/python3-decorator/obs/_service new file mode 100644 index 00000000..a6801369 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-decorator/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/d/decorator/decorator-5.3.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-decorator/package.yaml b/root/ppg/devel/pgadmin/python3-decorator/package.yaml new file mode 100644 index 00000000..212eb2ae --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-decorator/package.yaml @@ -0,0 +1,5 @@ +title: decorator 5.3.1 for Python 3.12 +description: | + Decorators for Humans. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-decorator/rpm/python3-decorator.spec b/root/ppg/devel/pgadmin/python3-decorator/rpm/python3-decorator.spec new file mode 100644 index 00000000..acb87426 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-decorator/rpm/python3-decorator.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-decorator +Version: 5.3.1 +Release: 1%{?dist} +Summary: Decorators for Humans +License: BSD-2-Clause +URL: https://pypi.org/project/decorator/ +Source0: https://files.pythonhosted.org/packages/source/d/decorator/decorator-5.3.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Decorators for Humans. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n decorator-5.3.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import decorator" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 5.3.1-1 +- Package decorator 5.3.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-dns/obs/_aggregate b/root/ppg/devel/pgadmin/python3-dns/obs/_aggregate new file mode 100644 index 00000000..5e02ae09 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-dns/obs/_aggregate @@ -0,0 +1,5 @@ +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-dns</package> + </aggregate> +</aggregatelist> diff --git a/root/ppg/devel/pgadmin/python3-email-validator/obs/_service b/root/ppg/devel/pgadmin/python3-email-validator/obs/_service new file mode 100644 index 00000000..c41a1dbf --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-email-validator/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/e/email-validator/email_validator-2.3.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-email-validator/package.yaml b/root/ppg/devel/pgadmin/python3-email-validator/package.yaml new file mode 100644 index 00000000..0680c271 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-email-validator/package.yaml @@ -0,0 +1,5 @@ +title: email-validator 2.3.0 for Python 3.12 +description: | + A robust email address syntax and deliverability validation library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-email-validator/rpm/python3-email-validator.spec b/root/ppg/devel/pgadmin/python3-email-validator/rpm/python3-email-validator.spec new file mode 100644 index 00000000..e972f52e --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-email-validator/rpm/python3-email-validator.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-email-validator +Version: 2.3.0 +Release: 1%{?dist} +Summary: A robust email address syntax and deliverability validation library +License: Unlicense +URL: https://github.com/JoshData/python-email-validator +Source0: https://files.pythonhosted.org/packages/source/e/email-validator/email_validator-2.3.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-dns +BuildRequires: python3.12-idna + +Requires: %{python3_pkgprefix}-dns +Requires: python3.12-idna + +%description +A robust email address syntax and deliverability validation library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n email_validator-2.3.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import email_validator" + +%files +%{python3_sitelib}/* +%{_bindir}/email_validator + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.3.0-1 +- Package email-validator 2.3.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-babel/obs/_service b/root/ppg/devel/pgadmin/python3-flask-babel/obs/_service new file mode 100644 index 00000000..5295116f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-babel/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/f/flask-babel/flask_babel-4.0.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-babel/package.yaml b/root/ppg/devel/pgadmin/python3-flask-babel/package.yaml new file mode 100644 index 00000000..e2294151 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-babel/package.yaml @@ -0,0 +1,5 @@ +title: flask-babel 4.0.0 for Python 3.12 +description: | + Adds i18n/l10n support for Flask applications. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-babel/rpm/python3-flask-babel.spec b/root/ppg/devel/pgadmin/python3-flask-babel/rpm/python3-flask-babel.spec new file mode 100644 index 00000000..c4ae0af4 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-babel/rpm/python3-flask-babel.spec @@ -0,0 +1,65 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-babel +Version: 4.0.0 +Release: 1%{?dist} +Summary: Adds i18n/l10n support for Flask applications +License: BSD-3-Clause +URL: https://github.com/python-babel/flask-babel +Source0: https://files.pythonhosted.org/packages/source/f/flask-babel/flask_babel-4.0.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-poetry-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-pytz +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-babel +BuildRequires: %{python3_pkgprefix}-jinja2 + +Requires: %{python3_pkgprefix}-pytz +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-babel +Requires: %{python3_pkgprefix}-jinja2 + +%description +Adds i18n/l10n support for Flask applications. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_babel-4.0.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_babel" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.0.0-1 +- Package flask-babel 4.0.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-compress/obs/_service b/root/ppg/devel/pgadmin/python3-flask-compress/obs/_service new file mode 100644 index 00000000..af35bff2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-compress/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Compress/flask_compress-1.24.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-compress/package.yaml b/root/ppg/devel/pgadmin/python3-flask-compress/package.yaml new file mode 100644 index 00000000..cf2c4d44 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-compress/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Compress 1.24 for Python 3.12 +description: | + Compress responses in your Flask app with gzip, deflate, brotli or zstandard. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-compress/rpm/python3-flask-compress.spec b/root/ppg/devel/pgadmin/python3-flask-compress/rpm/python3-flask-compress.spec new file mode 100644 index 00000000..54bb07f2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-compress/rpm/python3-flask-compress.spec @@ -0,0 +1,63 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-compress +Version: 1.24 +Release: 1%{?dist} +Summary: Compress responses in your Flask app with gzip, deflate, brotli or zstandard +License: MIT +URL: https://github.com/colour-science/flask-compress +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Compress/flask_compress-1.24.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools_scm +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-brotli +BuildRequires: %{python3_pkgprefix}-backports-zstd + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-brotli +Requires: %{python3_pkgprefix}-backports-zstd + +%description +Compress responses in your Flask app with gzip, deflate, brotli or zstandard. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_compress-1.24 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_compress" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.24-1 +- Package Flask-Compress 1.24 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-login/obs/_service b/root/ppg/devel/pgadmin/python3-flask-login/obs/_service new file mode 100644 index 00000000..8e0a32c2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-login/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Login/Flask-Login-0.6.3.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-login/package.yaml b/root/ppg/devel/pgadmin/python3-flask-login/package.yaml new file mode 100644 index 00000000..291dc3f5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-login/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Login 0.6.3 for Python 3.12 +description: | + User authentication and session management for Flask. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-login/rpm/python3-flask-login.spec b/root/ppg/devel/pgadmin/python3-flask-login/rpm/python3-flask-login.spec new file mode 100644 index 00000000..335a31ab --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-login/rpm/python3-flask-login.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-login +Version: 0.6.3 +Release: 1%{?dist} +Summary: User authentication and session management for Flask +License: MIT +URL: https://github.com/maxcountryman/flask-login +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Login/Flask-Login-0.6.3.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask >= 1.0.4 +BuildRequires: %{python3_pkgprefix}-werkzeug >= 1.0.1 + +Requires: %{python3_pkgprefix}-flask >= 1.0.4 +Requires: %{python3_pkgprefix}-werkzeug >= 1.0.1 + +%description +User authentication and session management for Flask. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n Flask-Login-0.6.3 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_login" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.6.3-1 +- Package Flask-Login 0.6.3 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-mail/obs/_service b/root/ppg/devel/pgadmin/python3-flask-mail/obs/_service new file mode 100644 index 00000000..a23af8e1 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-mail/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Mail/flask_mail-0.10.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-mail/package.yaml b/root/ppg/devel/pgadmin/python3-flask-mail/package.yaml new file mode 100644 index 00000000..6fdc44b0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-mail/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Mail 0.10.0 for Python 3.12 +description: | + Flask extension for sending email. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-mail/rpm/python3-flask-mail.spec b/root/ppg/devel/pgadmin/python3-flask-mail/rpm/python3-flask-mail.spec new file mode 100644 index 00000000..0bfbbddf --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-mail/rpm/python3-flask-mail.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-mail +Version: 0.10.0 +Release: 1%{?dist} +Summary: Flask extension for sending email +License: BSD-3-Clause +URL: https://github.com/pallets-eco/flask-mail/ +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Mail/flask_mail-0.10.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-blinker + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-blinker + +%description +Flask extension for sending email. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_mail-0.10.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_mail" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.10.0-1 +- Package Flask-Mail 0.10.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-migrate/obs/_service b/root/ppg/devel/pgadmin/python3-flask-migrate/obs/_service new file mode 100644 index 00000000..723e4e55 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-migrate/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Migrate/flask_migrate-4.1.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-migrate/package.yaml b/root/ppg/devel/pgadmin/python3-flask-migrate/package.yaml new file mode 100644 index 00000000..6111c0c1 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-migrate/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Migrate 4.1.0 for Python 3.12 +description: | + SQLAlchemy database migrations for Flask applications using Alembic. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-migrate/rpm/python3-flask-migrate.spec b/root/ppg/devel/pgadmin/python3-flask-migrate/rpm/python3-flask-migrate.spec new file mode 100644 index 00000000..93e66f58 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-migrate/rpm/python3-flask-migrate.spec @@ -0,0 +1,62 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-migrate +Version: 4.1.0 +Release: 1%{?dist} +Summary: SQLAlchemy database migrations for Flask applications using Alembic +License: MIT +URL: https://github.com/miguelgrinberg/flask-migrate +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Migrate/flask_migrate-4.1.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask >= 0.9 +BuildRequires: %{python3_pkgprefix}-flask-sqlalchemy >= 1.0 +BuildRequires: %{python3_pkgprefix}-alembic >= 1.9.0 + +Requires: %{python3_pkgprefix}-flask >= 0.9 +Requires: %{python3_pkgprefix}-flask-sqlalchemy >= 1.0 +Requires: %{python3_pkgprefix}-alembic >= 1.9.0 + +%description +SQLAlchemy database migrations for Flask applications using Alembic. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_migrate-4.1.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_migrate" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.1.0-1 +- Package Flask-Migrate 4.1.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-paranoid/obs/_service b/root/ppg/devel/pgadmin/python3-flask-paranoid/obs/_service new file mode 100644 index 00000000..ac9c899d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-paranoid/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Paranoid/Flask-Paranoid-0.3.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-paranoid/package.yaml b/root/ppg/devel/pgadmin/python3-flask-paranoid/package.yaml new file mode 100644 index 00000000..2e6011df --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-paranoid/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Paranoid 0.3.0 for Python 3.12 +description: | + Simple user session protection. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-paranoid/rpm/python3-flask-paranoid.spec b/root/ppg/devel/pgadmin/python3-flask-paranoid/rpm/python3-flask-paranoid.spec new file mode 100644 index 00000000..c5dcd860 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-paranoid/rpm/python3-flask-paranoid.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-paranoid +Version: 0.3.0 +Release: 1%{?dist} +Summary: Simple user session protection +License: MIT +URL: https://github.com/miguelgrinberg/flask-paranoid +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Paranoid/Flask-Paranoid-0.3.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask + +Requires: %{python3_pkgprefix}-flask + +%description +Simple user session protection. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n Flask-Paranoid-0.3.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_paranoid" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.3.0-1 +- Package Flask-Paranoid 0.3.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-principal/obs/_service b/root/ppg/devel/pgadmin/python3-flask-principal/obs/_service new file mode 100644 index 00000000..f8f2ab20 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-principal/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Principal/Flask-Principal-0.4.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-principal/package.yaml b/root/ppg/devel/pgadmin/python3-flask-principal/package.yaml new file mode 100644 index 00000000..0b9f99a8 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-principal/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Principal 0.4.0 for Python 3.12 +description: | + Identity management for flask. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-principal/rpm/python3-flask-principal.spec b/root/ppg/devel/pgadmin/python3-flask-principal/rpm/python3-flask-principal.spec new file mode 100644 index 00000000..40af36f3 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-principal/rpm/python3-flask-principal.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-principal +Version: 0.4.0 +Release: 1%{?dist} +Summary: Identity management for flask +License: MIT +URL: http://packages.python.org/Flask-Principal/ +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Principal/Flask-Principal-0.4.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-blinker + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-blinker + +%description +Identity management for flask. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n Flask-Principal-0.4.0 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_principal" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.4.0-1 +- Package Flask-Principal 0.4.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-security-too/obs/_service b/root/ppg/devel/pgadmin/python3-flask-security-too/obs/_service new file mode 100644 index 00000000..849752da --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-security-too/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-Security-Too/flask_security_too-5.8.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-security-too/package.yaml b/root/ppg/devel/pgadmin/python3-flask-security-too/package.yaml new file mode 100644 index 00000000..66b50473 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-security-too/package.yaml @@ -0,0 +1,5 @@ +title: Flask-Security-Too 5.8.2 for Python 3.12 +description: | + Quickly add security features to your Flask application. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-security-too/rpm/python3-flask-security-too.spec b/root/ppg/devel/pgadmin/python3-flask-security-too/rpm/python3-flask-security-too.spec new file mode 100644 index 00000000..dc666f17 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-security-too/rpm/python3-flask-security-too.spec @@ -0,0 +1,73 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-security-too +Version: 5.8.2 +Release: 1%{?dist} +Summary: Quickly add security features to your Flask application +License: MIT +URL: https://github.com/pallets-eco/flask-security +Source0: https://files.pythonhosted.org/packages/source/F/Flask-Security-Too/flask_security_too-5.8.2.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask >= 3.1.1 +BuildRequires: %{python3_pkgprefix}-flask-login >= 0.6.3 +BuildRequires: %{python3_pkgprefix}-flask-principal >= 0.4.0 +BuildRequires: %{python3_pkgprefix}-flask-wtf >= 1.1.2 +BuildRequires: %{python3_pkgprefix}-email-validator >= 2.3.0 +BuildRequires: %{python3_pkgprefix}-markupsafe >= 2.1.0 +BuildRequires: %{python3_pkgprefix}-libpass >= 1.9.3 +BuildRequires: %{python3_pkgprefix}-wtforms >= 3.0.0 + +Requires: %{python3_pkgprefix}-flask >= 3.1.1 +Requires: %{python3_pkgprefix}-flask-login >= 0.6.3 +Requires: %{python3_pkgprefix}-flask-principal >= 0.4.0 +Requires: %{python3_pkgprefix}-flask-wtf >= 1.1.2 +Requires: %{python3_pkgprefix}-email-validator >= 2.3.0 +Requires: %{python3_pkgprefix}-markupsafe >= 2.1.0 +Requires: %{python3_pkgprefix}-libpass >= 1.9.3 +Requires: %{python3_pkgprefix}-wtforms >= 3.0.0 + +%description +Quickly add security features to your Flask application. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_security_too-5.8.2 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_security" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 5.8.2-1 +- Package Flask-Security-Too 5.8.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-socketio/obs/_service b/root/ppg/devel/pgadmin/python3-flask-socketio/obs/_service new file mode 100644 index 00000000..10b4f6d8 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-socketio/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-SocketIO/flask_socketio-5.6.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-socketio/package.yaml b/root/ppg/devel/pgadmin/python3-flask-socketio/package.yaml new file mode 100644 index 00000000..097c1b0b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-socketio/package.yaml @@ -0,0 +1,5 @@ +title: Flask-SocketIO 5.6.1 for Python 3.12 +description: | + Socket.IO integration for Flask applications. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-socketio/rpm/python3-flask-socketio.spec b/root/ppg/devel/pgadmin/python3-flask-socketio/rpm/python3-flask-socketio.spec new file mode 100644 index 00000000..4aedf000 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-socketio/rpm/python3-flask-socketio.spec @@ -0,0 +1,68 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-socketio +Version: 5.6.1 +Release: 1%{?dist} +Summary: Socket.IO integration for Flask applications +License: MIT +URL: https://github.com/miguelgrinberg/flask-socketio +Source0: https://files.pythonhosted.org/packages/source/F/Flask-SocketIO/flask_socketio-5.6.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-blinker +BuildRequires: %{python3_pkgprefix}-click +BuildRequires: %{python3_pkgprefix}-flask >= 2.1.0 +BuildRequires: %{python3_pkgprefix}-jinja2 +BuildRequires: %{python3_pkgprefix}-python-socketio >= 5.12.0 +BuildRequires: %{python3_pkgprefix}-werkzeug + +Requires: %{python3_pkgprefix}-blinker +Requires: %{python3_pkgprefix}-click +Requires: %{python3_pkgprefix}-flask >= 2.1.0 +Requires: %{python3_pkgprefix}-jinja2 +Requires: %{python3_pkgprefix}-python-socketio >= 5.12.0 +Requires: %{python3_pkgprefix}-werkzeug + +%description +Socket.IO integration for Flask applications. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_socketio-5.6.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_socketio" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 5.6.1-1 +- Package Flask-SocketIO 5.6.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/obs/_service b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/obs/_service new file mode 100644 index 00000000..fa92d7b8 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-SQLAlchemy/flask_sqlalchemy-3.1.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/package.yaml b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/package.yaml new file mode 100644 index 00000000..5e96a5c5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/package.yaml @@ -0,0 +1,5 @@ +title: Flask-SQLAlchemy 3.1.1 for Python 3.12 +description: | + Add SQLAlchemy support to your Flask application. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/rpm/python3-flask-sqlalchemy.spec b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/rpm/python3-flask-sqlalchemy.spec new file mode 100644 index 00000000..d5efe0c6 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-sqlalchemy/rpm/python3-flask-sqlalchemy.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-sqlalchemy +Version: 3.1.1 +Release: 1%{?dist} +Summary: Add SQLAlchemy support to your Flask application +License: BSD-3-Clause +URL: https://pypi.org/project/Flask-SQLAlchemy/ +Source0: https://files.pythonhosted.org/packages/source/F/Flask-SQLAlchemy/flask_sqlalchemy-3.1.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask >= 2.2.5 +BuildRequires: %{python3_pkgprefix}-sqlalchemy >= 2.0.16 + +Requires: %{python3_pkgprefix}-flask >= 2.2.5 +Requires: %{python3_pkgprefix}-sqlalchemy >= 2.0.16 + +%description +Add SQLAlchemy support to your Flask application. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_sqlalchemy-3.1.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_sqlalchemy" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.1.1-1 +- Package Flask-SQLAlchemy 3.1.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask-wtf/obs/_service b/root/ppg/devel/pgadmin/python3-flask-wtf/obs/_service new file mode 100644 index 00000000..c3fd06e2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-wtf/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask-WTF/flask_wtf-1.3.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask-wtf/package.yaml b/root/ppg/devel/pgadmin/python3-flask-wtf/package.yaml new file mode 100644 index 00000000..b593f1ba --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-wtf/package.yaml @@ -0,0 +1,5 @@ +title: Flask-WTF 1.3.0 for Python 3.12 +description: | + Form rendering, validation, and CSRF protection for Flask with WTForms. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask-wtf/rpm/python3-flask-wtf.spec b/root/ppg/devel/pgadmin/python3-flask-wtf/rpm/python3-flask-wtf.spec new file mode 100644 index 00000000..6067e3cd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask-wtf/rpm/python3-flask-wtf.spec @@ -0,0 +1,67 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask-wtf +Version: 1.3.0 +Release: 1%{?dist} +Summary: Form rendering, validation, and CSRF protection for Flask with WTForms +License: BSD-3-Clause +URL: https://pypi.org/project/Flask-WTF/ +Source0: https://files.pythonhosted.org/packages/source/F/Flask-WTF/flask_wtf-1.3.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-flask +BuildRequires: %{python3_pkgprefix}-itsdangerous +BuildRequires: %{python3_pkgprefix}-wtforms + +Requires: %{python3_pkgprefix}-flask +Requires: %{python3_pkgprefix}-itsdangerous +Requires: %{python3_pkgprefix}-wtforms + +%description +Form rendering, validation, and CSRF protection for Flask with WTForms. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask_wtf-1.3.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask_wtf" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.3.0-1 +- Package Flask-WTF 1.3.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-flask/obs/_service b/root/ppg/devel/pgadmin/python3-flask/obs/_service new file mode 100644 index 00000000..748860a2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/F/Flask/flask-3.1.3.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-flask/package.yaml b/root/ppg/devel/pgadmin/python3-flask/package.yaml new file mode 100644 index 00000000..e5e2c5bd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask/package.yaml @@ -0,0 +1,5 @@ +title: Flask 3.1.3 for Python 3.12 +description: | + A simple framework for building complex web applications. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-flask/rpm/python3-flask.spec b/root/ppg/devel/pgadmin/python3-flask/rpm/python3-flask.spec new file mode 100644 index 00000000..d56f60b2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-flask/rpm/python3-flask.spec @@ -0,0 +1,70 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-flask +Version: 3.1.3 +Release: 1%{?dist} +Summary: A simple framework for building complex web applications +License: BSD-3-Clause +URL: https://github.com/pallets/flask/ +Source0: https://files.pythonhosted.org/packages/source/F/Flask/flask-3.1.3.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-blinker >= 1.9.0 +BuildRequires: %{python3_pkgprefix}-click +BuildRequires: %{python3_pkgprefix}-itsdangerous >= 2.2.0 +BuildRequires: %{python3_pkgprefix}-jinja2 >= 3.1.2 +BuildRequires: %{python3_pkgprefix}-markupsafe >= 2.1.1 +BuildRequires: %{python3_pkgprefix}-werkzeug >= 3.1.0 + +Requires: %{python3_pkgprefix}-blinker >= 1.9.0 +Requires: %{python3_pkgprefix}-click +Requires: %{python3_pkgprefix}-itsdangerous >= 2.2.0 +Requires: %{python3_pkgprefix}-jinja2 >= 3.1.2 +Requires: %{python3_pkgprefix}-markupsafe >= 2.1.1 +Requires: %{python3_pkgprefix}-werkzeug >= 3.1.0 + +%description +A simple framework for building complex web applications. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n flask-3.1.3 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import flask" + +%files +%{python3_sitelib}/* +%{_bindir}/flask + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.1.3-1 +- Package Flask 3.1.3 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-greenlet/obs/_service b/root/ppg/devel/pgadmin/python3-greenlet/obs/_service new file mode 100644 index 00000000..a2a13590 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-greenlet/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/g/greenlet/greenlet-3.5.5.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-greenlet/package.yaml b/root/ppg/devel/pgadmin/python3-greenlet/package.yaml new file mode 100644 index 00000000..71105fda --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-greenlet/package.yaml @@ -0,0 +1,5 @@ +title: greenlet 3.5.5 for Python 3.12 +description: | + Lightweight in-process concurrent programming. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-greenlet/rpm/python3-greenlet.spec b/root/ppg/devel/pgadmin/python3-greenlet/rpm/python3-greenlet.spec new file mode 100644 index 00000000..60a3729a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-greenlet/rpm/python3-greenlet.spec @@ -0,0 +1,56 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-greenlet +Version: 3.5.5 +Release: 1%{?dist} +Summary: Lightweight in-process concurrent programming +License: MIT AND PSF-2.0 +URL: https://greenlet.readthedocs.io +Source0: https://files.pythonhosted.org/packages/source/g/greenlet/greenlet-3.5.5.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: gcc-c++ + +%description +Lightweight in-process concurrent programming. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n greenlet-3.5.5 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import greenlet" + +%files +%{python3_sitearch}/* +%{_includedir}/python%{py3ver}/greenlet/ + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.5.5-1 +- Package greenlet 3.5.5 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-gssapi/obs/_service b/root/ppg/devel/pgadmin/python3-gssapi/obs/_service new file mode 100644 index 00000000..b8e5216d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gssapi/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/g/gssapi/gssapi-1.11.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-gssapi/package.yaml b/root/ppg/devel/pgadmin/python3-gssapi/package.yaml new file mode 100644 index 00000000..22b287a0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gssapi/package.yaml @@ -0,0 +1,5 @@ +title: gssapi 1.11.1 for Python 3.12 +description: | + Python GSSAPI Wrapper. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-gssapi/rpm/python3-gssapi.spec b/root/ppg/devel/pgadmin/python3-gssapi/rpm/python3-gssapi.spec new file mode 100644 index 00000000..c3ac9614 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gssapi/rpm/python3-gssapi.spec @@ -0,0 +1,59 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-gssapi +Version: 1.11.1 +Release: 1%{?dist} +Summary: Python GSSAPI Wrapper +License: ISC +URL: https://github.com/pythongssapi/python-gssapi +Source0: https://files.pythonhosted.org/packages/source/g/gssapi/gssapi-1.11.1.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-cython +BuildRequires: krb5-devel +BuildRequires: gcc +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-decorator + +Requires: %{python3_pkgprefix}-decorator + +%description +Python GSSAPI Wrapper. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n gssapi-1.11.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import gssapi" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.11.1-1 +- Package gssapi 1.11.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-gunicorn/obs/_service b/root/ppg/devel/pgadmin/python3-gunicorn/obs/_service new file mode 100644 index 00000000..ef79b9db --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gunicorn/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/g/gunicorn/gunicorn-26.2.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-gunicorn/package.yaml b/root/ppg/devel/pgadmin/python3-gunicorn/package.yaml new file mode 100644 index 00000000..7198ef88 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gunicorn/package.yaml @@ -0,0 +1,5 @@ +title: gunicorn 26.2.0 for Python 3.12 +description: | + WSGI HTTP Server for UNIX. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-gunicorn/rpm/python3-gunicorn.spec b/root/ppg/devel/pgadmin/python3-gunicorn/rpm/python3-gunicorn.spec new file mode 100644 index 00000000..0a0d8903 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-gunicorn/rpm/python3-gunicorn.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-gunicorn +Version: 26.2.0 +Release: 1%{?dist} +Summary: WSGI HTTP Server for UNIX +License: MIT +URL: https://gunicorn.org +Source0: https://files.pythonhosted.org/packages/source/g/gunicorn/gunicorn-26.2.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +WSGI HTTP Server for UNIX. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n gunicorn-26.2.0 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import gunicorn" + +%files +%{python3_sitelib}/* +%{_bindir}/gunicorn +%{_bindir}/gunicornc + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 26.2.0-1 +- Package gunicorn 26.2.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-h11/obs/_service b/root/ppg/devel/pgadmin/python3-h11/obs/_service new file mode 100644 index 00000000..e2ad0458 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-h11/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/h/h11/h11-0.16.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-h11/package.yaml b/root/ppg/devel/pgadmin/python3-h11/package.yaml new file mode 100644 index 00000000..6e69c4e5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-h11/package.yaml @@ -0,0 +1,5 @@ +title: h11 0.16.0 for Python 3.12 +description: | + A pure-Python, bring-your-own-I/O implementation of HTTP/1.1. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-h11/rpm/python3-h11.spec b/root/ppg/devel/pgadmin/python3-h11/rpm/python3-h11.spec new file mode 100644 index 00000000..98abd2cd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-h11/rpm/python3-h11.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-h11 +Version: 0.16.0 +Release: 1%{?dist} +Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 +License: MIT +URL: https://github.com/python-hyper/h11 +Source0: https://files.pythonhosted.org/packages/source/h/h11/h11-0.16.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +A pure-Python, bring-your-own-I/O implementation of HTTP/1.1. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n h11-0.16.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import h11" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.16.0-1 +- Package h11 0.16.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-itsdangerous/obs/_service b/root/ppg/devel/pgadmin/python3-itsdangerous/obs/_service new file mode 100644 index 00000000..f2198f71 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-itsdangerous/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/i/itsdangerous/itsdangerous-2.2.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-itsdangerous/package.yaml b/root/ppg/devel/pgadmin/python3-itsdangerous/package.yaml new file mode 100644 index 00000000..6018af10 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-itsdangerous/package.yaml @@ -0,0 +1,5 @@ +title: itsdangerous 2.2.0 for Python 3.12 +description: | + Safely pass data to untrusted environments and back. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-itsdangerous/rpm/python3-itsdangerous.spec b/root/ppg/devel/pgadmin/python3-itsdangerous/rpm/python3-itsdangerous.spec new file mode 100644 index 00000000..1d103d41 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-itsdangerous/rpm/python3-itsdangerous.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-itsdangerous +Version: 2.2.0 +Release: 1%{?dist} +Summary: Safely pass data to untrusted environments and back +License: BSD-3-Clause +URL: https://github.com/pallets/itsdangerous/ +Source0: https://files.pythonhosted.org/packages/source/i/itsdangerous/itsdangerous-2.2.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Safely pass data to untrusted environments and back. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n itsdangerous-2.2.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import itsdangerous" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.2.0-1 +- Package itsdangerous 2.2.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jaraco-classes/obs/_service b/root/ppg/devel/pgadmin/python3-jaraco-classes/obs/_service new file mode 100644 index 00000000..b06ff219 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-classes/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/j/jaraco.classes/jaraco.classes-3.4.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jaraco-classes/package.yaml b/root/ppg/devel/pgadmin/python3-jaraco-classes/package.yaml new file mode 100644 index 00000000..da61275d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-classes/package.yaml @@ -0,0 +1,5 @@ +title: jaraco.classes 3.4.0 for Python 3.12 +description: | + Utility functions for Python class constructs. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jaraco-classes/rpm/python3-jaraco-classes.spec b/root/ppg/devel/pgadmin/python3-jaraco-classes/rpm/python3-jaraco-classes.spec new file mode 100644 index 00000000..6016503a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-classes/rpm/python3-jaraco-classes.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jaraco-classes +Version: 3.4.0 +Release: 1%{?dist} +Summary: Utility functions for Python class constructs +License: MIT +URL: https://github.com/jaraco/jaraco.classes +Source0: https://files.pythonhosted.org/packages/source/j/jaraco.classes/jaraco.classes-3.4.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools_scm +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-more-itertools + +Requires: %{python3_pkgprefix}-more-itertools + +%description +Utility functions for Python class constructs. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jaraco.classes-3.4.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jaraco.classes" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.4.0-1 +- Package jaraco.classes 3.4.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jaraco-context/obs/_service b/root/ppg/devel/pgadmin/python3-jaraco-context/obs/_service new file mode 100644 index 00000000..68747dc2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-context/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/j/jaraco.context/jaraco_context-6.0.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jaraco-context/package.yaml b/root/ppg/devel/pgadmin/python3-jaraco-context/package.yaml new file mode 100644 index 00000000..789118f8 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-context/package.yaml @@ -0,0 +1,5 @@ +title: jaraco.context 6.0.1 for Python 3.12 +description: | + Useful decorators and context managers. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jaraco-context/rpm/python3-jaraco-context.spec b/root/ppg/devel/pgadmin/python3-jaraco-context/rpm/python3-jaraco-context.spec new file mode 100644 index 00000000..c2475b9d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-context/rpm/python3-jaraco-context.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jaraco-context +Version: 6.0.1 +Release: 1%{?dist} +Summary: Useful decorators and context managers +License: MIT +URL: https://github.com/jaraco/jaraco.context +Source0: https://files.pythonhosted.org/packages/source/j/jaraco.context/jaraco_context-6.0.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools_scm + +%description +Useful decorators and context managers. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jaraco_context-6.0.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jaraco.context" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 6.0.1-1 +- Package jaraco.context 6.0.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jaraco-functools/obs/_service b/root/ppg/devel/pgadmin/python3-jaraco-functools/obs/_service new file mode 100644 index 00000000..2a89a5e2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-functools/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/j/jaraco.functools/jaraco_functools-4.1.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jaraco-functools/package.yaml b/root/ppg/devel/pgadmin/python3-jaraco-functools/package.yaml new file mode 100644 index 00000000..efca9f49 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-functools/package.yaml @@ -0,0 +1,5 @@ +title: jaraco.functools 4.1.0 for Python 3.12 +description: | + Functools like those found in stdlib. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jaraco-functools/rpm/python3-jaraco-functools.spec b/root/ppg/devel/pgadmin/python3-jaraco-functools/rpm/python3-jaraco-functools.spec new file mode 100644 index 00000000..1c955ff0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jaraco-functools/rpm/python3-jaraco-functools.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jaraco-functools +Version: 4.1.0 +Release: 1%{?dist} +Summary: Functools like those found in stdlib +License: MIT +URL: https://github.com/jaraco/jaraco.functools +Source0: https://files.pythonhosted.org/packages/source/j/jaraco.functools/jaraco_functools-4.1.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools_scm +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-more-itertools + +Requires: %{python3_pkgprefix}-more-itertools + +%description +Functools like those found in stdlib. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jaraco_functools-4.1.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jaraco.functools" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.1.0-1 +- Package jaraco.functools 4.1.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jeepney/obs/_service b/root/ppg/devel/pgadmin/python3-jeepney/obs/_service new file mode 100644 index 00000000..84b102b1 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jeepney/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/j/jeepney/jeepney-0.9.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jeepney/package.yaml b/root/ppg/devel/pgadmin/python3-jeepney/package.yaml new file mode 100644 index 00000000..aeaf61b9 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jeepney/package.yaml @@ -0,0 +1,5 @@ +title: jeepney 0.9.0 for Python 3.12 +description: | + Low-level, pure Python DBus protocol wrapper. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jeepney/rpm/python3-jeepney.spec b/root/ppg/devel/pgadmin/python3-jeepney/rpm/python3-jeepney.spec new file mode 100644 index 00000000..cae0a6ec --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jeepney/rpm/python3-jeepney.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jeepney +Version: 0.9.0 +Release: 1%{?dist} +Summary: Low-level, pure Python DBus protocol wrapper +License: MIT +URL: https://gitlab.com/takluyver/jeepney +Source0: https://files.pythonhosted.org/packages/source/j/jeepney/jeepney-0.9.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Low-level, pure Python DBus protocol wrapper. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jeepney-0.9.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jeepney" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.9.0-1 +- Package jeepney 0.9.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jinja2/obs/_service b/root/ppg/devel/pgadmin/python3-jinja2/obs/_service new file mode 100644 index 00000000..06090a58 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jinja2/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/J/Jinja2/jinja2-3.1.6.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jinja2/package.yaml b/root/ppg/devel/pgadmin/python3-jinja2/package.yaml new file mode 100644 index 00000000..530a8fca --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jinja2/package.yaml @@ -0,0 +1,5 @@ +title: Jinja2 3.1.6 for Python 3.12 +description: | + A very fast and expressive template engine. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jinja2/rpm/python3-jinja2.spec b/root/ppg/devel/pgadmin/python3-jinja2/rpm/python3-jinja2.spec new file mode 100644 index 00000000..b875b556 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jinja2/rpm/python3-jinja2.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jinja2 +Version: 3.1.6 +Release: 1%{?dist} +Summary: A very fast and expressive template engine +License: BSD-3-Clause +URL: https://github.com/pallets/jinja/ +Source0: https://files.pythonhosted.org/packages/source/J/Jinja2/jinja2-3.1.6.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-markupsafe >= 2.0 + +Requires: %{python3_pkgprefix}-markupsafe >= 2.0 + +%description +A very fast and expressive template engine. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jinja2-3.1.6 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jinja2" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.1.6-1 +- Package Jinja2 3.1.6 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-jsonformatter/obs/_service b/root/ppg/devel/pgadmin/python3-jsonformatter/obs/_service new file mode 100644 index 00000000..1004a067 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jsonformatter/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/j/jsonformatter/jsonformatter-0.3.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-jsonformatter/package.yaml b/root/ppg/devel/pgadmin/python3-jsonformatter/package.yaml new file mode 100644 index 00000000..d9a29c91 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jsonformatter/package.yaml @@ -0,0 +1,5 @@ +title: jsonformatter 0.3.4 for Python 3.12 +description: | + Python log in json format. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-jsonformatter/rpm/python3-jsonformatter.spec b/root/ppg/devel/pgadmin/python3-jsonformatter/rpm/python3-jsonformatter.spec new file mode 100644 index 00000000..83461cf4 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-jsonformatter/rpm/python3-jsonformatter.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-jsonformatter +Version: 0.3.4 +Release: 1%{?dist} +Summary: Python log in json format +License: MIT +URL: https://github.com/MyColorfulDays/jsonformatter.git +Source0: https://files.pythonhosted.org/packages/source/j/jsonformatter/jsonformatter-0.3.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Python log in json format. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n jsonformatter-0.3.4 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import jsonformatter" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.3.4-1 +- Package jsonformatter 0.3.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-keyring/obs/_service b/root/ppg/devel/pgadmin/python3-keyring/obs/_service new file mode 100644 index 00000000..aca7195f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-keyring/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/k/keyring/keyring-25.2.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-keyring/package.yaml b/root/ppg/devel/pgadmin/python3-keyring/package.yaml new file mode 100644 index 00000000..88a03b51 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-keyring/package.yaml @@ -0,0 +1,5 @@ +title: keyring 25.2.1 for Python 3.12 +description: | + Store and access your passwords safely. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-keyring/rpm/python3-keyring.spec b/root/ppg/devel/pgadmin/python3-keyring/rpm/python3-keyring.spec new file mode 100644 index 00000000..33b865ab --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-keyring/rpm/python3-keyring.spec @@ -0,0 +1,68 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-keyring +Version: 25.2.1 +Release: 1%{?dist} +Summary: Store and access your passwords safely +License: MIT +URL: https://github.com/jaraco/keyring +Source0: https://files.pythonhosted.org/packages/source/k/keyring/keyring-25.2.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-setuptools_scm +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-jaraco-classes +BuildRequires: %{python3_pkgprefix}-jaraco-functools +BuildRequires: %{python3_pkgprefix}-jaraco-context +BuildRequires: %{python3_pkgprefix}-secretstorage >= 3.2 +BuildRequires: %{python3_pkgprefix}-jeepney >= 0.4.2 + +Requires: %{python3_pkgprefix}-jaraco-classes +Requires: %{python3_pkgprefix}-jaraco-functools +Requires: %{python3_pkgprefix}-jaraco-context +Requires: %{python3_pkgprefix}-secretstorage >= 3.2 +Requires: %{python3_pkgprefix}-jeepney >= 0.4.2 + +%description +Store and access your passwords safely. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n keyring-25.2.1 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import keyring" + +%files +%{python3_sitelib}/* +%{_bindir}/keyring + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 25.2.1-1 +- Package keyring 25.2.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-ldap3/obs/_service b/root/ppg/devel/pgadmin/python3-ldap3/obs/_service new file mode 100644 index 00000000..4e1aa883 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ldap3/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/l/ldap3/ldap3-2.9.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-ldap3/package.yaml b/root/ppg/devel/pgadmin/python3-ldap3/package.yaml new file mode 100644 index 00000000..c7267140 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ldap3/package.yaml @@ -0,0 +1,5 @@ +title: ldap3 2.9.1 for Python 3.12 +description: | + A strictly RFC 4510 conforming LDAP V3 pure Python client library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-ldap3/rpm/python3-ldap3.spec b/root/ppg/devel/pgadmin/python3-ldap3/rpm/python3-ldap3.spec new file mode 100644 index 00000000..d91d4d17 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ldap3/rpm/python3-ldap3.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-ldap3 +Version: 2.9.1 +Release: 1%{?dist} +Summary: A strictly RFC 4510 conforming LDAP V3 pure Python client library +License: LGPL-3.0-only +URL: https://github.com/cannatag/ldap3 +Source0: https://files.pythonhosted.org/packages/source/l/ldap3/ldap3-2.9.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-pyasn1 + +Requires: %{python3_pkgprefix}-pyasn1 + +%description +A strictly RFC 4510 conforming LDAP V3 pure Python client library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n ldap3-2.9.1 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import ldap3" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.9.1-1 +- Package ldap3 2.9.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-libgravatar/obs/_service b/root/ppg/devel/pgadmin/python3-libgravatar/obs/_service new file mode 100644 index 00000000..76bc9ca2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libgravatar/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/l/libgravatar/libgravatar-1.0.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-libgravatar/package.yaml b/root/ppg/devel/pgadmin/python3-libgravatar/package.yaml new file mode 100644 index 00000000..86e873bd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libgravatar/package.yaml @@ -0,0 +1,5 @@ +title: libgravatar 1.0.4 for Python 3.12 +description: | + A library that provides a Python 3 interface for the Gravatar API. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-libgravatar/rpm/python3-libgravatar.spec b/root/ppg/devel/pgadmin/python3-libgravatar/rpm/python3-libgravatar.spec new file mode 100644 index 00000000..5b1d04fd --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libgravatar/rpm/python3-libgravatar.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-libgravatar +Version: 1.0.4 +Release: 1%{?dist} +Summary: A library that provides a Python 3 interface for the Gravatar API +License: MIT +URL: https://github.com/pabluk/libgravatar +Source0: https://files.pythonhosted.org/packages/source/l/libgravatar/libgravatar-1.0.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +A library that provides a Python 3 interface for the Gravatar API. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n libgravatar-1.0.4 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import libgravatar" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.0.4-1 +- Package libgravatar 1.0.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-libpass/obs/_service b/root/ppg/devel/pgadmin/python3-libpass/obs/_service new file mode 100644 index 00000000..f5c9302c --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libpass/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/l/libpass/libpass-1.9.3.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-libpass/package.yaml b/root/ppg/devel/pgadmin/python3-libpass/package.yaml new file mode 100644 index 00000000..abfe4c11 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libpass/package.yaml @@ -0,0 +1,5 @@ +title: libpass 1.9.3 for Python 3.12 +description: | + Fork of passlib, a comprehensive password hashing framework supporting over 30 schemes. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-libpass/rpm/python3-libpass.spec b/root/ppg/devel/pgadmin/python3-libpass/rpm/python3-libpass.spec new file mode 100644 index 00000000..72f493d2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-libpass/rpm/python3-libpass.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-libpass +Version: 1.9.3 +Release: 1%{?dist} +Summary: Fork of passlib, a comprehensive password hashing framework supporting over 30 schemes +License: BSD +URL: https://github.com/notypecheck/passlib +Source0: https://files.pythonhosted.org/packages/source/l/libpass/libpass-1.9.3.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif +Provides: %{python3_pkgprefix}-passlib = %{version} +Conflicts: %{python3_pkgprefix}-passlib < 1.9 + +%description +Fork of passlib, a comprehensive password hashing framework supporting over 30 schemes. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n libpass-1.9.3 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import passlib" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.9.3-1 +- Package libpass 1.9.3 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-mako/obs/_service b/root/ppg/devel/pgadmin/python3-mako/obs/_service new file mode 100644 index 00000000..0b7636c6 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mako/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/M/Mako/mako-1.4.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-mako/package.yaml b/root/ppg/devel/pgadmin/python3-mako/package.yaml new file mode 100644 index 00000000..35b921b3 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mako/package.yaml @@ -0,0 +1,5 @@ +title: Mako 1.4.1 for Python 3.12 +description: | + A super-fast templating language that borrows the best ideas from the existing templating languages. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-mako/rpm/python3-mako.spec b/root/ppg/devel/pgadmin/python3-mako/rpm/python3-mako.spec new file mode 100644 index 00000000..06df169b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mako/rpm/python3-mako.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-mako +Version: 1.4.1 +Release: 1%{?dist} +Summary: A super-fast templating language that borrows the best ideas from the existing templating languages +License: MIT +URL: https://www.makotemplates.org/ +Source0: https://files.pythonhosted.org/packages/source/M/Mako/mako-1.4.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-markupsafe >= 2.0 + +Requires: %{python3_pkgprefix}-markupsafe >= 2.0 + +%description +A super-fast templating language that borrows the best ideas from the existing templating languages. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n mako-1.4.1 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import mako" + +%files +%{python3_sitelib}/* +%{_bindir}/mako-render + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.4.1-1 +- Package Mako 1.4.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-markdown-it-py/obs/_service b/root/ppg/devel/pgadmin/python3-markdown-it-py/obs/_service new file mode 100644 index 00000000..d9cc0f2e --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markdown-it-py/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/m/markdown-it-py/markdown_it_py-4.2.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-markdown-it-py/package.yaml b/root/ppg/devel/pgadmin/python3-markdown-it-py/package.yaml new file mode 100644 index 00000000..930ce746 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markdown-it-py/package.yaml @@ -0,0 +1,5 @@ +title: markdown-it-py 4.2.0 for Python 3.12 +description: | + Python port of markdown-it. Markdown parsing, done right!. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-markdown-it-py/rpm/python3-markdown-it-py.spec b/root/ppg/devel/pgadmin/python3-markdown-it-py/rpm/python3-markdown-it-py.spec new file mode 100644 index 00000000..d0bc148a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markdown-it-py/rpm/python3-markdown-it-py.spec @@ -0,0 +1,60 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-markdown-it-py +Version: 4.2.0 +Release: 1%{?dist} +Summary: Python port of markdown-it. Markdown parsing, done right! +License: MIT +URL: https://github.com/executablebooks/markdown-it-py +Source0: https://files.pythonhosted.org/packages/source/m/markdown-it-py/markdown_it_py-4.2.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-mdurl >= 0.1 + +Requires: %{python3_pkgprefix}-mdurl >= 0.1 + +%description +Python port of markdown-it. Markdown parsing, done right!. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n markdown_it_py-4.2.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import markdown_it" + +%files +%{python3_sitelib}/* +%{_bindir}/markdown-it + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.2.0-1 +- Package markdown-it-py 4.2.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-markupsafe/obs/_service b/root/ppg/devel/pgadmin/python3-markupsafe/obs/_service new file mode 100644 index 00000000..415574bf --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markupsafe/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/M/MarkupSafe/markupsafe-3.0.3.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-markupsafe/package.yaml b/root/ppg/devel/pgadmin/python3-markupsafe/package.yaml new file mode 100644 index 00000000..5aabd029 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markupsafe/package.yaml @@ -0,0 +1,5 @@ +title: MarkupSafe 3.0.3 for Python 3.12 +description: | + Safely add untrusted strings to HTML/XML markup. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-markupsafe/rpm/python3-markupsafe.spec b/root/ppg/devel/pgadmin/python3-markupsafe/rpm/python3-markupsafe.spec new file mode 100644 index 00000000..a0ee7521 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-markupsafe/rpm/python3-markupsafe.spec @@ -0,0 +1,55 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-markupsafe +Version: 3.0.3 +Release: 1%{?dist} +Summary: Safely add untrusted strings to HTML/XML markup +License: BSD-3-Clause +URL: https://github.com/pallets/markupsafe/ +Source0: https://files.pythonhosted.org/packages/source/M/MarkupSafe/markupsafe-3.0.3.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: gcc + +%description +Safely add untrusted strings to HTML/XML markup. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n markupsafe-3.0.3 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import markupsafe" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.0.3-1 +- Package MarkupSafe 3.0.3 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-mdurl/obs/_service b/root/ppg/devel/pgadmin/python3-mdurl/obs/_service new file mode 100644 index 00000000..15125170 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mdurl/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/m/mdurl/mdurl-0.1.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-mdurl/package.yaml b/root/ppg/devel/pgadmin/python3-mdurl/package.yaml new file mode 100644 index 00000000..34cb8564 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mdurl/package.yaml @@ -0,0 +1,5 @@ +title: mdurl 0.1.2 for Python 3.12 +description: | + Markdown URL utilities. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-mdurl/rpm/python3-mdurl.spec b/root/ppg/devel/pgadmin/python3-mdurl/rpm/python3-mdurl.spec new file mode 100644 index 00000000..3c18a991 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-mdurl/rpm/python3-mdurl.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-mdurl +Version: 0.1.2 +Release: 1%{?dist} +Summary: Markdown URL utilities +License: MIT +URL: https://github.com/executablebooks/mdurl +Source0: https://files.pythonhosted.org/packages/source/m/mdurl/mdurl-0.1.2.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Markdown URL utilities. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n mdurl-0.1.2 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import mdurl" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.1.2-1 +- Package mdurl 0.1.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-more-itertools/obs/_service b/root/ppg/devel/pgadmin/python3-more-itertools/obs/_service new file mode 100644 index 00000000..9f8242c6 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-more-itertools/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/m/more-itertools/more_itertools-11.1.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-more-itertools/package.yaml b/root/ppg/devel/pgadmin/python3-more-itertools/package.yaml new file mode 100644 index 00000000..2f385baf --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-more-itertools/package.yaml @@ -0,0 +1,5 @@ +title: more-itertools 11.1.0 for Python 3.12 +description: | + More routines for operating on iterables, beyond itertools. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-more-itertools/rpm/python3-more-itertools.spec b/root/ppg/devel/pgadmin/python3-more-itertools/rpm/python3-more-itertools.spec new file mode 100644 index 00000000..c1847494 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-more-itertools/rpm/python3-more-itertools.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-more-itertools +Version: 11.1.0 +Release: 1%{?dist} +Summary: More routines for operating on iterables, beyond itertools +License: MIT +URL: https://github.com/more-itertools/more-itertools +Source0: https://files.pythonhosted.org/packages/source/m/more-itertools/more_itertools-11.1.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +More routines for operating on iterables, beyond itertools. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n more_itertools-11.1.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import more_itertools" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 11.1.0-1 +- Package more-itertools 11.1.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-paramiko/obs/_service b/root/ppg/devel/pgadmin/python3-paramiko/obs/_service new file mode 100644 index 00000000..1432b27c --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-paramiko/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/paramiko/paramiko-3.5.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-paramiko/package.yaml b/root/ppg/devel/pgadmin/python3-paramiko/package.yaml new file mode 100644 index 00000000..e7746c16 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-paramiko/package.yaml @@ -0,0 +1,5 @@ +title: paramiko 3.5.1 for Python 3.12 +description: | + SSH2 protocol library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-paramiko/rpm/python3-paramiko.spec b/root/ppg/devel/pgadmin/python3-paramiko/rpm/python3-paramiko.spec new file mode 100644 index 00000000..cffc620b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-paramiko/rpm/python3-paramiko.spec @@ -0,0 +1,63 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-paramiko +Version: 3.5.1 +Release: 1%{?dist} +Summary: SSH2 protocol library +License: LGPL-2.1-or-later +URL: https://paramiko.org +Source0: https://files.pythonhosted.org/packages/source/p/paramiko/paramiko-3.5.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-bcrypt >= 3.2 +BuildRequires: python3.12-cryptography +BuildRequires: %{python3_pkgprefix}-pynacl >= 1.5 + +Requires: %{python3_pkgprefix}-bcrypt >= 3.2 +Requires: python3.12-cryptography +Requires: %{python3_pkgprefix}-pynacl >= 1.5 + +%description +SSH2 protocol library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n paramiko-3.5.1 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import paramiko" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.5.1-1 +- Package paramiko 3.5.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pdm-backend/obs/_service b/root/ppg/devel/pgadmin/python3-pdm-backend/obs/_service new file mode 100644 index 00000000..22490002 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pdm-backend/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/pdm-backend/pdm_backend-2.4.5.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pdm-backend/package.yaml b/root/ppg/devel/pgadmin/python3-pdm-backend/package.yaml new file mode 100644 index 00000000..e825a166 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pdm-backend/package.yaml @@ -0,0 +1,5 @@ +title: pdm-backend 2.4.5 for Python 3.12 +description: | + The build backend used by PDM that supports latest packaging standards. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pdm-backend/rpm/python3-pdm-backend.spec b/root/ppg/devel/pgadmin/python3-pdm-backend/rpm/python3-pdm-backend.spec new file mode 100644 index 00000000..bd41bd1b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pdm-backend/rpm/python3-pdm-backend.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pdm-backend +Version: 2.4.5 +Release: 1%{?dist} +Summary: The build backend used by PDM that supports latest packaging standards +License: MIT +URL: https://github.com/pdm-project/pdm-backend +Source0: https://files.pythonhosted.org/packages/source/p/pdm-backend/pdm_backend-2.4.5.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +The build backend used by PDM that supports latest packaging standards. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pdm_backend-2.4.5 + +%build +export PYTHONPATH=$PWD/src +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pdm.backend" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.4.5-1 +- Package pdm-backend 2.4.5 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pillow/obs/_service b/root/ppg/devel/pgadmin/python3-pillow/obs/_service new file mode 100644 index 00000000..298ba863 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pillow/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/pillow/pillow-11.1.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pillow/package.yaml b/root/ppg/devel/pgadmin/python3-pillow/package.yaml new file mode 100644 index 00000000..523af4ce --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pillow/package.yaml @@ -0,0 +1,5 @@ +title: pillow 11.1.0 for Python 3.12 +description: | + Python Imaging Library (Fork). RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pillow/rpm/python3-pillow.spec b/root/ppg/devel/pgadmin/python3-pillow/rpm/python3-pillow.spec new file mode 100644 index 00000000..af81fbf9 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pillow/rpm/python3-pillow.spec @@ -0,0 +1,55 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pillow +Version: 11.1.0 +Release: 1%{?dist} +Summary: Python Imaging Library (Fork) +License: MIT-CMU +URL: https://python-pillow.github.io +Source0: https://files.pythonhosted.org/packages/source/p/pillow/pillow-11.1.0.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: libjpeg-turbo-devel +BuildRequires: zlib-devel +BuildRequires: gcc + +%description +Python Imaging Library (Fork). + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pillow-11.1.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index -C platform-guessing=disable -C zlib=enable -C jpeg=enable -C tiff=disable -C freetype=disable -C raqm=disable -C lcms=disable -C webp=disable -C xcb=disable -C jpeg2000=disable -C imagequant=disable -C avif=disable --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import PIL" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 11.1.0-1 +- Package pillow 11.1.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-poetry-core/obs/_service b/root/ppg/devel/pgadmin/python3-poetry-core/obs/_service new file mode 100644 index 00000000..2fb26f27 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-poetry-core/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/poetry-core/poetry_core-2.2.1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-poetry-core/package.yaml b/root/ppg/devel/pgadmin/python3-poetry-core/package.yaml new file mode 100644 index 00000000..1f115c64 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-poetry-core/package.yaml @@ -0,0 +1,5 @@ +title: poetry-core 2.2.1 for Python 3.12 +description: | + Poetry PEP 517 Build Backend. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-poetry-core/rpm/python3-poetry-core.spec b/root/ppg/devel/pgadmin/python3-poetry-core/rpm/python3-poetry-core.spec new file mode 100644 index 00000000..9648800a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-poetry-core/rpm/python3-poetry-core.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-poetry-core +Version: 2.2.1 +Release: 1%{?dist} +Summary: Poetry PEP 517 Build Backend +License: MIT +URL: https://github.com/python-poetry/poetry-core +Source0: https://files.pythonhosted.org/packages/source/p/poetry-core/poetry_core-2.2.1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Poetry PEP 517 Build Backend. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n poetry_core-2.2.1 + +%build +export PYTHONPATH=$PWD/src +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import poetry.core.masonry.api" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.2.1-1 +- Package poetry-core 2.2.1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-psutil/obs/_aggregate b/root/ppg/devel/pgadmin/python3-psutil/obs/_aggregate new file mode 100644 index 00000000..3a04a29f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psutil/obs/_aggregate @@ -0,0 +1,5 @@ +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-psutil</package> + </aggregate> +</aggregatelist> diff --git a/root/ppg/devel/pgadmin/python3-psycopg-c/obs/_service b/root/ppg/devel/pgadmin/python3-psycopg-c/obs/_service new file mode 100644 index 00000000..1c1ab5fb --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg-c/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/psycopg-c/psycopg_c-3.3.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-psycopg-c/package.yaml b/root/ppg/devel/pgadmin/python3-psycopg-c/package.yaml new file mode 100644 index 00000000..50845e1c --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg-c/package.yaml @@ -0,0 +1,5 @@ +title: psycopg-c 3.3.4 for Python 3.12 +description: | + PostgreSQL database adapter for Python -- C optimisation distribution. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-psycopg-c/rpm/python3-psycopg-c.spec b/root/ppg/devel/pgadmin/python3-psycopg-c/rpm/python3-psycopg-c.spec new file mode 100644 index 00000000..764b9608 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg-c/rpm/python3-psycopg-c.spec @@ -0,0 +1,58 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-psycopg-c +Version: 3.3.4 +Release: 1%{?dist} +Summary: PostgreSQL database adapter for Python -- C optimisation distribution +License: LGPL-3.0-only +URL: https://psycopg.org/ +Source0: https://files.pythonhosted.org/packages/source/p/psycopg-c/psycopg_c-3.3.4.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: libpq-devel +BuildRequires: gcc +# for the %check import +BuildRequires: %{python3_pkgprefix}-psycopg + +%description +PostgreSQL database adapter for Python -- C optimisation distribution. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n psycopg_c-3.3.4 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import psycopg; import psycopg_c" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.3.4-1 +- Package psycopg-c 3.3.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-psycopg/obs/_service b/root/ppg/devel/pgadmin/python3-psycopg/obs/_service new file mode 100644 index 00000000..f976b1df --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/psycopg/psycopg-3.3.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-psycopg/package.yaml b/root/ppg/devel/pgadmin/python3-psycopg/package.yaml new file mode 100644 index 00000000..fc497a6f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg/package.yaml @@ -0,0 +1,5 @@ +title: psycopg 3.3.4 for Python 3.12 +description: | + PostgreSQL database adapter for Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-psycopg/rpm/python3-psycopg.spec b/root/ppg/devel/pgadmin/python3-psycopg/rpm/python3-psycopg.spec new file mode 100644 index 00000000..b49d45b7 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-psycopg/rpm/python3-psycopg.spec @@ -0,0 +1,64 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-psycopg +Version: 3.3.4 +Release: 1%{?dist} +Summary: PostgreSQL database adapter for Python +License: LGPL-3.0-only +URL: https://psycopg.org/ +Source0: https://files.pythonhosted.org/packages/source/p/psycopg/psycopg-3.3.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-typing-extensions >= 4.6 +BuildRequires: libpq + +Requires: %{python3_pkgprefix}-typing-extensions >= 4.6 +Requires: libpq +# not mirrored as BuildRequires: the dependency BuildRequires this package for its %check +Requires: %{python3_pkgprefix}-psycopg-c + +%description +PostgreSQL database adapter for Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n psycopg-3.3.4 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import psycopg" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.3.4-1 +- Package psycopg 3.3.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pyasn1/obs/_service b/root/ppg/devel/pgadmin/python3-pyasn1/obs/_service new file mode 100644 index 00000000..3df937d8 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyasn1/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/pyasn1/pyasn1-0.6.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pyasn1/package.yaml b/root/ppg/devel/pgadmin/python3-pyasn1/package.yaml new file mode 100644 index 00000000..27b40303 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyasn1/package.yaml @@ -0,0 +1,5 @@ +title: pyasn1 0.6.4 for Python 3.12 +description: | + Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208). RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pyasn1/rpm/python3-pyasn1.spec b/root/ppg/devel/pgadmin/python3-pyasn1/rpm/python3-pyasn1.spec new file mode 100644 index 00000000..847b409b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyasn1/rpm/python3-pyasn1.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pyasn1 +Version: 0.6.4 +Release: 1%{?dist} +Summary: Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208) +License: BSD-2-Clause +URL: https://github.com/pyasn1/pyasn1 +Source0: https://files.pythonhosted.org/packages/source/p/pyasn1/pyasn1-0.6.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208). + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pyasn1-0.6.4 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pyasn1" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.6.4-1 +- Package pyasn1 0.6.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pygments/obs/_service b/root/ppg/devel/pgadmin/python3-pygments/obs/_service new file mode 100644 index 00000000..5ce00d53 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pygments/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/P/Pygments/pygments-2.21.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pygments/package.yaml b/root/ppg/devel/pgadmin/python3-pygments/package.yaml new file mode 100644 index 00000000..16773803 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pygments/package.yaml @@ -0,0 +1,5 @@ +title: Pygments 2.21.0 for Python 3.12 +description: | + Pygments is a syntax highlighting package written in Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pygments/rpm/python3-pygments.spec b/root/ppg/devel/pgadmin/python3-pygments/rpm/python3-pygments.spec new file mode 100644 index 00000000..71ad4bb5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pygments/rpm/python3-pygments.spec @@ -0,0 +1,60 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pygments +Version: 2.21.0 +Release: 1%{?dist} +Summary: Pygments is a syntax highlighting package written in Python +License: BSD-2-Clause +URL: https://pygments.org +Source0: https://files.pythonhosted.org/packages/source/P/Pygments/pygments-2.21.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif + +%description +Pygments is a syntax highlighting package written in Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pygments-2.21.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pygments" + +%files +%{python3_sitelib}/* +%{_bindir}/pygmentize + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.21.0-1 +- Package Pygments 2.21.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pynacl/obs/_service b/root/ppg/devel/pgadmin/python3-pynacl/obs/_service new file mode 100644 index 00000000..89a8e489 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pynacl/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/P/PyNaCl/pynacl-1.6.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pynacl/package.yaml b/root/ppg/devel/pgadmin/python3-pynacl/package.yaml new file mode 100644 index 00000000..a0dc1a22 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pynacl/package.yaml @@ -0,0 +1,5 @@ +title: PyNaCl 1.6.2 for Python 3.12 +description: | + Python binding to the Networking and Cryptography (NaCl) library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pynacl/rpm/python3-pynacl.spec b/root/ppg/devel/pgadmin/python3-pynacl/rpm/python3-pynacl.spec new file mode 100644 index 00000000..2d2be256 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pynacl/rpm/python3-pynacl.spec @@ -0,0 +1,59 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pynacl +Version: 1.6.2 +Release: 1%{?dist} +Summary: Python binding to the Networking and Cryptography (NaCl) library +License: Apache-2.0 +URL: https://github.com/pyca/pynacl +Source0: https://files.pythonhosted.org/packages/source/P/PyNaCl/pynacl-1.6.2.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: libffi-devel +BuildRequires: make +BuildRequires: gcc +# runtime dependencies, also needed by the %check import test +BuildRequires: python3.12-cffi + +Requires: python3.12-cffi + +%description +Python binding to the Networking and Cryptography (NaCl) library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pynacl-1.6.2 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import nacl" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.6.2-1 +- Package PyNaCl 1.6.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pyotp/obs/_service b/root/ppg/devel/pgadmin/python3-pyotp/obs/_service new file mode 100644 index 00000000..3ddaf1f4 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyotp/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/P/PyOTP/pyotp-2.9.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pyotp/package.yaml b/root/ppg/devel/pgadmin/python3-pyotp/package.yaml new file mode 100644 index 00000000..a293a358 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyotp/package.yaml @@ -0,0 +1,5 @@ +title: PyOTP 2.9.0 for Python 3.12 +description: | + Python One Time Password Library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pyotp/rpm/python3-pyotp.spec b/root/ppg/devel/pgadmin/python3-pyotp/rpm/python3-pyotp.spec new file mode 100644 index 00000000..d4ee7452 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pyotp/rpm/python3-pyotp.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pyotp +Version: 2.9.0 +Release: 1%{?dist} +Summary: Python One Time Password Library +License: MIT +URL: https://github.com/pyotp/pyotp +Source0: https://files.pythonhosted.org/packages/source/P/PyOTP/pyotp-2.9.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Python One Time Password Library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pyotp-2.9.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pyotp" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.9.0-1 +- Package PyOTP 2.9.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-python-engineio/obs/_service b/root/ppg/devel/pgadmin/python3-python-engineio/obs/_service new file mode 100644 index 00000000..b2a4d2b3 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-engineio/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/python-engineio/python_engineio-4.13.5.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-python-engineio/package.yaml b/root/ppg/devel/pgadmin/python3-python-engineio/package.yaml new file mode 100644 index 00000000..5f39c2f7 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-engineio/package.yaml @@ -0,0 +1,5 @@ +title: python-engineio 4.13.5 for Python 3.12 +description: | + Engine.IO server and client for Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-python-engineio/rpm/python3-python-engineio.spec b/root/ppg/devel/pgadmin/python3-python-engineio/rpm/python3-python-engineio.spec new file mode 100644 index 00000000..af903b0d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-engineio/rpm/python3-python-engineio.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-python-engineio +Version: 4.13.5 +Release: 1%{?dist} +Summary: Engine.IO server and client for Python +License: MIT +URL: https://github.com/miguelgrinberg/python-engineio +Source0: https://files.pythonhosted.org/packages/source/p/python-engineio/python_engineio-4.13.5.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-simple-websocket >= 0.10.0 + +Requires: %{python3_pkgprefix}-simple-websocket >= 0.10.0 + +%description +Engine.IO server and client for Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n python_engineio-4.13.5 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import engineio" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.13.5-1 +- Package python-engineio 4.13.5 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-python-socketio/obs/_service b/root/ppg/devel/pgadmin/python3-python-socketio/obs/_service new file mode 100644 index 00000000..36f57624 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-socketio/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/python-socketio/python_socketio-5.16.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-python-socketio/package.yaml b/root/ppg/devel/pgadmin/python3-python-socketio/package.yaml new file mode 100644 index 00000000..554d6f9b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-socketio/package.yaml @@ -0,0 +1,5 @@ +title: python-socketio 5.16.4 for Python 3.12 +description: | + Socket.IO server and client for Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-python-socketio/rpm/python3-python-socketio.spec b/root/ppg/devel/pgadmin/python3-python-socketio/rpm/python3-python-socketio.spec new file mode 100644 index 00000000..9fc47444 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-python-socketio/rpm/python3-python-socketio.spec @@ -0,0 +1,60 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-python-socketio +Version: 5.16.4 +Release: 1%{?dist} +Summary: Socket.IO server and client for Python +License: MIT +URL: https://github.com/miguelgrinberg/python-socketio +Source0: https://files.pythonhosted.org/packages/source/p/python-socketio/python_socketio-5.16.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-bidict >= 0.21.0 +BuildRequires: %{python3_pkgprefix}-python-engineio >= 4.13.2 + +Requires: %{python3_pkgprefix}-bidict >= 0.21.0 +Requires: %{python3_pkgprefix}-python-engineio >= 4.13.2 + +%description +Socket.IO server and client for Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n python_socketio-5.16.4 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import socketio" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 5.16.4-1 +- Package python-socketio 5.16.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-pytz/obs/_service b/root/ppg/devel/pgadmin/python3-pytz/obs/_service new file mode 100644 index 00000000..ef4d42bb --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pytz/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/p/pytz/pytz-2026.3.post1.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-pytz/package.yaml b/root/ppg/devel/pgadmin/python3-pytz/package.yaml new file mode 100644 index 00000000..8ae79b56 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pytz/package.yaml @@ -0,0 +1,5 @@ +title: pytz 2026.3.post1 for Python 3.12 +description: | + World timezone definitions, modern and historical. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-pytz/rpm/python3-pytz.spec b/root/ppg/devel/pgadmin/python3-pytz/rpm/python3-pytz.spec new file mode 100644 index 00000000..a2340a6a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-pytz/rpm/python3-pytz.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-pytz +Version: 2026.3.post1 +Release: 1%{?dist} +Summary: World timezone definitions, modern and historical +License: MIT +URL: http://pythonhosted.org/pytz +Source0: https://files.pythonhosted.org/packages/source/p/pytz/pytz-2026.3.post1.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +World timezone definitions, modern and historical. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n pytz-2026.3.post1 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import pytz" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2026.3.post1-1 +- Package pytz 2026.3.post1 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-qrcode/obs/_service b/root/ppg/devel/pgadmin/python3-qrcode/obs/_service new file mode 100644 index 00000000..63b11f53 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-qrcode/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/q/qrcode/qrcode-8.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-qrcode/package.yaml b/root/ppg/devel/pgadmin/python3-qrcode/package.yaml new file mode 100644 index 00000000..ab2a9663 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-qrcode/package.yaml @@ -0,0 +1,5 @@ +title: qrcode 8.2 for Python 3.12 +description: | + QR Code image generator. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-qrcode/rpm/python3-qrcode.spec b/root/ppg/devel/pgadmin/python3-qrcode/rpm/python3-qrcode.spec new file mode 100644 index 00000000..71e412c5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-qrcode/rpm/python3-qrcode.spec @@ -0,0 +1,62 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-qrcode +Version: 8.2 +Release: 1%{?dist} +Summary: QR Code image generator +License: BSD-3-Clause +URL: https://github.com/lincolnloop/python-qrcode +Source0: https://files.pythonhosted.org/packages/source/q/qrcode/qrcode-8.2.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-poetry-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-pillow + +Requires: %{python3_pkgprefix}-pillow + +%description +QR Code image generator. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n qrcode-8.2 +# console_scripts.py is a module, not a script: drop its ambiguous #!/usr/bin/env python shebang +sed -i '1{/^#!\/usr\/bin\/env python$/d}' qrcode/console_scripts.py + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import qrcode" + +%files +%{python3_sitelib}/* +%{_bindir}/qr + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 8.2-1 +- Package qrcode 8.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-rich/obs/_service b/root/ppg/devel/pgadmin/python3-rich/obs/_service new file mode 100644 index 00000000..24bf6866 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-rich/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/r/rich/rich-15.0.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-rich/package.yaml b/root/ppg/devel/pgadmin/python3-rich/package.yaml new file mode 100644 index 00000000..ccc1c828 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-rich/package.yaml @@ -0,0 +1,5 @@ +title: rich 15.0.0 for Python 3.12 +description: | + Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-rich/rpm/python3-rich.spec b/root/ppg/devel/pgadmin/python3-rich/rpm/python3-rich.spec new file mode 100644 index 00000000..5c0dc84f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-rich/rpm/python3-rich.spec @@ -0,0 +1,61 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-rich +Version: 15.0.0 +Release: 1%{?dist} +Summary: Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal +License: MIT +URL: https://github.com/Textualize/rich +Source0: https://files.pythonhosted.org/packages/source/r/rich/rich-15.0.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-poetry-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-markdown-it-py >= 2.2.0 +BuildRequires: %{python3_pkgprefix}-pygments >= 2.13.0 + +Requires: %{python3_pkgprefix}-markdown-it-py >= 2.2.0 +Requires: %{python3_pkgprefix}-pygments >= 2.13.0 + +%description +Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n rich-15.0.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import rich" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 15.0.0-1 +- Package rich 15.0.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-secretstorage/obs/_service b/root/ppg/devel/pgadmin/python3-secretstorage/obs/_service new file mode 100644 index 00000000..20b863d5 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-secretstorage/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/S/SecretStorage/secretstorage-3.5.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-secretstorage/package.yaml b/root/ppg/devel/pgadmin/python3-secretstorage/package.yaml new file mode 100644 index 00000000..1232e4d0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-secretstorage/package.yaml @@ -0,0 +1,5 @@ +title: SecretStorage 3.5.0 for Python 3.12 +description: | + Python bindings to FreeDesktop.org Secret Service API. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-secretstorage/rpm/python3-secretstorage.spec b/root/ppg/devel/pgadmin/python3-secretstorage/rpm/python3-secretstorage.spec new file mode 100644 index 00000000..869f120e --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-secretstorage/rpm/python3-secretstorage.spec @@ -0,0 +1,62 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-secretstorage +Version: 3.5.0 +Release: 1%{?dist} +Summary: Python bindings to FreeDesktop.org Secret Service API +License: BSD-3-Clause +URL: https://github.com/mitya57/secretstorage +Source0: https://files.pythonhosted.org/packages/source/S/SecretStorage/secretstorage-3.5.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: python3.12-cryptography +BuildRequires: %{python3_pkgprefix}-jeepney >= 0.6 + +Requires: python3.12-cryptography +Requires: %{python3_pkgprefix}-jeepney >= 0.6 + +%description +Python bindings to FreeDesktop.org Secret Service API. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n secretstorage-3.5.0 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import secretstorage" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.5.0-1 +- Package SecretStorage 3.5.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-shellingham/obs/_service b/root/ppg/devel/pgadmin/python3-shellingham/obs/_service new file mode 100644 index 00000000..e6975373 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-shellingham/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/s/shellingham/shellingham-1.5.4.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-shellingham/package.yaml b/root/ppg/devel/pgadmin/python3-shellingham/package.yaml new file mode 100644 index 00000000..94857c1b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-shellingham/package.yaml @@ -0,0 +1,5 @@ +title: shellingham 1.5.4 for Python 3.12 +description: | + Tool to Detect Surrounding Shell. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-shellingham/rpm/python3-shellingham.spec b/root/ppg/devel/pgadmin/python3-shellingham/rpm/python3-shellingham.spec new file mode 100644 index 00000000..f5042c32 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-shellingham/rpm/python3-shellingham.spec @@ -0,0 +1,54 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-shellingham +Version: 1.5.4 +Release: 1%{?dist} +Summary: Tool to Detect Surrounding Shell +License: ISC +URL: https://github.com/sarugaku/shellingham +Source0: https://files.pythonhosted.org/packages/source/s/shellingham/shellingham-1.5.4.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel + +%description +Tool to Detect Surrounding Shell. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n shellingham-1.5.4 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import shellingham" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.5.4-1 +- Package shellingham 1.5.4 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-simple-websocket/obs/_service b/root/ppg/devel/pgadmin/python3-simple-websocket/obs/_service new file mode 100644 index 00000000..49e29056 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-simple-websocket/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/s/simple-websocket/simple_websocket-1.1.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-simple-websocket/package.yaml b/root/ppg/devel/pgadmin/python3-simple-websocket/package.yaml new file mode 100644 index 00000000..c97e0012 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-simple-websocket/package.yaml @@ -0,0 +1,5 @@ +title: simple-websocket 1.1.0 for Python 3.12 +description: | + Simple WebSocket server and client for Python. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-simple-websocket/rpm/python3-simple-websocket.spec b/root/ppg/devel/pgadmin/python3-simple-websocket/rpm/python3-simple-websocket.spec new file mode 100644 index 00000000..1150e6b7 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-simple-websocket/rpm/python3-simple-websocket.spec @@ -0,0 +1,58 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-simple-websocket +Version: 1.1.0 +Release: 1%{?dist} +Summary: Simple WebSocket server and client for Python +License: MIT +URL: https://github.com/miguelgrinberg/simple-websocket +Source0: https://files.pythonhosted.org/packages/source/s/simple-websocket/simple_websocket-1.1.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-wsproto + +Requires: %{python3_pkgprefix}-wsproto + +%description +Simple WebSocket server and client for Python. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n simple_websocket-1.1.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import simple_websocket" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.1.0-1 +- Package simple-websocket 1.1.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-six/obs/_aggregate b/root/ppg/devel/pgadmin/python3-six/obs/_aggregate new file mode 100644 index 00000000..93b1fc97 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-six/obs/_aggregate @@ -0,0 +1,5 @@ +<aggregatelist> + <aggregate project="${OBS_ROOTPRJ}:ppg:common:deps"> + <package>python3-six</package> + </aggregate> +</aggregatelist> diff --git a/root/ppg/devel/pgadmin/python3-sqlalchemy/obs/_service b/root/ppg/devel/pgadmin/python3-sqlalchemy/obs/_service new file mode 100644 index 00000000..3d89ea10 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlalchemy/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/S/SQLAlchemy/sqlalchemy-2.0.52.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-sqlalchemy/package.yaml b/root/ppg/devel/pgadmin/python3-sqlalchemy/package.yaml new file mode 100644 index 00000000..e1d915e3 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlalchemy/package.yaml @@ -0,0 +1,5 @@ +title: SQLAlchemy 2.0.52 for Python 3.12 +description: | + Database Abstraction Library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-sqlalchemy/rpm/python3-sqlalchemy.spec b/root/ppg/devel/pgadmin/python3-sqlalchemy/rpm/python3-sqlalchemy.spec new file mode 100644 index 00000000..82dff7c0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlalchemy/rpm/python3-sqlalchemy.spec @@ -0,0 +1,60 @@ +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitearch %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-sqlalchemy +Version: 2.0.52 +Release: 1%{?dist} +Summary: Database Abstraction Library +License: MIT +URL: https://www.sqlalchemy.org +Source0: https://files.pythonhosted.org/packages/source/S/SQLAlchemy/sqlalchemy-2.0.52.tar.gz +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-cython +BuildRequires: gcc +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-greenlet >= 1 +BuildRequires: %{python3_pkgprefix}-typing-extensions >= 4.6.0 + +Requires: %{python3_pkgprefix}-greenlet >= 1 +Requires: %{python3_pkgprefix}-typing-extensions >= 4.6.0 + +%description +Database Abstraction Library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n sqlalchemy-2.0.52 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitearch} %{__ospython} -P -c "import sqlalchemy" + +%files +%{python3_sitearch}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.0.52-1 +- Package SQLAlchemy 2.0.52 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-sqlparse/obs/_service b/root/ppg/devel/pgadmin/python3-sqlparse/obs/_service new file mode 100644 index 00000000..f5b64f33 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlparse/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/s/sqlparse/sqlparse-0.6.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-sqlparse/package.yaml b/root/ppg/devel/pgadmin/python3-sqlparse/package.yaml new file mode 100644 index 00000000..fcae017c --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlparse/package.yaml @@ -0,0 +1,5 @@ +title: sqlparse 0.6.0 for Python 3.12 +description: | + A non-validating SQL parser. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-sqlparse/rpm/python3-sqlparse.spec b/root/ppg/devel/pgadmin/python3-sqlparse/rpm/python3-sqlparse.spec new file mode 100644 index 00000000..06089ff1 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sqlparse/rpm/python3-sqlparse.spec @@ -0,0 +1,60 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-sqlparse +Version: 0.6.0 +Release: 1%{?dist} +Summary: A non-validating SQL parser +License: BSD-3-Clause +URL: https://github.com/andialbrecht/sqlparse +Source0: https://files.pythonhosted.org/packages/source/s/sqlparse/sqlparse-0.6.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif + +%description +A non-validating SQL parser. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n sqlparse-0.6.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import sqlparse" + +%files +%{python3_sitelib}/* +%{_bindir}/sqlformat + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.6.0-1 +- Package sqlparse 0.6.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-sshtunnel/obs/_service b/root/ppg/devel/pgadmin/python3-sshtunnel/obs/_service new file mode 100644 index 00000000..30c4bada --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sshtunnel/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-sshtunnel/package.yaml b/root/ppg/devel/pgadmin/python3-sshtunnel/package.yaml new file mode 100644 index 00000000..280d4bea --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sshtunnel/package.yaml @@ -0,0 +1,5 @@ +title: sshtunnel 0.4.0 for Python 3.12 +description: | + Pure python SSH tunnels. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-sshtunnel/rpm/python3-sshtunnel.spec b/root/ppg/devel/pgadmin/python3-sshtunnel/rpm/python3-sshtunnel.spec new file mode 100644 index 00000000..fa739a45 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-sshtunnel/rpm/python3-sshtunnel.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-sshtunnel +Version: 0.4.0 +Release: 1%{?dist} +Summary: Pure python SSH tunnels +License: MIT +URL: https://github.com/pahaz/sshtunnel +Source0: https://files.pythonhosted.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-paramiko + +Requires: %{python3_pkgprefix}-paramiko + +%description +Pure python SSH tunnels. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n sshtunnel-0.4.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import sshtunnel" + +%files +%{python3_sitelib}/* +%{_bindir}/sshtunnel + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.4.0-1 +- Package sshtunnel 0.4.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-typer/obs/_service b/root/ppg/devel/pgadmin/python3-typer/obs/_service new file mode 100644 index 00000000..35d5026f --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typer/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/t/typer/typer-0.26.8.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-typer/package.yaml b/root/ppg/devel/pgadmin/python3-typer/package.yaml new file mode 100644 index 00000000..5a6c4bbc --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typer/package.yaml @@ -0,0 +1,5 @@ +title: typer 0.26.8 for Python 3.12 +description: | + Typer, build great CLIs. Easy to code. Based on Python type hints. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-typer/rpm/python3-typer.spec b/root/ppg/devel/pgadmin/python3-typer/rpm/python3-typer.spec new file mode 100644 index 00000000..aa5b1e8b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typer/rpm/python3-typer.spec @@ -0,0 +1,64 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-typer +Version: 0.26.8 +Release: 1%{?dist} +Summary: Typer, build great CLIs. Easy to code. Based on Python type hints +License: MIT +URL: https://github.com/fastapi/typer +Source0: https://files.pythonhosted.org/packages/source/t/typer/typer-0.26.8.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: %{python3_pkgprefix}-pdm-backend +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-shellingham >= 1.3.0 +BuildRequires: %{python3_pkgprefix}-rich >= 13.8.0 +BuildRequires: %{python3_pkgprefix}-annotated-doc >= 0.0.2 + +Requires: %{python3_pkgprefix}-shellingham >= 1.3.0 +Requires: %{python3_pkgprefix}-rich >= 13.8.0 +Requires: %{python3_pkgprefix}-annotated-doc >= 0.0.2 + +%description +Typer, build great CLIs. Easy to code. Based on Python type hints. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n typer-0.26.8 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import typer" + +%files +%{python3_sitelib}/* +%{_bindir}/typer + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.26.8-1 +- Package typer 0.26.8 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-typing-extensions/obs/_service b/root/ppg/devel/pgadmin/python3-typing-extensions/obs/_service new file mode 100644 index 00000000..762a27aa --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typing-extensions/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/t/typing-extensions/typing_extensions-4.16.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-typing-extensions/package.yaml b/root/ppg/devel/pgadmin/python3-typing-extensions/package.yaml new file mode 100644 index 00000000..23e901d0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typing-extensions/package.yaml @@ -0,0 +1,5 @@ +title: typing-extensions 4.16.0 for Python 3.12 +description: | + Backported and Experimental Type Hints for Python 3.9+. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-typing-extensions/rpm/python3-typing-extensions.spec b/root/ppg/devel/pgadmin/python3-typing-extensions/rpm/python3-typing-extensions.spec new file mode 100644 index 00000000..794df828 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-typing-extensions/rpm/python3-typing-extensions.spec @@ -0,0 +1,55 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-typing-extensions +Version: 4.16.0 +Release: 1%{?dist} +Summary: Backported and Experimental Type Hints for Python 3.9+ +License: PSF-2.0 +URL: https://pypi.org/project/typing-extensions/ +Source0: https://files.pythonhosted.org/packages/source/t/typing-extensions/typing_extensions-4.16.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core + +%description +Backported and Experimental Type Hints for Python 3.9+. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n typing_extensions-4.16.0 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import typing_extensions" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 4.16.0-1 +- Package typing-extensions 4.16.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-ua-parser/obs/_service b/root/ppg/devel/pgadmin/python3-ua-parser/obs/_service new file mode 100644 index 00000000..775a0ed2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ua-parser/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/u/ua-parser/ua-parser-0.18.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-ua-parser/package.yaml b/root/ppg/devel/pgadmin/python3-ua-parser/package.yaml new file mode 100644 index 00000000..08e8a34d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ua-parser/package.yaml @@ -0,0 +1,5 @@ +title: ua-parser 0.18.0 for Python 3.12 +description: | + Python port of Browserscope's user agent parser. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-ua-parser/rpm/python3-ua-parser.spec b/root/ppg/devel/pgadmin/python3-ua-parser/rpm/python3-ua-parser.spec new file mode 100644 index 00000000..3bf11143 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-ua-parser/rpm/python3-ua-parser.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-ua-parser +Version: 0.18.0 +Release: 1%{?dist} +Summary: Python port of Browserscope's user agent parser +License: Apache-2.0 +URL: https://github.com/ua-parser/uap-python +Source0: https://files.pythonhosted.org/packages/source/u/ua-parser/ua-parser-0.18.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# setup.py's build_py compiles regexes.yaml with PyYAML +BuildRequires: python3.12-pyyaml + +%description +Python port of Browserscope's user agent parser. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n ua-parser-0.18.0 +# PyYAML comes from the build root; stop setuptools from fetching it +sed -i '/^\s*setup_requires=\["pyyaml"\],$/d' setup.py + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import ua_parser" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 0.18.0-1 +- Package ua-parser 0.18.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-user-agents/obs/_service b/root/ppg/devel/pgadmin/python3-user-agents/obs/_service new file mode 100644 index 00000000..2a276803 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-user-agents/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/u/user-agents/user-agents-2.2.0.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-user-agents/package.yaml b/root/ppg/devel/pgadmin/python3-user-agents/package.yaml new file mode 100644 index 00000000..682669c2 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-user-agents/package.yaml @@ -0,0 +1,5 @@ +title: user-agents 2.2.0 for Python 3.12 +description: | + A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-user-agents/rpm/python3-user-agents.spec b/root/ppg/devel/pgadmin/python3-user-agents/rpm/python3-user-agents.spec new file mode 100644 index 00000000..f8fd493d --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-user-agents/rpm/python3-user-agents.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-user-agents +Version: 2.2.0 +Release: 1%{?dist} +Summary: A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings +License: MIT +URL: https://github.com/selwin/python-user-agents +Source0: https://files.pythonhosted.org/packages/source/u/user-agents/user-agents-2.2.0.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-ua-parser + +Requires: %{python3_pkgprefix}-ua-parser + +%description +A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n user-agents-2.2.0 + +%build +%{__ospython} setup.py build + +%install +%{__ospython} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} --record=INSTALLED_FILES +find %{buildroot}%{python3_sitelib} -mindepth 1 -type d | sed "s|%{buildroot}||" | sed 's/^/%dir /' >> INSTALLED_FILES + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import user_agents" + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 2.2.0-1 +- Package user-agents 2.2.0 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-werkzeug/obs/_service b/root/ppg/devel/pgadmin/python3-werkzeug/obs/_service new file mode 100644 index 00000000..e284b633 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-werkzeug/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/W/Werkzeug/werkzeug-3.1.8.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-werkzeug/package.yaml b/root/ppg/devel/pgadmin/python3-werkzeug/package.yaml new file mode 100644 index 00000000..cecd5c7b --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-werkzeug/package.yaml @@ -0,0 +1,5 @@ +title: Werkzeug 3.1.8 for Python 3.12 +description: | + The comprehensive WSGI web application library. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-werkzeug/rpm/python3-werkzeug.spec b/root/ppg/devel/pgadmin/python3-werkzeug/rpm/python3-werkzeug.spec new file mode 100644 index 00000000..8e839ef4 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-werkzeug/rpm/python3-werkzeug.spec @@ -0,0 +1,59 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-werkzeug +Version: 3.1.8 +Release: 1%{?dist} +Summary: The comprehensive WSGI web application library +License: BSD-3-Clause +URL: https://github.com/pallets/werkzeug/ +Source0: https://files.pythonhosted.org/packages/source/W/Werkzeug/werkzeug-3.1.8.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +BuildRequires: python%{python3_buildversion}-flit-core +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-markupsafe >= 2.1.1 + +Requires: %{python3_pkgprefix}-markupsafe >= 2.1.1 + +%description +The comprehensive WSGI web application library. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n werkzeug-3.1.8 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import werkzeug" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.1.8-1 +- Package Werkzeug 3.1.8 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-wsproto/obs/_service b/root/ppg/devel/pgadmin/python3-wsproto/obs/_service new file mode 100644 index 00000000..947c6a27 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wsproto/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/w/wsproto/wsproto-1.3.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-wsproto/package.yaml b/root/ppg/devel/pgadmin/python3-wsproto/package.yaml new file mode 100644 index 00000000..b56459ca --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wsproto/package.yaml @@ -0,0 +1,5 @@ +title: wsproto 1.3.2 for Python 3.12 +description: | + Pure-Python WebSocket protocol implementation. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-wsproto/rpm/python3-wsproto.spec b/root/ppg/devel/pgadmin/python3-wsproto/rpm/python3-wsproto.spec new file mode 100644 index 00000000..f37e981a --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wsproto/rpm/python3-wsproto.spec @@ -0,0 +1,60 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-wsproto +Version: 1.3.2 +Release: 1%{?dist} +Summary: Pure-Python WebSocket protocol implementation +License: MIT +URL: https://github.com/python-hyper/wsproto/ +Source0: https://files.pythonhosted.org/packages/source/w/wsproto/wsproto-1.3.2.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-h11 >= 0.16.0 + +Requires: %{python3_pkgprefix}-h11 >= 0.16.0 + +%description +Pure-Python WebSocket protocol implementation. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n wsproto-1.3.2 +# setuptools 68 (RHEL 9) cannot parse PEP 639 licence metadata: use the table form, drop license-files +sed -i -e 's/^license = "\(.*\)"$/license = {text = "\1"}/' -e '/^license-files = \[$/,/^\]$/d' -e '/^license-files = \[.*\]$/d' pyproject.toml + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import wsproto" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 1.3.2-1 +- Package wsproto 1.3.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/root/ppg/devel/pgadmin/python3-wtforms/obs/_service b/root/ppg/devel/pgadmin/python3-wtforms/obs/_service new file mode 100644 index 00000000..9e326351 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wtforms/obs/_service @@ -0,0 +1,5 @@ +<services> + <service name="download_url"> + <param name="url">https://files.pythonhosted.org/packages/source/W/WTForms/wtforms-3.2.2.tar.gz</param> + </service> +</services> diff --git a/root/ppg/devel/pgadmin/python3-wtforms/package.yaml b/root/ppg/devel/pgadmin/python3-wtforms/package.yaml new file mode 100644 index 00000000..323aaad0 --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wtforms/package.yaml @@ -0,0 +1,5 @@ +title: WTForms 3.2.2 for Python 3.12 +description: | + Form validation and rendering for Python web development. RPM-only python3.12-* build from the PyPI sdist, + consumed by pgAdmin 4 (percona-pgadmin4). Bump by editing obs/_service (sdist URL) and + rpm/*.spec (Version + changelog). diff --git a/root/ppg/devel/pgadmin/python3-wtforms/rpm/python3-wtforms.spec b/root/ppg/devel/pgadmin/python3-wtforms/rpm/python3-wtforms.spec new file mode 100644 index 00000000..ada98baf --- /dev/null +++ b/root/ppg/devel/pgadmin/python3-wtforms/rpm/python3-wtforms.spec @@ -0,0 +1,65 @@ +%global debug_package %{nil} +%if 0%{?rhel} && 0%{?rhel} >= 8 +%global __ospython %{_bindir}/python3.12 +%global python3_pkgprefix python3.12 +%global python3_buildversion 3.12 +%global __requires_exclude ^python3\\.12dist +%else +%global __ospython %{_bindir}/python3 +%global python3_pkgprefix python3 +%global python3_buildversion 3 +%endif +%{expand: %%global py3ver %(echo `%{__ospython} -P -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" `)} +%global python3_sitelib %(%{__ospython} -Esc "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '/usr', 'base': '%{_prefix}'}))") + +Name: %{python3_pkgprefix}-wtforms +Version: 3.2.2 +Release: 1%{?dist} +Summary: Form validation and rendering for Python web development +License: BSD-3-Clause +URL: https://pypi.org/project/WTForms/ +Source0: https://files.pythonhosted.org/packages/source/W/WTForms/wtforms-3.2.2.tar.gz +BuildArch: noarch +Vendor: Percona, LLC +Packager: Percona Development Team <https://jira.percona.com> +Epoch: 1 + +BuildRequires: python%{python3_buildversion}-devel +BuildRequires: python%{python3_buildversion}-pip +BuildRequires: python%{python3_buildversion}-setuptools +BuildRequires: python%{python3_buildversion}-wheel +%if 0%{?rhel} == 8 || 0%{?rhel} == 9 +BuildRequires: %{python3_pkgprefix}-hatchling +%else +BuildRequires: python3-hatchling +%endif +# the hatch build hook compiles the translation catalogs with Babel +BuildRequires: %{python3_pkgprefix}-babel +# runtime dependencies, also needed by the %check import test +BuildRequires: %{python3_pkgprefix}-markupsafe >= 1.1.1 + +Requires: %{python3_pkgprefix}-markupsafe >= 1.1.1 + +%description +Form validation and rendering for Python web development. + +Built for Python 3.12 from the PyPI sdist; part of the pgAdmin 4 (percona-pgadmin4) dependency stack. + +%prep +%autosetup -p1 -n wtforms-3.2.2 + +%build +%{__ospython} -m pip wheel --no-deps --no-build-isolation --no-index --wheel-dir dist . + +%install +%{__ospython} -m pip install --no-deps --no-index --root %{buildroot} --prefix %{_prefix} dist/*.whl + +%check +PYTHONPATH=%{buildroot}%{python3_sitelib} %{__ospython} -P -c "import wtforms" + +%files +%{python3_sitelib}/* + +%changelog +* Thu Aug 27 2026 Percona Development Team <info@percona.com> - 3.2.2-1 +- Package WTForms 3.2.2 for Python 3.12 (pgAdmin 4 dependency stack) diff --git a/tests/test_run_local_services.py b/tests/test_run_local_services.py new file mode 100644 index 00000000..1caea9cc --- /dev/null +++ b/tests/test_run_local_services.py @@ -0,0 +1,133 @@ +"""Unit tests for services._run_local_services with fake service binaries. + +Reproduces the pgAdmin node_modules requirement: a mode="manual" service may +legitimately emit an .obscpio archive (node_modules.obscpio) that OBS unpacks +itself at build time. _run_local_services used to extract and delete every +*.obscpio in the workdir, which would have exploded that archive into ~1400 +loose tarballs and dropped it from the upload. Only Phase-1 (obs_scm) +archives may be extracted. +""" + +import shutil +import stat +from pathlib import Path + +import pytest + +import percona_obs.services as services +from percona_obs.services import _run_local_services + +SERVICE_XML = """<services> + <service name="obs_scm"> + <param name="url">https://example.invalid/foo.git</param> + <param name="scm">git</param> + <param name="revision">v1.0</param> + <param name="filename">foo</param> + </service> + <service name="fake_manual" mode="manual"> + <param name="cpio">bar.obscpio</param> + </service> +</services> +""" + +# Fake obs_scm: writes foo.obsinfo (with a commit) and a real cpio archive +# containing foo-1.0/hello.txt into --outdir. +FAKE_OBS_SCM = """#!/bin/sh +set -e +outdir="" +while [ $# -gt 0 ]; do + if [ "$1" = "--outdir" ]; then outdir="$2"; shift; fi + shift +done +printf 'name: foo\\nversion: 1.0\\nmtime: 0\\ncommit: %s\\n' "$FAKE_COMMIT" > "$outdir/foo.obsinfo" +tmp=$(mktemp -d) +mkdir -p "$tmp/foo-1.0" +echo hello > "$tmp/foo-1.0/hello.txt" +(cd "$tmp" && find foo-1.0 -depth -print | cpio -o -H newc --quiet) > "$outdir/foo.obscpio" +rm -rf "$tmp" +""" + +# Fake manual service: writes bar.obscpio (arbitrary bytes are fine — the +# tool must not open it) and a companion text file into --outdir. +FAKE_MANUAL = """#!/bin/sh +set -e +outdir="" +while [ $# -gt 0 ]; do + if [ "$1" = "--outdir" ]; then outdir="$2"; shift; fi + shift +done +printf 'MANUAL-CPIO-BYTES' > "$outdir/bar.obscpio" +printf 'Source10000: x\\n' > "$outdir/bar.spec.inc" +""" + +FAIL_IF_RUN = """#!/bin/sh +echo "manual service must not run on a cache hit" >&2 +exit 99 +""" + + +def _install(bindir: Path, name: str, body: str) -> None: + path = bindir / name + path.write_text(body) + path.chmod(path.stat().st_mode | stat.S_IXUSR) + + +@pytest.fixture +def fake_services(tmp_path, monkeypatch): + if shutil.which("cpio") is None: + pytest.skip("cpio not installed") + bindir = tmp_path / "obs-service" + bindir.mkdir() + _install(bindir, "obs_scm", FAKE_OBS_SCM) + _install(bindir, "fake_manual", FAKE_MANUAL) + monkeypatch.setattr(services, "_OBS_SERVICE_DIR", bindir) + monkeypatch.setenv("FAKE_COMMIT", "0123456789abcdef0123456789abcdef01234567") + # Point every cache tree at tmp so nothing touches the repo's .cache/. + cache_dir = tmp_path / ".cache" + for attr, sub in [ + ("_OBS_SCM_CACHE_DIR", "obs_scm"), + ("_SVC_CACHE_DIR", "services"), + ("_DOWNLOAD_URL_CACHE_DIR", "download_url"), + ("_CARGO_VENDOR_CACHE_DIR", "cargo_vendor"), + ]: + monkeypatch.setattr(services, attr, cache_dir / sub) + # No network: the obs_scm cache key lookup must fall through to _run_one. + monkeypatch.setattr(services, "_git_head_sha", lambda url, rev: None) + pkg = tmp_path / "pkg" + obs_dir = pkg / "obs" + obs_dir.mkdir(parents=True) + (obs_dir / "_service").write_text(SERVICE_XML) + return bindir, obs_dir, cache_dir + + +def test_manual_obscpio_kept_and_obs_scm_obscpio_extracted(fake_services): + _, obs_dir, _ = fake_services + workdir = _run_local_services(obs_dir, cache=False) + try: + names = sorted(p.name for p in workdir.iterdir()) + assert "bar.obscpio" in names + assert (workdir / "bar.obscpio").read_bytes() == b"MANUAL-CPIO-BYTES" + assert "bar.spec.inc" in names + assert "foo.obscpio" not in names + assert "foo.obsinfo" in names + assert not any(p.is_dir() for p in workdir.iterdir()) + finally: + shutil.rmtree(workdir, ignore_errors=True) + + +def test_manual_obscpio_kept_on_cache_hit(fake_services): + bindir, obs_dir, cache_dir = fake_services + commit = "0123456789abcdef0123456789abcdef01234567" + entry = cache_dir / "services" / commit + entry.mkdir(parents=True) + (entry / "bar.obscpio").write_bytes(b"CACHED-CPIO-BYTES") + (entry / "bar.spec.inc").write_text("Source10000: cached\n") + _install(bindir, "fake_manual", FAIL_IF_RUN) + + workdir = _run_local_services(obs_dir, cache=True) + try: + assert (workdir / "bar.obscpio").read_bytes() == b"CACHED-CPIO-BYTES" + assert (workdir / "bar.spec.inc").read_text() == "Source10000: cached\n" + assert not (workdir / "foo.obscpio").exists() + finally: + shutil.rmtree(workdir, ignore_errors=True) diff --git a/tests/test_vendor_content_check.py b/tests/test_vendor_content_check.py index 59285df9..17864c9b 100644 --- a/tests/test_vendor_content_check.py +++ b/tests/test_vendor_content_check.py @@ -1,78 +1,176 @@ -"""Unit tests for vendor-aware content comparison (percona_obs.cmd_sync). - -Reproduces the percona/obs-packaging PR #5 bug: pgvectorscale ships no -Cargo.lock, so cargo_vendor re-resolves the crate graph on every run and -vendor.tar.gz bytes drift with the crates.io state at generation time. A -branch-from content check comparing raw md5s then reported "vendor.tar.gz -differs" for packages whose real inputs were untouched, promoting -percona-pgvectorscale in every extras project on unrelated PRs. +"""Unit tests for drift-tolerant content comparison (services + cmd_sync). + +Some service outputs are functions of an external registry's state at +generation time rather than of any input under git: + +* cargo_vendor's vendor.tar.* — percona/obs-packaging PR #5: pgvectorscale + ships no Cargo.lock, so the crate graph is re-resolved on every run and the + bytes drift between machines and dates. +* node_modules' node_modules.obscpio / node_modules.spec.inc and the + package-lock.json generated by npm_lockfile — npm re-resolves ranges. + +A branch-from content check comparing raw md5s would report those files as +"differs" and promote the package on unrelated PRs. Such files are compared +by presence only; absence on either side is still a mismatch. """ +import re + from percona_obs.cmd_sync import _content_mismatches -from percona_obs.services import _has_cargo_vendor_service +from percona_obs.services import drift_tolerant_patterns SPEC = "percona-pgvectorscale.spec" SRC = "percona-pgvectorscale_16-0.9.0.tar.gz" VENDOR = "vendor.tar.gz" +LOCK = "package-lock.json" +CPIO = "node_modules.obscpio" +INC = "node_modules.spec.inc" + + +def _service_file(tmp_path, body): + path = tmp_path / "obs" / "_service" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(f"<services>{body}</services>") + return path + + +def _matches(patterns, name): + return any(p.search(name) for p in patterns) + + +def _cargo(tmp_path): + return drift_tolerant_patterns( + _service_file( + tmp_path, + '<service mode="buildtime" name="cargo_vendor">' + '<param name="compression">gz</param></service>', + ) + ) + + +def _npm(tmp_path, params=""): + return drift_tolerant_patterns( + _service_file( + tmp_path, + f'<service name="node_modules" mode="manual">{params}</service>', + ) + ) + + +# ── drift_tolerant_patterns ────────────────────────────────────────────── + + +def test_no_patterns_without_registry_services(tmp_path): + path = _service_file(tmp_path, '<service mode="buildtime" name="tar" />') + assert drift_tolerant_patterns(path) == [] + +def test_no_patterns_for_missing_or_broken_service_file(tmp_path): + assert drift_tolerant_patterns(tmp_path / "missing" / "_service") == [] + broken = tmp_path / "broken" / "_service" + broken.parent.mkdir() + broken.write_text("<services><service name='x'") + assert drift_tolerant_patterns(broken) == [] -def test_vendor_only_drift_ignored_for_cargo_packages(): + +def test_cargo_vendor_patterns_cover_every_compression(tmp_path): + pats = _cargo(tmp_path) + for name in ("vendor.tar.gz", "vendor.tar.xz", "vendor.tar.zst"): + assert _matches(pats, name) + assert not _matches(pats, "vendor.tar") + assert not _matches(pats, "my-vendor.tar.gz") + assert not _matches(pats, SPEC) + + +def test_node_modules_default_patterns(tmp_path): + pats = _npm(tmp_path) + for name in (CPIO, INC, LOCK): + assert _matches(pats, name) + assert not _matches(pats, SPEC) + assert not _matches(pats, "node_modules.sums") + + +def test_node_modules_patterns_follow_params(tmp_path): + pats = _npm( + tmp_path, + '<param name="cpio">deps.obscpio</param>' + '<param name="output">deps.inc</param>' + '<param name="input">web-lock.json</param>', + ) + for name in ("deps.obscpio", "deps.inc", "web-lock.json"): + assert _matches(pats, name) + assert not _matches(pats, CPIO) + assert not _matches(pats, INC) + + +def test_both_services_combine(tmp_path): + pats = drift_tolerant_patterns( + _service_file( + tmp_path, + '<service mode="buildtime" name="cargo_vendor" />' + '<service name="node_modules" mode="manual" />', + ) + ) + assert _matches(pats, VENDOR) + assert _matches(pats, CPIO) + + +# ── _content_mismatches ────────────────────────────────────────────────── + + +def test_vendor_only_drift_ignored_for_cargo_packages(tmp_path): local = {SPEC: "a", SRC: "b", VENDOR: "local-bytes"} obs = {SPEC: "a", SRC: "b", VENDOR: "obs-bytes"} - assert _content_mismatches(local, obs, ignore_vendor=True) == [] + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [] -def test_vendor_drift_reported_without_cargo_vendor_service(): +def test_vendor_drift_reported_without_tolerant_patterns(): local = {SPEC: "a", VENDOR: "local-bytes"} obs = {SPEC: "a", VENDOR: "obs-bytes"} - assert _content_mismatches(local, obs, ignore_vendor=False) == [VENDOR] + assert _content_mismatches(local, obs, []) == [VENDOR] -def test_real_change_still_reported_alongside_vendor_drift(): +def test_real_change_still_reported_alongside_vendor_drift(tmp_path): local = {SPEC: "changed", SRC: "b", VENDOR: "local-bytes"} obs = {SPEC: "a", SRC: "b", VENDOR: "obs-bytes"} - assert _content_mismatches(local, obs, ignore_vendor=True) == [SPEC] + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [SPEC] -def test_vendor_missing_on_obs_is_a_mismatch(): +def test_vendor_missing_on_obs_is_a_mismatch(tmp_path): local = {SPEC: "a", VENDOR: "local-bytes"} obs = {SPEC: "a"} - assert _content_mismatches(local, obs, ignore_vendor=True) == [VENDOR] + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [VENDOR] -def test_vendor_missing_locally_is_a_mismatch(): +def test_vendor_missing_locally_is_a_mismatch(tmp_path): local = {SPEC: "a"} obs = {SPEC: "a", VENDOR: "obs-bytes"} - assert _content_mismatches(local, obs, ignore_vendor=True) == [VENDOR] + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [VENDOR] + + +def test_npm_artifact_drift_ignored_for_node_modules_packages(tmp_path): + local = {SPEC: "a", LOCK: "l1", CPIO: "c1", INC: "i1"} + obs = {SPEC: "a", LOCK: "l2", CPIO: "c2", INC: "i2"} + assert _content_mismatches(local, obs, _npm(tmp_path)) == [] + + +def test_npm_artifact_missing_on_obs_is_a_mismatch(tmp_path): + local = {SPEC: "a", LOCK: "l1", CPIO: "c1", INC: "i1"} + obs = {SPEC: "a", LOCK: "l2", INC: "i2"} + assert _content_mismatches(local, obs, _npm(tmp_path)) == [CPIO] -def test_other_compressions_ignored_too(): - local = {SPEC: "a", "vendor.tar.xz": "x", "vendor.tar.zst": "y"} - obs = {SPEC: "a", "vendor.tar.xz": "p", "vendor.tar.zst": "q"} - assert _content_mismatches(local, obs, ignore_vendor=True) == [] +def test_npm_drift_reported_for_packages_without_node_modules(tmp_path): + local = {SPEC: "a", CPIO: "c1"} + obs = {SPEC: "a", CPIO: "c2"} + assert _content_mismatches(local, obs, _cargo(tmp_path)) == [CPIO] def test_identical_content_has_no_mismatches(): local = {SPEC: "a", SRC: "b"} obs = {SPEC: "a", SRC: "b"} - assert _content_mismatches(local, obs, ignore_vendor=False) == [] - - -def test_has_cargo_vendor_service(tmp_path): - with_vendor = tmp_path / "with" / "_service" - with_vendor.parent.mkdir() - with_vendor.write_text( - "<services>" - '<service mode="buildtime" name="cargo_vendor">' - '<param name="compression">gz</param>' - "</service>" - "</services>" - ) - without_vendor = tmp_path / "without" / "_service" - without_vendor.parent.mkdir() - without_vendor.write_text( - '<services><service mode="buildtime" name="tar" /></services>' - ) - assert _has_cargo_vendor_service(with_vendor) is True - assert _has_cargo_vendor_service(without_vendor) is False - assert _has_cargo_vendor_service(tmp_path / "missing" / "_service") is False + assert _content_mismatches(local, obs, []) == [] + + +def test_patterns_are_compiled_regexes(tmp_path): + assert all(isinstance(p, re.Pattern) for p in _cargo(tmp_path))