gitpulse-analytics 0.2.0__py3-none-any.whl

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.
gitpulse/storage.py ADDED
@@ -0,0 +1,61 @@
1
+ """SQLite persistence for collected traffic snapshots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import sqlite3
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ def default_db_path() -> Path:
14
+ configured = os.getenv("GITPULSE_DB")
15
+ if configured:
16
+ return Path(configured).expanduser()
17
+ return Path.home() / ".local" / "share" / "gitpulse" / "gitpulse.sqlite3"
18
+
19
+
20
+ class SnapshotStore:
21
+ """Store timestamped GitHub traffic responses locally."""
22
+
23
+ def __init__(self, path: Path | None = None) -> None:
24
+ self.path = path or default_db_path()
25
+ self.path.parent.mkdir(parents=True, exist_ok=True)
26
+ with self._connect() as conn:
27
+ conn.execute(
28
+ """CREATE TABLE IF NOT EXISTS snapshots (
29
+ id INTEGER PRIMARY KEY,
30
+ owner TEXT NOT NULL,
31
+ repo TEXT NOT NULL,
32
+ metric TEXT NOT NULL,
33
+ fetched_at TEXT NOT NULL,
34
+ payload TEXT NOT NULL
35
+ )"""
36
+ )
37
+ conn.execute(
38
+ "CREATE INDEX IF NOT EXISTS idx_snapshots_repo_metric "
39
+ "ON snapshots(owner, repo, metric, fetched_at)"
40
+ )
41
+
42
+ def _connect(self) -> sqlite3.Connection:
43
+ return sqlite3.connect(self.path)
44
+
45
+ def save(self, owner: str, repo: str, metric: str, payload: dict[str, Any]) -> None:
46
+ fetched_at = datetime.now(timezone.utc).isoformat(timespec="seconds")
47
+ with self._connect() as conn:
48
+ conn.execute(
49
+ "INSERT INTO snapshots(owner, repo, metric, fetched_at, payload) VALUES (?, ?, ?, ?, ?)",
50
+ (owner, repo, metric, fetched_at, json.dumps(payload)),
51
+ )
52
+
53
+ def history(self, owner: str, repo: str, metric: str, limit: int = 30) -> list[dict[str, Any]]:
54
+ with self._connect() as conn:
55
+ rows = conn.execute(
56
+ """SELECT fetched_at, payload FROM snapshots
57
+ WHERE owner = ? AND repo = ? AND metric = ?
58
+ ORDER BY fetched_at DESC, id DESC LIMIT ?""",
59
+ (owner, repo, metric, limit),
60
+ ).fetchall()
61
+ return [{"fetched_at": stamp, **json.loads(payload)} for stamp, payload in reversed(rows)]
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.5
2
+ Name: gitpulse-analytics
3
+ Version: 0.2.0
4
+ Summary: GitHub traffic, clones, referrers, and release downloads in your terminal, with org-wide HTML dashboards
5
+ Project-URL: Homepage, https://github.com/fullstackspectrum/gitpulse
6
+ Project-URL: Issues, https://github.com/fullstackspectrum/gitpulse/issues
7
+ Project-URL: Changelog, https://github.com/fullstackspectrum/gitpulse/releases
8
+ Author-email: FullStackSpectrum <dev@fullstackspectrum.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: analytics,cli,dashboard,downloads,github,releases,traffic
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Version Control :: Git
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: requests<3,>=2.32
26
+ Requires-Dist: rich<15,>=13.7
27
+ Requires-Dist: typer<1,>=0.12
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
30
+ Requires-Dist: ruff<1,>=0.6; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # GitPulse
34
+
35
+ A local Python CLI for GitHub repository traffic and release asset download stats. GitPulse can build a dashboard for a whole organization, and it keeps SQLite snapshots so you have history beyond GitHub's short traffic window.
36
+
37
+ > **What GitHub can tell you:** aggregate daily views and clones, unique counts, popular paths, referrers, and release-asset download totals. It does **not** identify individual visitors or people who cloned a repository. GitHub's traffic data is generally available for the most recent 14 days. Release asset downloads are not Homebrew formula install counts.
38
+
39
+ ![GitPulse organization dashboard](https://raw.githubusercontent.com/fullstackspectrum/gitpulse/main/docs/screenshots/dashboard-light.png)
40
+
41
+ ## Requirements
42
+
43
+ - Python 3.10+
44
+ - A GitHub token with access to the repositories you want to inspect. Classic tokens need the `repo` scope for private repositories. For fine-grained tokens, grant these repository permissions:
45
+
46
+ | Permission | Needed for |
47
+ | --- | --- |
48
+ | **Metadata: read** | All commands (always included) |
49
+ | **Contents: read** | `downloads`, and release downloads in `org` |
50
+ | **Administration: read** | `traffic`, `snapshot`, and traffic in `org` |
51
+
52
+ A fine-grained token only covers repositories owned by its resource owner, so org-owned repositories need a token whose resource owner is that org. Without a required permission, GitHub returns `Resource not accessible by personal access token`.
53
+
54
+ Traffic endpoints only work for repositories you have push access to. Public visibility alone does not grant access to traffic analytics.
55
+
56
+ ## Install
57
+
58
+ **Homebrew** (macOS and Linux):
59
+
60
+ ```sh
61
+ brew install fullstackspectrum/gitpulse/gitpulse
62
+ ```
63
+
64
+ **pipx or pip.** The package is called `gitpulse-analytics` on PyPI; the command it installs is `gitpulse`:
65
+
66
+ ```sh
67
+ pipx install gitpulse-analytics
68
+ # or
69
+ python -m pip install gitpulse-analytics
70
+ ```
71
+
72
+ **Docker.** Images for `linux/amd64` and `linux/arm64` are on [Docker Hub](https://hub.docker.com/r/fullstackspectrum/gitpulse). Mount a directory at `/data` to keep the HTML dashboard and snapshot history:
73
+
74
+ ```sh
75
+ docker run --rm -e GITHUB_TOKEN -v "$PWD:/data" fullstackspectrum/gitpulse org ORG
76
+ ```
77
+
78
+ The container runs as UID 1000. On Linux, if your user can't write the mounted directory as that UID, add `--user "$(id -u):$(id -g)"`.
79
+
80
+ **From source**, for development:
81
+
82
+ ```sh
83
+ python3 -m venv .venv
84
+ source .venv/bin/activate
85
+ python -m pip install --upgrade pip
86
+ python -m pip install -e '.[dev]'
87
+ ```
88
+
89
+ Check the install with `gitpulse --version`.
90
+
91
+ Set a token in your shell. `GITHUB_TOKEN` and `GH_TOKEN` are both supported. GitPulse never saves the token:
92
+
93
+ ```sh
94
+ export GITHUB_TOKEN="github_pat_..."
95
+ ```
96
+
97
+ If you use the [GitHub CLI](https://cli.github.com/) and are signed in with `gh auth login`, you can reuse its token instead:
98
+
99
+ ```sh
100
+ export GITHUB_TOKEN="$(gh auth token)"
101
+ ```
102
+
103
+ ## Organization dashboard
104
+
105
+ ```sh
106
+ gitpulse org ORG # terminal summary + gitpulse-ORG.html
107
+ gitpulse org ORG --open # also open the dashboard in your browser
108
+ gitpulse org ORG --save # also save views/clones snapshots for `history`
109
+ gitpulse org ORG --all --output reports/org.html # include forks and archived repos, custom path
110
+ ```
111
+
112
+ `org` works for an organization or a user account. It fetches every repository in parallel (`--workers`, default 8), then prints a summary in the terminal: totals, daily sparklines, a table of every repository, and the top referrers.
113
+
114
+ ![Terminal summary from gitpulse org](https://raw.githubusercontent.com/fullstackspectrum/gitpulse/main/docs/screenshots/terminal.png)
115
+
116
+ It also writes a self-contained HTML dashboard to `gitpulse-ORG.html` in the current directory, or to the path you pass with `--output`. The dashboard shows:
117
+
118
+ - Totals for views, unique visitors, clones, stars, forks, release downloads, and open issues and PRs
119
+ - Daily views and clones summed across repositories, with a hover readout for each day
120
+ - The most viewed and most starred repositories, a language breakdown, top referrers, and release downloads by repository
121
+ - A sortable, filterable table of every repository with a 14-day views sparkline
122
+
123
+ Styles, charts, and scripts are all inline, so it works offline (only the owner's avatar loads from GitHub). It follows your system's light or dark theme, and has a toggle to switch.
124
+
125
+ ![Dashboard in dark mode](https://raw.githubusercontent.com/fullstackspectrum/gitpulse/main/docs/screenshots/dashboard-dark.png)
126
+
127
+ By default, forks and archived repositories are skipped; `--all` includes them. When your token can't read traffic for a repository, its stars, forks, and downloads still appear, and the dashboard lists which repositories are missing traffic and why. Unique visitor and cloner counts are summed per repository, so one person who visits two repositories is counted twice.
128
+
129
+ *Screenshots show sample data.*
130
+
131
+ ## Repository commands
132
+
133
+ ```sh
134
+ gitpulse --help
135
+ gitpulse repos
136
+ gitpulse repos --all
137
+ gitpulse traffic OWNER/REPO
138
+ gitpulse traffic OWNER/REPO --metric clones
139
+ gitpulse traffic OWNER/REPO --metric referrers
140
+ gitpulse traffic OWNER/REPO --metric popular/paths
141
+ gitpulse traffic OWNER/REPO --metric views --save
142
+ gitpulse snapshot OWNER/REPO
143
+ gitpulse history OWNER/REPO --metric views
144
+ gitpulse history OWNER/REPO --metric clones
145
+ gitpulse downloads OWNER/REPO
146
+ ```
147
+
148
+ `repos` lists repositories accessible to the authenticated account and filters forks and archived repositories by default. `--all` includes them.
149
+
150
+ `traffic` displays GitHub's recent traffic response. `snapshot` fetches views and clones and saves both responses locally. Run it on a schedule (for example, daily) to build a history beyond GitHub's 14-day window. `history` displays saved snapshots; it does not backfill earlier data.
151
+
152
+ `downloads` lists release asset download counts. It does not measure Homebrew formula installation.
153
+
154
+ ## Local data and privacy
155
+
156
+ Snapshots are stored at `~/.local/share/gitpulse/gitpulse.sqlite3`. The database contains repository names and traffic responses, but no GitHub token. To use another location, set `GITPULSE_DB` to a SQLite file path.
157
+
158
+ Generated dashboards contain repository names, descriptions, and traffic numbers, including for private repositories. Treat them like any other internal report before sharing. `gitpulse-*.html` files are ignored by git.
159
+
160
+ ## Homebrew tap usage
161
+
162
+ For tap repositories, clone traffic and unique cloners can be useful directional signals, but they are not install totals. A tap clone, formula installation, and binary download are distinct events. GitHub does not expose an install-by-user report for formulae through these traffic endpoints.
163
+
164
+ ## Development
165
+
166
+ Run tests with `pytest` and lint with `ruff check .`.
167
+
168
+ ### Releasing
169
+
170
+ 1. Bump `__version__` in `src/gitpulse/__init__.py` and merge to `main`.
171
+ 2. Publish a GitHub release with the tag `vX.Y.Z` matching that version.
172
+
173
+ The `Release` workflow checks that the tag matches the version, then publishes to PyPI. After that it pushes the multi-arch Docker image and updates the formula in [homebrew-gitpulse](https://github.com/fullstackspectrum/homebrew-gitpulse). A release marked as a pre-release goes to PyPI and to Docker Hub under its exact version tag only, without moving `latest` or the Homebrew formula. The formula template lives in `packaging/homebrew/`; edit it there, not in the tap.
@@ -0,0 +1,11 @@
1
+ gitpulse/__init__.py,sha256=uaujMt6z0EBrnPgON1unjO2ySnoGKc_kNgbgC2oG5W4,64
2
+ gitpulse/cli.py,sha256=yR-NEx1DNm65vEFgnDw00JBWYVEX9U8vtn_7TaP8wJo,14849
3
+ gitpulse/github_api.py,sha256=1kg_leP2BvUcoKlPorSIilJ9IZijrSbroWl9PD0L6LY,7104
4
+ gitpulse/org.py,sha256=0rrKFhGvtUOowZmJQgI1XgCyoT94Bzhg-bNelacxRJA,6726
5
+ gitpulse/report.py,sha256=F_gnkh_hwXL5zrB09HKQoz-Brk84ecD-q0IT9TPGQBw,28466
6
+ gitpulse/storage.py,sha256=Sb6aMov3Kfr5e1YsSmB29jV7QuZLmeQGW5r8CEWJWqg,2319
7
+ gitpulse_analytics-0.2.0.dist-info/METADATA,sha256=CYj1ayuzEhBrDFQQqDpruCNmQpjEQ25wf3ajvEJJNfU,8847
8
+ gitpulse_analytics-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
9
+ gitpulse_analytics-0.2.0.dist-info/entry_points.txt,sha256=UYYnCPz2E3i16TG3b-nP_W9BealjSq4T-7CJYbsk_pk,46
10
+ gitpulse_analytics-0.2.0.dist-info/licenses/LICENSE,sha256=AiW-x_jnANoG7qKtS_2u_hZjG4Z3a2J_MJU2faxlhzs,1069
11
+ gitpulse_analytics-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gitpulse = gitpulse.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Colin Moynes
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.