qauvern 0.2.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,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+ - name: install
18
+ run: |
19
+ uv tool install rust-just
20
+ uv sync
21
+ - run: just lint
22
+ - run: just test
@@ -0,0 +1,154 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'pyproject.toml'
9
+
10
+ jobs:
11
+ validate:
12
+ runs-on: ubuntu-latest
13
+ outputs:
14
+ version: ${{ steps.get-version.outputs.version }}
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Get version from pyproject.toml
20
+ id: get-version
21
+ run: |
22
+ VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
23
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
24
+ echo "Version to release: $VERSION"
25
+
26
+ - name: Validate version format
27
+ run: |
28
+ VERSION="${{ steps.get-version.outputs.version }}"
29
+ if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
30
+ echo "Error: Invalid version format: $VERSION"
31
+ exit 1
32
+ fi
33
+
34
+ - name: Check if tag exists
35
+ run: |
36
+ VERSION="${{ steps.get-version.outputs.version }}"
37
+ if git ls-remote --tags origin "v$VERSION" | grep -q .; then
38
+ echo "Error: Tag v$VERSION already exists"
39
+ exit 1
40
+ fi
41
+
42
+ test:
43
+ needs: validate
44
+ runs-on: ubuntu-latest
45
+ strategy:
46
+ matrix:
47
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
48
+
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+
52
+ - uses: astral-sh/setup-uv@v5
53
+ with:
54
+ python-version: ${{ matrix.python-version }}
55
+
56
+ - name: Install dependencies
57
+ run: |
58
+ uv tool install rust-just
59
+ uv sync
60
+
61
+ - name: Run tests
62
+ run: just test
63
+
64
+ build:
65
+ needs: [validate, test]
66
+ runs-on: ubuntu-latest
67
+
68
+ steps:
69
+ - uses: actions/checkout@v4
70
+
71
+ - uses: astral-sh/setup-uv@v5
72
+
73
+ - name: Build distribution
74
+ run: uv build
75
+
76
+ - name: Store the distribution packages
77
+ uses: actions/upload-artifact@v4
78
+ with:
79
+ name: python-package-distributions
80
+ path: dist/
81
+
82
+ publish-to-pypi:
83
+ needs: build
84
+ runs-on: ubuntu-latest
85
+ environment:
86
+ name: pypi
87
+ url: https://pypi.org/project/qauvern/
88
+ permissions:
89
+ id-token: write
90
+
91
+ steps:
92
+ - name: Download all the dists
93
+ uses: actions/download-artifact@v4
94
+ with:
95
+ name: python-package-distributions
96
+ path: dist/
97
+
98
+ - name: Publish distribution to PyPI
99
+ uses: pypa/gh-action-pypi-publish@release/v1
100
+
101
+ create-release:
102
+ needs: [validate, publish-to-pypi]
103
+ runs-on: ubuntu-latest
104
+ permissions:
105
+ contents: write
106
+
107
+ steps:
108
+ - uses: actions/checkout@v4
109
+
110
+ - name: Create and push tag
111
+ run: |
112
+ VERSION="${{ needs.validate.outputs.version }}"
113
+ git config user.name "github-actions[bot]"
114
+ git config user.email "github-actions[bot]@users.noreply.github.com"
115
+ git tag -a "v$VERSION" -m "Release version $VERSION"
116
+ git push origin "v$VERSION"
117
+
118
+ - name: Extract changelog for this version
119
+ id: changelog
120
+ run: |
121
+ VERSION="${{ needs.validate.outputs.version }}"
122
+ python3 << 'EOF' > release-notes.md
123
+ import re
124
+ import os
125
+ VERSION = os.environ.get('VERSION', '')
126
+ with open('CHANGELOG.md', 'r') as f:
127
+ content = f.read()
128
+ pattern = rf'## \[{re.escape(VERSION)}\].*?\n(.*?)(?=\n## \[|$)'
129
+ match = re.search(pattern, content, re.DOTALL)
130
+ if match:
131
+ notes = match.group(1).strip()
132
+ print(notes)
133
+ else:
134
+ print(f'Release version {VERSION}')
135
+ EOF
136
+ cat release-notes.md
137
+ env:
138
+ VERSION: ${{ needs.validate.outputs.version }}
139
+
140
+ - name: Download distribution artifacts
141
+ uses: actions/download-artifact@v4
142
+ with:
143
+ name: python-package-distributions
144
+ path: dist/
145
+
146
+ - name: Create GitHub Release
147
+ env:
148
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149
+ VERSION: ${{ needs.validate.outputs.version }}
150
+ run: |
151
+ gh release create "v$VERSION" \
152
+ --title "Release v$VERSION" \
153
+ --notes-file release-notes.md \
154
+ dist/*
@@ -0,0 +1,80 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ env.sh
10
+ env.staging.sh
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ pip-wheel-metadata/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+
51
+ # Virtual environments
52
+ .venv/
53
+ venv/
54
+ env/
55
+ ENV/
56
+ env.bak/
57
+ venv.bak/
58
+
59
+ # IDEs
60
+ .vscode/
61
+ .idea/
62
+ *.swp
63
+ *.swo
64
+ *~
65
+ /.claude
66
+ !/.claude/commands
67
+
68
+ # OS
69
+ .DS_Store
70
+ Thumbs.db
71
+
72
+ # Project specific
73
+ config.yaml
74
+ *.log
75
+ openapi.json
76
+ /test_*.py
77
+ /test_*.sh
78
+ env.sh
79
+ env.staging.sh
80
+ /config/
@@ -0,0 +1,8 @@
1
+ repos:
2
+ - repo: https://github.com/Yelp/detect-secrets
3
+ rev: v1.5.0
4
+ hooks:
5
+ - id: detect-secrets
6
+ args: ['--baseline', '.secrets.baseline']
7
+ exclude: package.lock.json
8
+
@@ -0,0 +1,141 @@
1
+ {
2
+ "version": "1.5.0",
3
+ "plugins_used": [
4
+ {
5
+ "name": "ArtifactoryDetector"
6
+ },
7
+ {
8
+ "name": "AWSKeyDetector"
9
+ },
10
+ {
11
+ "name": "AzureStorageKeyDetector"
12
+ },
13
+ {
14
+ "name": "Base64HighEntropyString",
15
+ "limit": 4.5
16
+ },
17
+ {
18
+ "name": "BasicAuthDetector"
19
+ },
20
+ {
21
+ "name": "CloudantDetector"
22
+ },
23
+ {
24
+ "name": "DiscordBotTokenDetector"
25
+ },
26
+ {
27
+ "name": "GitHubTokenDetector"
28
+ },
29
+ {
30
+ "name": "GitLabTokenDetector"
31
+ },
32
+ {
33
+ "name": "HexHighEntropyString",
34
+ "limit": 3.0
35
+ },
36
+ {
37
+ "name": "IbmCloudIamDetector"
38
+ },
39
+ {
40
+ "name": "IbmCosHmacDetector"
41
+ },
42
+ {
43
+ "name": "IPPublicDetector"
44
+ },
45
+ {
46
+ "name": "JwtTokenDetector"
47
+ },
48
+ {
49
+ "name": "KeywordDetector",
50
+ "keyword_exclude": ""
51
+ },
52
+ {
53
+ "name": "MailchimpDetector"
54
+ },
55
+ {
56
+ "name": "NpmDetector"
57
+ },
58
+ {
59
+ "name": "OpenAIDetector"
60
+ },
61
+ {
62
+ "name": "PrivateKeyDetector"
63
+ },
64
+ {
65
+ "name": "PypiTokenDetector"
66
+ },
67
+ {
68
+ "name": "SendGridDetector"
69
+ },
70
+ {
71
+ "name": "SlackDetector"
72
+ },
73
+ {
74
+ "name": "SoftlayerDetector"
75
+ },
76
+ {
77
+ "name": "SquareOAuthDetector"
78
+ },
79
+ {
80
+ "name": "StripeDetector"
81
+ },
82
+ {
83
+ "name": "TelegramBotTokenDetector"
84
+ },
85
+ {
86
+ "name": "TwilioKeyDetector"
87
+ }
88
+ ],
89
+ "filters_used": [
90
+ {
91
+ "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
92
+ },
93
+ {
94
+ "path": "detect_secrets.filters.common.is_baseline_file",
95
+ "filename": ".secrets.baseline"
96
+ },
97
+ {
98
+ "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
99
+ "min_level": 2
100
+ },
101
+ {
102
+ "path": "detect_secrets.filters.heuristic.is_indirect_reference"
103
+ },
104
+ {
105
+ "path": "detect_secrets.filters.heuristic.is_likely_id_string"
106
+ },
107
+ {
108
+ "path": "detect_secrets.filters.heuristic.is_lock_file"
109
+ },
110
+ {
111
+ "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
112
+ },
113
+ {
114
+ "path": "detect_secrets.filters.heuristic.is_potential_uuid"
115
+ },
116
+ {
117
+ "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
118
+ },
119
+ {
120
+ "path": "detect_secrets.filters.heuristic.is_sequential_string"
121
+ },
122
+ {
123
+ "path": "detect_secrets.filters.heuristic.is_swagger_file"
124
+ },
125
+ {
126
+ "path": "detect_secrets.filters.heuristic.is_templated_secret"
127
+ }
128
+ ],
129
+ "results": {
130
+ "README.md": [
131
+ {
132
+ "type": "Secret Keyword",
133
+ "filename": "README.md",
134
+ "hashed_secret": "1042188d51afe0c0b267d5c98e5ac2f2c741b28f",
135
+ "is_verified": false,
136
+ "line_number": 113
137
+ }
138
+ ]
139
+ },
140
+ "generated_at": "2026-05-08T20:48:11Z"
141
+ }
@@ -0,0 +1,65 @@
1
+ # AGENTS.md
2
+
3
+ This is a Python CLI tool for optimizing IBM Quantum resource allocations across cloud accounts,
4
+ using fairness-based scheduling to rebalance QAU allocations among service instances. See
5
+ `Design.md` for full architecture, algorithm details, and API endpoint documentation.
6
+
7
+ ## Development Setup
8
+
9
+ Requires [Just](https://just.systems/man/en/) and [uv](https://docs.astral.sh/uv/).
10
+
11
+ ## Running Tests and Linting
12
+
13
+ Always run both before committing:
14
+
15
+ ```bash
16
+ just lint
17
+ just test
18
+ ```
19
+
20
+ To auto-format:
21
+
22
+ ```bash
23
+ just fmt
24
+ ```
25
+
26
+ ## Testing Rules
27
+
28
+ - **Never make live IBM Cloud API calls to test code.** Use `tests/mock_api.py`, which provides
29
+ a complete configurable mock of `IBMQuantumAPIClient`.
30
+ - Tests do not require real credentials or network access.
31
+ - The mock supports setting account allocation, per-instance allocations/limits, and usage across
32
+ five time windows (28d, 14d, 7d, 3d, 24h).
33
+
34
+ ## Committing
35
+
36
+ All commits must be DCO signed-off. Use `git commit -s`.
37
+
38
+ ## Key Prohibitions
39
+
40
+ - Do not run `qauvern optimize` against a real account to verify behavior — use `analyze`
41
+ with `--dry-run` or write a test with `mock_api.py`.
42
+ - Do not hardcode API keys or CRNs in source files.
43
+
44
+ ## Release Process
45
+
46
+ Releases are automated via GitHub Actions. To release a new version:
47
+
48
+ 1. Update the `version` in `pyproject.toml` (semver format: `X.Y.Z`)
49
+ 2. Add a `## [X.Y.Z] - YYYY-MM-DD` section to `CHANGELOG.md` with release notes
50
+ 3. Add the version link at the bottom of `CHANGELOG.md`
51
+ 4. Merge to `main`
52
+
53
+ The release workflow triggers on any push to `main` that changes `pyproject.toml`. It will:
54
+ - Validate the version format and check no tag already exists
55
+ - Run the full test suite across all supported Python versions
56
+ - Build the distribution
57
+ - Publish to PyPI via trusted publishing
58
+ - Create a git tag and GitHub Release with changelog notes
59
+
60
+ PyPI publishing uses trusted publishing (OIDC), configured via the `pypi` GitHub environment.
61
+
62
+ ## Maintenance Rule
63
+
64
+ After any design change (algorithm, API endpoints, data model, config format), update `Design.md`
65
+ so the project can be reconstructed from that document alone.
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to qauvern will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-05-13
9
+
10
+ ### Added
11
+
12
+ - Release workflow for automated PyPI publishing via GitHub Actions
13
+ - CI test matrix across Python 3.10, 3.11, 3.12, 3.13, 3.14
14
+ - Python 3.12, 3.13, 3.14 support
15
+
16
+ ## [0.1.0] - 2026-05-06
17
+
18
+ ### Added
19
+
20
+ - CLI with `analyze`, `optimize`, `configure`, `instances`, `show`, and `create` commands
21
+ - Activity-score-based allocation algorithm with exponential time weighting
22
+ - Rolling-window-aware `net_grants` for additive budget boosts
23
+ - IBM Cloud IAM authentication (API key via `IBMCLOUD_API_KEY`)
24
+ - Regional endpoint support (auto-extracted from CRN)
25
+ - Staging environment support (`--staging` flag)
26
+ - YAML configuration with `configure` command for auto-discovery
27
+ - Configurable minimum allocation floor
28
+
29
+ [0.2.0]: https://github.com/ibm/qauvern/releases/tag/v0.2.0
30
+ [0.1.0]: https://github.com/ibm/qauvern/releases/tag/v0.1.0
@@ -0,0 +1,69 @@
1
+ # Contributing
2
+
3
+ ## Pre-requisites
4
+
5
+ - [Just](https://just.systems/man/en/)
6
+ - [uv](https://docs.astral.sh/uv/getting-started/installation/)`
7
+
8
+ ## Signing Commits
9
+
10
+ All commits must be signed off under the [Developer Certificate of Origin](https://developercertificate.org/).
11
+ Use `git commit -s` (or `--signoff`) to append a `Signed-off-by` trailer to your commit message.
12
+ PRs without sign-off will fail the DCO check.
13
+
14
+ ```bash
15
+ git commit -s -m "Your commit message"
16
+ ```
17
+
18
+ To sign off existing commits, amend or rebase with `--signoff`:
19
+
20
+ ```bash
21
+ git commit --amend --signoff
22
+ git rebase --signoff main
23
+ ```
24
+
25
+ ## Pre-commit hooks (detect-secrets)
26
+
27
+ This project uses `detect-secrets` to prevent accidental commits of sensitive information.
28
+
29
+ ```bash
30
+ uvx pre-commit install
31
+
32
+ # First time only: create the secrets baseline
33
+ uvx detect-secrets scan > .secrets.baseline
34
+
35
+ # Verify hooks work
36
+ uvx pre-commit run --all-files
37
+ ```
38
+
39
+ If the hook detects a potential secret, either remove it from your code or add it to the baseline if it's a false positive:
40
+
41
+ ```bash
42
+ uvx detect-secrets scan --baseline .secrets.baseline
43
+ ```
44
+
45
+ ## Running Tests
46
+
47
+ ```bash
48
+ # Run all tests
49
+ just test
50
+
51
+ # Run with coverage
52
+ just test --cov=qauvern --cov-report=term-missing
53
+
54
+ # Run a specific file
55
+ just test tests/test_models.py
56
+
57
+ # Verbose output
58
+ just test -v
59
+ ```
60
+
61
+ ## Code Quality
62
+
63
+ ```bash
64
+ just fmt
65
+ ```
66
+
67
+ ```bash
68
+ just lint
69
+ ```