juniper-ci-tools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Overtoad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: juniper-ci-tools
3
+ Version: 0.1.0
4
+ Summary: Shared CI / build tooling for the Juniper ML platform (dependency-documentation generator with YAML-safe conda export)
5
+ Author: Paul Calnon
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pcalnon/juniper-ml
8
+ Project-URL: Repository, https://github.com/pcalnon/juniper-ml
9
+ Project-URL: Issues, https://github.com/pcalnon/juniper-ml/issues
10
+ Keywords: juniper,ci,dependency-documentation,conda,pip
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Classifier: Topic :: System :: Software Distribution
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: PyYAML>=6.0
25
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=8.0; extra == "test"
28
+ Requires-Dist: pytest-cov>=5.0; extra == "test"
29
+ Dynamic: license-file
30
+
31
+ <!-- markdownlint-disable -->
32
+
33
+ # juniper-ci-tools
34
+
35
+ Shared CI / build tooling for the [Juniper ML platform](https://github.com/pcalnon/juniper-ml).
36
+
37
+ This package is the **single source of truth** for the dependency-documentation
38
+ generator that historically lived as `scripts/generate_dep_docs.sh` (or
39
+ `util/generate_dep_docs.sh`) in every Juniper repo. Three variants of that
40
+ script drifted on `origin/main`; the 2026-05-20 bug fix in
41
+ [juniper-cascor#276](https://github.com/pcalnon/juniper-cascor/pull/276)
42
+ (switching the conda-dependency extraction from `sed` to `awk` to avoid
43
+ emitting an invalid trailing `prefix:` / `variables:` key) shipped in 1 of 8
44
+ repos. This package distributes that fix as `pip install`-able tooling so it
45
+ cannot drift again.
46
+
47
+ This work mirrors the
48
+ [`juniper-doc-tools` PyPI migration plan](https://github.com/pcalnon/juniper-ml/blob/main/notes/JUNIPER_DOC_TOOLS_PYPI_MIGRATION_PLAN_2026-05-18.md)
49
+ that addressed the analogous 2026-05-18 doc-link validator incident.
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install juniper-ci-tools
55
+ ```
56
+
57
+ This installs the `juniper-generate-dep-docs` console script. The package
58
+ requires Python 3.11 or newer and depends on PyYAML.
59
+
60
+ ## Usage
61
+
62
+ Run from the root of any Juniper repo that has a `pyproject.toml`:
63
+
64
+ ```bash
65
+ juniper-generate-dep-docs
66
+ ```
67
+
68
+ Equivalent module form:
69
+
70
+ ```bash
71
+ python -m juniper_ci_tools
72
+ ```
73
+
74
+ This will:
75
+
76
+ 1. Create `conf/` if needed.
77
+ 2. Back up any existing `conf/requirements_ci.txt` with a timestamp infix.
78
+ 3. Render `notes/PIP_DEPENDENCY_FILE_HEADER.md` (substituting placeholders
79
+ like `<X.Y.Z ...>`, `<YYYY-MM-dd ...>`, `<Python Version>`, `<Pip Version>`)
80
+ and append `pip list --format=freeze` output.
81
+ 4. If `conda` is on `PATH`: back up any existing
82
+ `conf/conda_environment_ci.yaml`, render
83
+ `notes/CONDA_DEPENDENCY_FILE_HEADER.md`, append the dependency block
84
+ extracted from `conda env export --no-builds` (using the awk-equivalent
85
+ logic that produces valid YAML), and validate the result with
86
+ `yaml.safe_load`.
87
+
88
+ If conda is not available the conda step is skipped with a warning (matches
89
+ the legacy bash script). If the generated YAML fails to parse, the command
90
+ exits non-zero.
91
+
92
+ ### CLI options
93
+
94
+ | Flag | Default | Purpose |
95
+ |---|---|---|
96
+ | `--repo-root` | cwd | Repo root containing `pyproject.toml` |
97
+ | `--conf-dir` | `conf` | Output directory |
98
+ | `--notes-dir` | `notes` | Directory containing header templates |
99
+ | `--pip-header` | `PIP_DEPENDENCY_FILE_HEADER.md` | Pip header template filename |
100
+ | `--conda-header` | `CONDA_DEPENDENCY_FILE_HEADER.md` | Conda header template filename |
101
+ | `--pip-filename` | `requirements_ci.txt` | Pip output filename |
102
+ | `--conda-filename` | `conda_environment_ci.yaml` | Conda output filename |
103
+ | `--no-conda` | off | Skip conda generation even if conda is installed |
104
+ | `--no-yaml-validation` | off | Skip `yaml.safe_load` on generated conda file |
105
+
106
+ ## Library API
107
+
108
+ ```python
109
+ from juniper_ci_tools import generate_dep_docs
110
+
111
+ result = generate_dep_docs(repo_root="/path/to/repo")
112
+ print(result.pip_file, result.conda_file, result.yaml_validated)
113
+ ```
114
+
115
+ See the package's `juniper_ci_tools/generate_dep_docs.py` docstring for the
116
+ full surface.
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ cd juniper-ci-tools
122
+ pip install -e ".[test]"
123
+ pytest
124
+ ```
125
+
126
+ ## License
127
+
128
+ MIT. Copyright (c) 2024-2026 Paul Calnon.
@@ -0,0 +1,98 @@
1
+ <!-- markdownlint-disable -->
2
+
3
+ # juniper-ci-tools
4
+
5
+ Shared CI / build tooling for the [Juniper ML platform](https://github.com/pcalnon/juniper-ml).
6
+
7
+ This package is the **single source of truth** for the dependency-documentation
8
+ generator that historically lived as `scripts/generate_dep_docs.sh` (or
9
+ `util/generate_dep_docs.sh`) in every Juniper repo. Three variants of that
10
+ script drifted on `origin/main`; the 2026-05-20 bug fix in
11
+ [juniper-cascor#276](https://github.com/pcalnon/juniper-cascor/pull/276)
12
+ (switching the conda-dependency extraction from `sed` to `awk` to avoid
13
+ emitting an invalid trailing `prefix:` / `variables:` key) shipped in 1 of 8
14
+ repos. This package distributes that fix as `pip install`-able tooling so it
15
+ cannot drift again.
16
+
17
+ This work mirrors the
18
+ [`juniper-doc-tools` PyPI migration plan](https://github.com/pcalnon/juniper-ml/blob/main/notes/JUNIPER_DOC_TOOLS_PYPI_MIGRATION_PLAN_2026-05-18.md)
19
+ that addressed the analogous 2026-05-18 doc-link validator incident.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install juniper-ci-tools
25
+ ```
26
+
27
+ This installs the `juniper-generate-dep-docs` console script. The package
28
+ requires Python 3.11 or newer and depends on PyYAML.
29
+
30
+ ## Usage
31
+
32
+ Run from the root of any Juniper repo that has a `pyproject.toml`:
33
+
34
+ ```bash
35
+ juniper-generate-dep-docs
36
+ ```
37
+
38
+ Equivalent module form:
39
+
40
+ ```bash
41
+ python -m juniper_ci_tools
42
+ ```
43
+
44
+ This will:
45
+
46
+ 1. Create `conf/` if needed.
47
+ 2. Back up any existing `conf/requirements_ci.txt` with a timestamp infix.
48
+ 3. Render `notes/PIP_DEPENDENCY_FILE_HEADER.md` (substituting placeholders
49
+ like `<X.Y.Z ...>`, `<YYYY-MM-dd ...>`, `<Python Version>`, `<Pip Version>`)
50
+ and append `pip list --format=freeze` output.
51
+ 4. If `conda` is on `PATH`: back up any existing
52
+ `conf/conda_environment_ci.yaml`, render
53
+ `notes/CONDA_DEPENDENCY_FILE_HEADER.md`, append the dependency block
54
+ extracted from `conda env export --no-builds` (using the awk-equivalent
55
+ logic that produces valid YAML), and validate the result with
56
+ `yaml.safe_load`.
57
+
58
+ If conda is not available the conda step is skipped with a warning (matches
59
+ the legacy bash script). If the generated YAML fails to parse, the command
60
+ exits non-zero.
61
+
62
+ ### CLI options
63
+
64
+ | Flag | Default | Purpose |
65
+ |---|---|---|
66
+ | `--repo-root` | cwd | Repo root containing `pyproject.toml` |
67
+ | `--conf-dir` | `conf` | Output directory |
68
+ | `--notes-dir` | `notes` | Directory containing header templates |
69
+ | `--pip-header` | `PIP_DEPENDENCY_FILE_HEADER.md` | Pip header template filename |
70
+ | `--conda-header` | `CONDA_DEPENDENCY_FILE_HEADER.md` | Conda header template filename |
71
+ | `--pip-filename` | `requirements_ci.txt` | Pip output filename |
72
+ | `--conda-filename` | `conda_environment_ci.yaml` | Conda output filename |
73
+ | `--no-conda` | off | Skip conda generation even if conda is installed |
74
+ | `--no-yaml-validation` | off | Skip `yaml.safe_load` on generated conda file |
75
+
76
+ ## Library API
77
+
78
+ ```python
79
+ from juniper_ci_tools import generate_dep_docs
80
+
81
+ result = generate_dep_docs(repo_root="/path/to/repo")
82
+ print(result.pip_file, result.conda_file, result.yaml_validated)
83
+ ```
84
+
85
+ See the package's `juniper_ci_tools/generate_dep_docs.py` docstring for the
86
+ full surface.
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ cd juniper-ci-tools
92
+ pip install -e ".[test]"
93
+ pytest
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT. Copyright (c) 2024-2026 Paul Calnon.
@@ -0,0 +1,36 @@
1
+ """``juniper-ci-tools`` -- shared CI / build tooling for the Juniper ecosystem.
2
+
3
+ Single-source-of-truth Python package for the dependency-documentation
4
+ generator that historically lived as a standalone
5
+ ``scripts/generate_dep_docs.sh`` in every Juniper repo. Three variants drifted
6
+ on origin/main; the cascor 2026-05-20 bug fix (sed -> awk for YAML extraction)
7
+ shipped to 1 of 8 repos. This package replaces all of them with a single
8
+ PyPI-distributed implementation that survives single-repo CI checkouts and
9
+ cannot drift.
10
+
11
+ The public API is:
12
+
13
+ - :class:`GenerateResult` -- structured outcome of a generation run.
14
+ - :func:`generate_dep_docs` -- ergonomic library entry point.
15
+
16
+ For CLI usage, install the package and run :program:`juniper-generate-dep-docs`
17
+ or ``python -m juniper_ci_tools``; the CLI surface is in :mod:`cli`.
18
+
19
+ See ``notes/JUNIPER_CI_TOOLS_PYPI_MIGRATION_PLAN_2026-05-20.md`` in the
20
+ juniper-ml repo for the design rationale, the incident-class motivation
21
+ (mirroring the 2026-05-18 doc-link validator incident), and the wave plan.
22
+ """
23
+
24
+ from juniper_ci_tools._version import __version__
25
+ from juniper_ci_tools.generate_dep_docs import (
26
+ GenerateResult,
27
+ generate_dep_docs,
28
+ render_header,
29
+ )
30
+
31
+ __all__ = [
32
+ "__version__",
33
+ "GenerateResult",
34
+ "generate_dep_docs",
35
+ "render_header",
36
+ ]
@@ -0,0 +1,13 @@
1
+ """``python -m juniper_ci_tools`` entry point.
2
+
3
+ Provides the module-form invocation alongside the
4
+ ``juniper-generate-dep-docs`` console script (mirrors the doc-tools pattern).
5
+ Both route through :func:`cli.main` so they cannot diverge.
6
+ """
7
+
8
+ import sys
9
+
10
+ from juniper_ci_tools.cli import main
11
+
12
+ if __name__ == "__main__":
13
+ sys.exit(main())
@@ -0,0 +1,3 @@
1
+ """Single source of truth for the package version."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,118 @@
1
+ """Command-line entry point for :program:`juniper-generate-dep-docs`.
2
+
3
+ Wraps :func:`juniper_ci_tools.generate_dep_docs.generate_dep_docs` with
4
+ argparse and printed progress output that mirrors the legacy bash script's
5
+ banner. Supports two invocation forms:
6
+
7
+ - ``juniper-generate-dep-docs [args]`` (console script, see :file:`pyproject.toml`)
8
+ - ``python -m juniper_ci_tools [args]`` (module form, see :mod:`__main__`)
9
+
10
+ Both route through :func:`main`.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ from juniper_ci_tools._version import __version__
20
+ from juniper_ci_tools.generate_dep_docs import (
21
+ GenerateDepDocsError,
22
+ YamlValidationError,
23
+ generate_dep_docs,
24
+ )
25
+
26
+
27
+ def _build_parser() -> argparse.ArgumentParser:
28
+ parser = argparse.ArgumentParser(
29
+ prog="juniper-generate-dep-docs",
30
+ description=("Generate conf/requirements_ci.txt and conf/conda_environment_ci.yaml for a Juniper repo. Replaces the legacy scripts/generate_dep_docs.sh that drifted across 8 repos."),
31
+ )
32
+ parser.add_argument("--repo-root", type=Path, default=None, help="Repo root containing pyproject.toml (default: cwd).")
33
+ parser.add_argument("--conf-dir", default="conf", help="Output directory for generated files (default: conf).")
34
+ parser.add_argument("--notes-dir", default="notes", help="Directory containing header templates (default: notes).")
35
+ parser.add_argument("--pip-header", default="PIP_DEPENDENCY_FILE_HEADER.md", help="Pip-requirements header template filename (in --notes-dir).")
36
+ parser.add_argument("--conda-header", default="CONDA_DEPENDENCY_FILE_HEADER.md", help="Conda-environment header template filename (in --notes-dir).")
37
+ parser.add_argument("--pip-filename", default="requirements_ci.txt", help="Output filename for pip requirements (in --conf-dir).")
38
+ parser.add_argument("--conda-filename", default="conda_environment_ci.yaml", help="Output filename for conda environment (in --conf-dir).")
39
+ parser.add_argument("--no-conda", action="store_true", help="Skip conda_environment_ci.yaml generation even if conda is available.")
40
+ parser.add_argument("--no-yaml-validation", action="store_true", help="Skip the post-write YAML validation of the conda environment file.")
41
+ parser.add_argument("--version", action="version", version="juniper-generate-dep-docs {}".format(__version__))
42
+ return parser
43
+
44
+
45
+ def _print_banner(args: argparse.Namespace, *, repo_version: str, python_version: str, pip_version: str, timestamp: str) -> None:
46
+ bar = "═" * 60
47
+ print("╔{}╗".format(bar))
48
+ print("║ Juniper - Generate Dependency Documentation ║")
49
+ print("╚{}╝".format(bar))
50
+ print()
51
+ print(" Repo Version: {}".format(repo_version))
52
+ print(" Python Version: {}".format(python_version))
53
+ print(" Pip Version: {}".format(pip_version))
54
+ print(" Timestamp: {}".format(timestamp))
55
+ print()
56
+
57
+
58
+ def main(argv: list[str] | None = None) -> int:
59
+ parser = _build_parser()
60
+ args = parser.parse_args(argv)
61
+
62
+ try:
63
+ # Detect versions early so we can print the banner even if the
64
+ # generation step fails on something later. We re-detect inside
65
+ # generate_dep_docs() so the result remains the source of truth.
66
+ from juniper_ci_tools.generate_dep_docs import (
67
+ _detect_pip_version,
68
+ _detect_python_version,
69
+ _now,
70
+ _read_pyproject_version,
71
+ )
72
+
73
+ root = Path(args.repo_root) if args.repo_root is not None else Path.cwd()
74
+ repo_version = _read_pyproject_version(root)
75
+ python_version = _detect_python_version()
76
+ pip_version = _detect_pip_version()
77
+ ts = _now().strftime("%Y-%m-%d_%H-%M-%S")
78
+ _print_banner(args, repo_version=repo_version, python_version=python_version, pip_version=pip_version, timestamp=ts)
79
+
80
+ result = generate_dep_docs(
81
+ repo_root=args.repo_root,
82
+ conf_dir=args.conf_dir,
83
+ notes_dir=args.notes_dir,
84
+ pip_header_name=args.pip_header,
85
+ conda_header_name=args.conda_header,
86
+ pip_filename=args.pip_filename,
87
+ conda_filename=args.conda_filename,
88
+ include_conda=not args.no_conda,
89
+ validate_yaml=not args.no_yaml_validation,
90
+ )
91
+
92
+ if result.pip_backup is not None:
93
+ print(" Backing up existing pip requirements to: {}".format(result.pip_backup))
94
+ print(" Generated: {}".format(result.pip_file))
95
+
96
+ if result.conda_file is not None:
97
+ if result.conda_backup is not None:
98
+ print(" Backing up existing conda environment to: {}".format(result.conda_backup))
99
+ if result.yaml_validated:
100
+ print(" Validated: {} YAML syntax OK".format(result.conda_file))
101
+ print(" Generated: {}".format(result.conda_file))
102
+ elif result.conda_skipped_reason:
103
+ print(" WARNING: {}".format(result.conda_skipped_reason))
104
+ print(" Install miniforge/miniconda to generate conda_environment_ci.yaml")
105
+
106
+ print()
107
+ print(" Dependency documentation generation complete.")
108
+ return 0
109
+ except YamlValidationError as exc:
110
+ print(" ERROR: {}".format(exc), file=sys.stderr)
111
+ return 1
112
+ except GenerateDepDocsError as exc:
113
+ print(" ERROR: {}".format(exc), file=sys.stderr)
114
+ return 1
115
+
116
+
117
+ if __name__ == "__main__": # pragma: no cover
118
+ sys.exit(main())