ghinsight 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.
- ghinsight-0.2.0/.gitattributes +2 -0
- ghinsight-0.2.0/.github/dependabot.yml +9 -0
- ghinsight-0.2.0/.github/workflows/ci.yml +149 -0
- ghinsight-0.2.0/.github/workflows/release.yml +39 -0
- ghinsight-0.2.0/.gitignore +14 -0
- ghinsight-0.2.0/AGENTS.md +47 -0
- ghinsight-0.2.0/LICENSE +29 -0
- ghinsight-0.2.0/MAINTAINER.md +88 -0
- ghinsight-0.2.0/PKG-INFO +249 -0
- ghinsight-0.2.0/README.md +222 -0
- ghinsight-0.2.0/bin/recreate.sh +104 -0
- ghinsight-0.2.0/bin/verify.sh +28 -0
- ghinsight-0.2.0/docs/conf.py +58 -0
- ghinsight-0.2.0/docs/design.md +161 -0
- ghinsight-0.2.0/docs/index.md +53 -0
- ghinsight-0.2.0/docs/tutorials/activity.md +55 -0
- ghinsight-0.2.0/docs/tutorials/cli.md +132 -0
- ghinsight-0.2.0/docs/tutorials/python_analysis.py +188 -0
- ghinsight-0.2.0/examples/analysis.toml +25 -0
- ghinsight-0.2.0/pixi.lock +3606 -0
- ghinsight-0.2.0/pyproject.toml +93 -0
- ghinsight-0.2.0/src/ghinsight/__init__.py +32 -0
- ghinsight-0.2.0/src/ghinsight/actions/__init__.py +41 -0
- ghinsight-0.2.0/src/ghinsight/actions/analysis.py +201 -0
- ghinsight-0.2.0/src/ghinsight/actions/billing.py +195 -0
- ghinsight-0.2.0/src/ghinsight/actions/charts.py +439 -0
- ghinsight-0.2.0/src/ghinsight/actions/cli.py +283 -0
- ghinsight-0.2.0/src/ghinsight/actions/compare.py +323 -0
- ghinsight-0.2.0/src/ghinsight/actions/config.py +189 -0
- ghinsight-0.2.0/src/ghinsight/actions/csv_export.py +60 -0
- ghinsight-0.2.0/src/ghinsight/actions/dataset.py +271 -0
- ghinsight-0.2.0/src/ghinsight/actions/domain.py +360 -0
- ghinsight-0.2.0/src/ghinsight/actions/fetch.py +317 -0
- ghinsight-0.2.0/src/ghinsight/actions/metrics.py +287 -0
- ghinsight-0.2.0/src/ghinsight/actions/report.py +655 -0
- ghinsight-0.2.0/src/ghinsight/activity.py +589 -0
- ghinsight-0.2.0/src/ghinsight/cli.py +122 -0
- ghinsight-0.2.0/src/ghinsight/client.py +321 -0
- ghinsight-0.2.0/src/ghinsight/repos.py +104 -0
- ghinsight-0.2.0/tests/__init__.py +0 -0
- ghinsight-0.2.0/tests/conftest.py +21 -0
- ghinsight-0.2.0/tests/synthetic.py +139 -0
- ghinsight-0.2.0/tests/test_activity.py +259 -0
- ghinsight-0.2.0/tests/test_billing.py +202 -0
- ghinsight-0.2.0/tests/test_charts.py +34 -0
- ghinsight-0.2.0/tests/test_client.py +309 -0
- ghinsight-0.2.0/tests/test_domain.py +109 -0
- ghinsight-0.2.0/tests/test_fetch.py +52 -0
- ghinsight-0.2.0/tests/test_pipeline.py +259 -0
- ghinsight-0.2.0/uv.lock +2563 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- name: Install uv
|
|
14
|
+
uses: astral-sh/setup-uv@v7
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- name: Install dependencies
|
|
20
|
+
run: uv sync --group dev
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: uv run pytest
|
|
23
|
+
- name: Build package
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
docs:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v7
|
|
30
|
+
- name: Install uv
|
|
31
|
+
uses: astral-sh/setup-uv@v7
|
|
32
|
+
- name: Set up Python
|
|
33
|
+
uses: actions/setup-python@v7
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
- name: Install docs dependencies
|
|
37
|
+
run: uv sync --group docs
|
|
38
|
+
- name: Build docs
|
|
39
|
+
run: uv run --group docs sphinx-build -b html -W docs docs/_build/html
|
|
40
|
+
|
|
41
|
+
online-smoke:
|
|
42
|
+
# End-to-end test against *real* GitHub Actions data, using the built-in
|
|
43
|
+
# GITHUB_TOKEN to read this repository's own runs/jobs. This exercises the
|
|
44
|
+
# network fetch path that cannot run in an offline dev sandbox.
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
permissions:
|
|
47
|
+
contents: read
|
|
48
|
+
actions: read
|
|
49
|
+
env:
|
|
50
|
+
ORG: ${{ github.repository_owner }}
|
|
51
|
+
REPO: ${{ github.event.repository.name }}
|
|
52
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v7
|
|
55
|
+
- name: Install uv
|
|
56
|
+
uses: astral-sh/setup-uv@v7
|
|
57
|
+
- name: Set up Python
|
|
58
|
+
uses: actions/setup-python@v7
|
|
59
|
+
with:
|
|
60
|
+
python-version: "3.12"
|
|
61
|
+
- name: Install dependencies
|
|
62
|
+
run: uv sync --group dev
|
|
63
|
+
- name: Fetch real Actions data (single repo) and build report
|
|
64
|
+
run: |
|
|
65
|
+
uv run ghinsight actions fetch \
|
|
66
|
+
--org "$ORG" --repo "$REPO" --period last-year --cache-dir cache
|
|
67
|
+
uv run ghinsight actions report \
|
|
68
|
+
--org "$ORG" --cache-dir cache --output-dir reports
|
|
69
|
+
uv run ghinsight actions dashboard \
|
|
70
|
+
--org "$ORG" --cache-dir cache \
|
|
71
|
+
--output docs/index.html --summary docs/summary.json
|
|
72
|
+
- name: Assert the pipeline produced non-empty results
|
|
73
|
+
run: |
|
|
74
|
+
uv run python - <<'PY'
|
|
75
|
+
import json
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
|
|
78
|
+
from ghinsight.actions.dataset import ActionsDataset
|
|
79
|
+
|
|
80
|
+
org = __import__("os").environ["ORG"]
|
|
81
|
+
dataset = ActionsDataset.from_cache(org, Path("cache"))
|
|
82
|
+
assert not dataset.is_empty, "no completed jobs were fetched"
|
|
83
|
+
print(f"OK: {len(dataset.jobs)} jobs, {len(dataset.runs)} runs")
|
|
84
|
+
|
|
85
|
+
for csv in ["repositories.csv", "workflows.csv"]:
|
|
86
|
+
path = Path("reports/actions-usage-metrics") / csv
|
|
87
|
+
assert path.exists() and path.stat().st_size > 0, f"missing/empty {path}"
|
|
88
|
+
|
|
89
|
+
summary = json.loads(Path("docs/summary.json").read_text())
|
|
90
|
+
assert summary["raw_total_minutes_period"] > 0, "zero raw minutes"
|
|
91
|
+
print("summary:", summary["raw_total_minutes_period"], "raw minutes")
|
|
92
|
+
PY
|
|
93
|
+
- name: Upload generated dashboard
|
|
94
|
+
uses: actions/upload-artifact@v7
|
|
95
|
+
with:
|
|
96
|
+
name: dashboard
|
|
97
|
+
path: |
|
|
98
|
+
docs/index.html
|
|
99
|
+
docs/summary.json
|
|
100
|
+
reports/
|
|
101
|
+
|
|
102
|
+
online-full-account:
|
|
103
|
+
# Full-account end-to-end test across *all* repos of an account. The built-in
|
|
104
|
+
# GITHUB_TOKEN is scoped to a single repo, so this needs a Personal Access
|
|
105
|
+
# Token with read access to the target account (classic: repo + read:org;
|
|
106
|
+
# fine-grained: Actions read across repos). Configure repository secrets
|
|
107
|
+
# GH_PAT and (optionally) GH_ACCOUNT to enable it; the job skips cleanly when
|
|
108
|
+
# GH_PAT is absent, so forks and unconfigured repos stay green.
|
|
109
|
+
# Its output is deliberately not uploaded: this repository is public, so an
|
|
110
|
+
# artifact would expose the account's private repository names and usage.
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
env:
|
|
113
|
+
GH_PAT: ${{ secrets.GH_PAT }}
|
|
114
|
+
GH_ACCOUNT: ${{ secrets.GH_ACCOUNT || github.repository_owner }}
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v7
|
|
117
|
+
- name: Install uv
|
|
118
|
+
uses: astral-sh/setup-uv@v7
|
|
119
|
+
- name: Set up Python
|
|
120
|
+
uses: actions/setup-python@v7
|
|
121
|
+
with:
|
|
122
|
+
python-version: "3.12"
|
|
123
|
+
- name: Install dependencies
|
|
124
|
+
run: uv sync --group dev
|
|
125
|
+
- name: Fetch the whole account and build the report
|
|
126
|
+
run: |
|
|
127
|
+
if [ -z "$GH_PAT" ]; then
|
|
128
|
+
echo "GH_PAT secret not set — skipping full-account test."
|
|
129
|
+
exit 0
|
|
130
|
+
fi
|
|
131
|
+
export GITHUB_TOKEN="$GH_PAT"
|
|
132
|
+
uv run ghinsight actions fetch \
|
|
133
|
+
--org "$GH_ACCOUNT" --period last-3-months --cache-dir cache
|
|
134
|
+
uv run ghinsight actions report \
|
|
135
|
+
--org "$GH_ACCOUNT" --cache-dir cache --output-dir reports
|
|
136
|
+
uv run ghinsight actions dashboard \
|
|
137
|
+
--org "$GH_ACCOUNT" --cache-dir cache \
|
|
138
|
+
--output docs/index.html --summary docs/summary.json
|
|
139
|
+
uv run python - <<'PY'
|
|
140
|
+
import os
|
|
141
|
+
from pathlib import Path
|
|
142
|
+
|
|
143
|
+
from ghinsight.actions.dataset import ActionsDataset
|
|
144
|
+
|
|
145
|
+
dataset = ActionsDataset.from_cache(os.environ["GH_ACCOUNT"], Path("cache"))
|
|
146
|
+
repos = {job.repo for job in dataset.jobs}
|
|
147
|
+
print(f"OK: {len(dataset.jobs)} jobs across {len(repos)} repos")
|
|
148
|
+
assert not dataset.is_empty, "no jobs fetched across the account"
|
|
149
|
+
PY
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v7
|
|
12
|
+
- name: Install uv
|
|
13
|
+
uses: astral-sh/setup-uv@v7
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v7
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- name: Build distributions
|
|
19
|
+
run: uv build
|
|
20
|
+
- name: Upload distributions
|
|
21
|
+
uses: actions/upload-artifact@v7
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write
|
|
32
|
+
steps:
|
|
33
|
+
- name: Download distributions
|
|
34
|
+
uses: actions/download-artifact@v8
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
- name: Publish to PyPI
|
|
39
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Repository notes for future agents
|
|
2
|
+
|
|
3
|
+
- Use `uv` for PyPI packaging and release checks in this repository.
|
|
4
|
+
- The repository also defines a `pixi` environment in `pyproject.toml`; keep
|
|
5
|
+
package runtime dependencies mirrored in `[project.dependencies]` and
|
|
6
|
+
`[tool.pixi.dependencies]` so uv resolves from PyPI while pixi resolves from
|
|
7
|
+
conda-forge.
|
|
8
|
+
- Prefer commands of the form:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv run python ...
|
|
12
|
+
uv run ghinsight ...
|
|
13
|
+
pixi run check
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Do not run bare `python` for project tasks unless the user explicitly asks for
|
|
17
|
+
it.
|
|
18
|
+
- Keep this package organization-agnostic. Organization names, private data
|
|
19
|
+
paths, billing assumptions, and one-off repository quirks should live in a
|
|
20
|
+
consuming repository's config, not in this library.
|
|
21
|
+
|
|
22
|
+
## Architecture (post-refactor)
|
|
23
|
+
|
|
24
|
+
- Layout: the core (`client.py`, `repos.py`, `activity.py`, `cli.py`) needs
|
|
25
|
+
only httpx2 and typer. `ghinsight.actions` (the Actions pipeline and its
|
|
26
|
+
dashboard) needs the `actions` extra (pandas, plotly, rich); nothing in the
|
|
27
|
+
core may import it at module level. Keep `[project.dependencies]`, the
|
|
28
|
+
`actions` extra and `[tool.pixi.dependencies]` in step.
|
|
29
|
+
- All GitHub API access goes through `client.py` (httpx2; `GitHubClient`,
|
|
30
|
+
`GitHubError`, `resolve_token`) and `repos.py` (`RepoInfo`, account repo
|
|
31
|
+
listing). Keep these two modules light and stable: other packages (e.g.
|
|
32
|
+
repo-secret-scan) depend on them, so they must not import pandas/plotly.
|
|
33
|
+
- `ghinsight.actions` has three functional stages: `fetch` (network) → `dataset` (the canonical tidy
|
|
34
|
+
IR, `ActionsDataset`) → analysis (`metrics`, `analysis`, `charts`, `report`).
|
|
35
|
+
- The intermediate representation is a tidy per-job table, not GitHub's
|
|
36
|
+
aggregate CSVs. CSVs are an export (`csv_export`) for `compare` only.
|
|
37
|
+
- Prefer the functional style already in place: frozen dataclasses, pure
|
|
38
|
+
functions, type hints on signatures, declarative specs over imperative loops.
|
|
39
|
+
- Derived measures (billed minutes, durations, OS) are defined once, as
|
|
40
|
+
properties on the `Job`/`Run` records in `domain.py`.
|
|
41
|
+
|
|
42
|
+
## Testing & docs
|
|
43
|
+
|
|
44
|
+
- `uv run pytest` runs the offline suite against `tests/synthetic.py` (no token
|
|
45
|
+
or network needed). Add fixtures there rather than depending on a live org.
|
|
46
|
+
- `uv run --group docs sphinx-build -b html -W docs docs/_build/html` builds the
|
|
47
|
+
docs (AutoAPI + MyST-NB tutorials); CI builds with `-W`, so keep it warning-free.
|
ghinsight-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Kolen Cheung
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Maintainer Guide
|
|
2
|
+
|
|
3
|
+
This package is intended to be published to PyPI as `ghinsight`.
|
|
4
|
+
|
|
5
|
+
## Local Release Checks
|
|
6
|
+
|
|
7
|
+
Use `uv` from the repository root:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync
|
|
11
|
+
uv build
|
|
12
|
+
uv run twine check dist/*
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also check the pixi environment after dependency changes:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pixi lock
|
|
19
|
+
pixi run check
|
|
20
|
+
pixi run help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Inspect the contents before uploading:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tar -tzf dist/ghinsight-*.tar.gz | sort | head -50
|
|
27
|
+
unzip -l dist/ghinsight-*.whl | sort | head -50
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## First PyPI Release
|
|
31
|
+
|
|
32
|
+
1. Confirm the package name in `pyproject.toml` is available on PyPI. If
|
|
33
|
+
`ghinsight` is already taken, rename the project before the first
|
|
34
|
+
release.
|
|
35
|
+
2. Confirm `README.md`, `LICENSE`, project URLs, dependencies, and the console
|
|
36
|
+
script metadata are correct.
|
|
37
|
+
3. Create a PyPI account if needed and enable 2FA.
|
|
38
|
+
4. Configure a PyPI Trusted Publisher for GitHub Actions.
|
|
39
|
+
- Project name: `ghinsight`
|
|
40
|
+
- Owner: the GitHub repository owner
|
|
41
|
+
- Repository: the GitHub repository name
|
|
42
|
+
- Workflow filename: `release.yml`
|
|
43
|
+
- Environment: `pypi`
|
|
44
|
+
5. Push the repository to GitHub.
|
|
45
|
+
6. Create a GitHub Release for the version in `pyproject.toml`.
|
|
46
|
+
7. The `release.yml` workflow will build the sdist/wheel with `uv build` and
|
|
47
|
+
publish with `pypa/gh-action-pypi-publish`.
|
|
48
|
+
|
|
49
|
+
Trusted Publishing avoids storing a long-lived PyPI token in GitHub secrets.
|
|
50
|
+
The workflow needs `permissions: id-token: write`, and the `pypi` environment
|
|
51
|
+
should match the environment configured on PyPI.
|
|
52
|
+
|
|
53
|
+
## Ongoing Releases
|
|
54
|
+
|
|
55
|
+
1. Update `version` in `pyproject.toml`.
|
|
56
|
+
2. Update the README or changelog if user-facing behavior changed.
|
|
57
|
+
3. If runtime dependencies changed, update both `[project.dependencies]` and
|
|
58
|
+
`[tool.pixi.dependencies]` in `pyproject.toml`, then regenerate `uv.lock`
|
|
59
|
+
and `pixi.lock`.
|
|
60
|
+
4. Run local checks:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv sync
|
|
64
|
+
|
|
65
|
+
uv build
|
|
66
|
+
uv run twine check dist/*
|
|
67
|
+
pixi lock
|
|
68
|
+
pixi run check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
5. Commit the version/docs changes.
|
|
72
|
+
6. Tag or create a GitHub Release for the same version, for example `v0.1.1`.
|
|
73
|
+
7. Confirm the GitHub Actions release workflow publishes successfully.
|
|
74
|
+
8. Verify the package page and install path:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv tool install --force 'ghinsight[actions]'
|
|
78
|
+
ghinsight --help
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Useful References
|
|
82
|
+
|
|
83
|
+
- PyPI Trusted Publishing:
|
|
84
|
+
https://docs.pypi.org/trusted-publishers/
|
|
85
|
+
- Publishing with a Trusted Publisher:
|
|
86
|
+
https://docs.pypi.org/trusted-publishers/using-a-publisher/
|
|
87
|
+
- Python Packaging User Guide for GitHub Actions publishing:
|
|
88
|
+
https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
|
ghinsight-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ghinsight
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Reusable tools for GitHub analysis: Actions usage, issue and PR activity, and a small API client
|
|
5
|
+
Project-URL: Homepage, https://github.com/ickc/ghinsight
|
|
6
|
+
Project-URL: Repository, https://github.com/ickc/ghinsight
|
|
7
|
+
Project-URL: Issues, https://github.com/ickc/ghinsight/issues
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: actions,activity,analysis,dashboard,github,metrics
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
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: Topic :: Software Development :: Build Tools
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: httpx2>=2.16
|
|
21
|
+
Requires-Dist: typer>=0.12
|
|
22
|
+
Provides-Extra: actions
|
|
23
|
+
Requires-Dist: pandas>=2.0; extra == 'actions'
|
|
24
|
+
Requires-Dist: plotly>=6.0; extra == 'actions'
|
|
25
|
+
Requires-Dist: rich>=13.0; extra == 'actions'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ghinsight
|
|
29
|
+
|
|
30
|
+
*Pronounced "gin-sight": the h is silent.*
|
|
31
|
+
|
|
32
|
+
Reusable tools for analysing GitHub organisations and user accounts:
|
|
33
|
+
|
|
34
|
+
| Part | Install | What it does |
|
|
35
|
+
| ---- | ------- | ------------ |
|
|
36
|
+
| `ghinsight activity` | core | Markdown table of a user's issue and PR activity over a date range |
|
|
37
|
+
| `ghinsight actions` | `[actions]` extra | GitHub Actions usage and performance: cached fetch, metric CSVs, HTML dashboard |
|
|
38
|
+
| `ghinsight.client`, `ghinsight.repos` | core | A small GitHub REST/GraphQL client and repository listing, for other tools to build on |
|
|
39
|
+
|
|
40
|
+
The core depends only on [httpx2](https://github.com/pydantic/httpx2) and
|
|
41
|
+
[typer](https://typer.tiangolo.com); the `actions` extra adds pandas, plotly and
|
|
42
|
+
rich. Everything is organisation-agnostic: names, paths and billing assumptions
|
|
43
|
+
are arguments or config.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool install ghinsight # core: activity
|
|
49
|
+
uv tool install 'ghinsight[actions]' # also: actions
|
|
50
|
+
ghinsight --help
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For local development from a checkout:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv sync --group dev # includes the actions extra
|
|
57
|
+
uv run ghinsight --help
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A conda-forge `pixi` environment is also provided:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pixi run help
|
|
64
|
+
pixi run check
|
|
65
|
+
pixi run test
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Authentication
|
|
69
|
+
|
|
70
|
+
Every command needs a GitHub token. For `ghinsight actions`, it must be able to
|
|
71
|
+
read Actions metadata for the target account. Use either:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
export GH_TOKEN=... # or GITHUB_TOKEN
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
or login with the GitHub CLI (`gh auth login`): if neither variable is set, the
|
|
78
|
+
token comes from `gh auth token`, wherever `gh` stores it. `GITHUB_API_URL` (set
|
|
79
|
+
inside GitHub Actions) selects a GitHub Enterprise Server API.
|
|
80
|
+
|
|
81
|
+
## Issue and PR activity
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
ghinsight activity --repo my-org/some-repo --user octocat --from 2026-05-20 --to 2026-05-27
|
|
85
|
+
ghinsight activity --org my-org --user octocat --from 2026-09-16 --to 2026-09-24 --output week.md
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
With `--org`, every repository of the organisation (or user) is searched and
|
|
89
|
+
items are labelled `repo#123`. An issue or PR is listed when, within the range,
|
|
90
|
+
it was:
|
|
91
|
+
|
|
92
|
+
- opened by `--user`;
|
|
93
|
+
- closed or merged, and either authored or closed/merged by `--user`;
|
|
94
|
+
- authored by `--user` earlier and updated by them (comments, commits,
|
|
95
|
+
force-pushes, draft/ready changes, review requests), even if still open;
|
|
96
|
+
- reviewed by `--user` (someone else's PR; the type shows approved, changes
|
|
97
|
+
requested or reviewed).
|
|
98
|
+
|
|
99
|
+
`--authored-only` drops the last category and PRs closed/merged by `--user`
|
|
100
|
+
that they did not author. Date ranges with more than 1,000 search results are
|
|
101
|
+
split automatically; long ranges across a large organisation are slower, since
|
|
102
|
+
every item closed in the range is fetched to check who closed it.
|
|
103
|
+
|
|
104
|
+
## GitHub Actions usage and performance
|
|
105
|
+
|
|
106
|
+
`ghinsight actions` fetches workflow-run and job data from the GitHub REST API,
|
|
107
|
+
caches the raw JSON locally, parses it into a single typed **intermediate
|
|
108
|
+
representation**, and derives every report — GitHub-style metric tables,
|
|
109
|
+
headline KPIs, and a static HTML dashboard — as a pure projection of that
|
|
110
|
+
representation.
|
|
111
|
+
|
|
112
|
+
### Architecture
|
|
113
|
+
|
|
114
|
+
A three-stage, functional pipeline (see the [design docs](docs/design.md) for
|
|
115
|
+
the full rationale):
|
|
116
|
+
|
|
117
|
+
| Stage | Module | Output |
|
|
118
|
+
| ----- | ------ | ------ |
|
|
119
|
+
| 1. Fetch | `ghinsight.actions.fetch` | cached raw JSON (verbatim API responses) |
|
|
120
|
+
| 2. Dataset (the IR) | `ghinsight.actions.dataset` | `ActionsDataset` — a tidy per-job / per-run table |
|
|
121
|
+
| 3a. Metric tables | `ghinsight.actions.metrics` | usage / performance DataFrames |
|
|
122
|
+
| 3b. Insights | `ghinsight.actions.analysis` | `UsageSummary`, monthly time-series |
|
|
123
|
+
| 3c. Report | `ghinsight.actions.report` | HTML dashboard + JSON summary |
|
|
124
|
+
|
|
125
|
+
The canonical intermediate representation is a **tidy per-job table** (one row
|
|
126
|
+
per job, with all derived measures and dimensions), not GitHub's aggregate CSV
|
|
127
|
+
exports. Aggregate exports drop per-job timestamps, so they cannot support
|
|
128
|
+
time-series views; the tidy table can, and every other view derives from it.
|
|
129
|
+
GitHub-compatible CSVs are still produced as an *export*
|
|
130
|
+
(`ghinsight.actions.csv_export`) for diffing against GitHub's own metrics.
|
|
131
|
+
|
|
132
|
+
### Quick start
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
mkdir github-actions-report && cd github-actions-report
|
|
136
|
+
$EDITOR analysis.toml # see examples/analysis.toml
|
|
137
|
+
ghinsight actions recreate --config analysis.toml
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`recreate` runs fetch → CSV export → dashboard end to end. The dashboard is
|
|
141
|
+
written to `docs/index.html` and machine-readable metadata to
|
|
142
|
+
`docs/summary.json`. See the [CLI tutorial](docs/tutorials/cli.md) for the
|
|
143
|
+
step-by-step version and the [Python tutorial](docs/tutorials/python_analysis.py)
|
|
144
|
+
for the programmatic analysis API.
|
|
145
|
+
|
|
146
|
+
### Other commands
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Fetch, then export CSVs, separately:
|
|
150
|
+
ghinsight actions fetch --org example-org --period last-year --cache-dir cache
|
|
151
|
+
ghinsight actions report --org example-org --cache-dir cache --output-dir reports
|
|
152
|
+
|
|
153
|
+
# Print a table to the terminal:
|
|
154
|
+
ghinsight actions show usage repositories --org example-org --cache-dir cache
|
|
155
|
+
|
|
156
|
+
# Diff generated CSVs against a reference snapshot exported from GitHub:
|
|
157
|
+
ghinsight actions compare reference-data reports
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Config reference
|
|
161
|
+
|
|
162
|
+
See [examples/analysis.toml](examples/analysis.toml). `period` accepts
|
|
163
|
+
`last-year`, `last-6-months`, `last-3-months`, `last-month`, or
|
|
164
|
+
`YYYY-MM-DD..YYYY-MM-DD`.
|
|
165
|
+
|
|
166
|
+
`[analysis]`: `org`, `period`, `cache_dir`, `reports_dir`, `data_dir`,
|
|
167
|
+
`output_html`, `summary_json`, `billing` (optional; see below).
|
|
168
|
+
|
|
169
|
+
`[dashboard]`: `title`, `period_label`, `plan_minutes` (optional monthly cap),
|
|
170
|
+
`extra_insights_html`, `extra_limitations_html`.
|
|
171
|
+
|
|
172
|
+
`[os_multipliers]`: runtime OS billing multipliers for billed-equivalent
|
|
173
|
+
estimates (defaults: linux 1, windows 2, macos 10).
|
|
174
|
+
|
|
175
|
+
### Private-repository minutes and the billing report
|
|
176
|
+
|
|
177
|
+
A plan's included minutes only apply to private (and internal) repositories:
|
|
178
|
+
public repositories on standard GitHub-hosted runners are free. `fetch` records
|
|
179
|
+
each repository's visibility (`repo.json` in the cache), and the dashboard's
|
|
180
|
+
minute charts get an **All repositories / Private repositories only** toggle,
|
|
181
|
+
with private-only KPIs and a `private_only` block in `summary.json`.
|
|
182
|
+
Visibility is as of the fetch, so a repository whose visibility changed during
|
|
183
|
+
the period is classified by its current visibility throughout. Caches written
|
|
184
|
+
before visibility was recorded render without the toggle; re-run `fetch` to add
|
|
185
|
+
it.
|
|
186
|
+
|
|
187
|
+
Optionally, `fetch --billing` (or `billing = true` in the config) also caches
|
|
188
|
+
GitHub's [billing usage report](https://docs.github.com/en/rest/billing/usage),
|
|
189
|
+
which gives the minutes GitHub actually billed and the net charge. The dashboard
|
|
190
|
+
then adds a billed-usage section and a `billing_report` block in `summary.json`.
|
|
191
|
+
The API appears to admit **organisation owners only**: billing managers get a
|
|
192
|
+
404 even with a classic token carrying `admin:org` (for a user account, the
|
|
193
|
+
token needs the `user` scope). Without access the fetch logs a warning and
|
|
194
|
+
caches nothing, and the dashboard notes that all figures are estimates.
|
|
195
|
+
|
|
196
|
+
Billing managers can instead download the usage report CSV from the
|
|
197
|
+
organisation's billing usage page (the *summarized* report covers up to a year
|
|
198
|
+
and breaks usage down by repository) and import it:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
ghinsight actions import-billing usage-report.csv --org example-org --cache-dir cache
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This writes the same cache files as the API fetch, which then reuses them.
|
|
205
|
+
|
|
206
|
+
The billing report appears to list only usage that counted towards the
|
|
207
|
+
quota: in testing, a public repository's usage while public was not billed at
|
|
208
|
+
all, while a repository billed for months was made public only afterwards.
|
|
209
|
+
The dashboard therefore shows billed minutes as they are, with no visibility
|
|
210
|
+
filter; being as-billed, they also reflect each repository's visibility *at the
|
|
211
|
+
time*, which the private-only estimates cannot.
|
|
212
|
+
|
|
213
|
+
## Using the client from Python
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from ghinsight import GitHubClient, list_repos
|
|
217
|
+
|
|
218
|
+
with GitHubClient() as gh: # token from GH_TOKEN, GITHUB_TOKEN or gh
|
|
219
|
+
repos = list_repos("my-org", gh) # RepoInfo: slug, visibility, fork, archived, ...
|
|
220
|
+
runs = gh.paginate("/repos/my-org/some-repo/actions/runs", response_key="workflow_runs")
|
|
221
|
+
viewer = gh.graphql("{ viewer { login } }")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`GitHubClient` waits out primary and secondary rate limits (up to
|
|
225
|
+
`max_rate_limit_wait` seconds), retries server and network errors with backoff,
|
|
226
|
+
and raises `GitHubError` (with `.status`) otherwise. For the authenticated user,
|
|
227
|
+
`list_repos` includes private repositories, which `/users/{user}/repos` omits.
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
uv sync --group dev
|
|
233
|
+
uv run pytest # offline test suite (synthetic fixtures)
|
|
234
|
+
uv build # build the wheel/sdist
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The test suite runs fully offline against a synthetic cache (`tests/synthetic.py`),
|
|
238
|
+
so no token or network is required.
|
|
239
|
+
|
|
240
|
+
## Documentation
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
uv sync --group docs
|
|
244
|
+
uv run --group docs sphinx-build -b html docs docs/_build/html
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The site uses Sphinx with AutoAPI (API reference from source), a design page,
|
|
248
|
+
and CLI + Python tutorials (the Python tutorial is an executable jupytext
|
|
249
|
+
notebook rendered via MyST-NB).
|