gitlytics 0.1.5__tar.gz → 0.1.6__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.
Files changed (48) hide show
  1. {gitlytics-0.1.5 → gitlytics-0.1.6}/LICENSE +19 -5
  2. {gitlytics-0.1.5 → gitlytics-0.1.6}/PKG-INFO +43 -23
  3. {gitlytics-0.1.5 → gitlytics-0.1.6}/README.md +42 -21
  4. {gitlytics-0.1.5 → gitlytics-0.1.6}/pyproject.toml +1 -2
  5. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/__init__.py +28 -13
  6. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/api.py +72 -31
  7. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/automation.py +14 -11
  8. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/cli.py +7 -3
  9. gitlytics-0.1.6/src/gitlytics/core.py +491 -0
  10. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/process.py +77 -49
  11. gitlytics-0.1.6/src/gitlytics/static/android-chrome-192x192.png +0 -0
  12. gitlytics-0.1.6/src/gitlytics/static/android-chrome-512x512.png +0 -0
  13. gitlytics-0.1.6/src/gitlytics/static/apple-touch-icon.png +0 -0
  14. gitlytics-0.1.6/src/gitlytics/static/assets/html2canvas-pro.esm-C9_j7xg5.js +10 -0
  15. gitlytics-0.1.6/src/gitlytics/static/assets/html2canvas.esm-QH1iLAAe.js +22 -0
  16. gitlytics-0.1.6/src/gitlytics/static/assets/index-D6vJCUrl.js +504 -0
  17. gitlytics-0.1.6/src/gitlytics/static/assets/index-hl2LPOqz.css +1 -0
  18. gitlytics-0.1.6/src/gitlytics/static/assets/index.es-BM2mGRzK.js +18 -0
  19. gitlytics-0.1.6/src/gitlytics/static/assets/jspdf.es.min-cihQsb1K.js +170 -0
  20. gitlytics-0.1.6/src/gitlytics/static/assets/purify.es-Csrj9YNg.js +3 -0
  21. gitlytics-0.1.6/src/gitlytics/static/favicon-16x16.png +0 -0
  22. gitlytics-0.1.6/src/gitlytics/static/favicon-32x32.png +0 -0
  23. gitlytics-0.1.6/src/gitlytics/static/favicon.ico +0 -0
  24. gitlytics-0.1.6/src/gitlytics/static/gitlytics-logo.png +0 -0
  25. gitlytics-0.1.6/src/gitlytics/static/index.html +19 -0
  26. gitlytics-0.1.6/src/gitlytics/static/octocat.png +0 -0
  27. gitlytics-0.1.6/src/gitlytics/static/robots.txt +5 -0
  28. gitlytics-0.1.6/src/gitlytics/static/sitemap.xml +13 -0
  29. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics.egg-info/PKG-INFO +43 -23
  30. gitlytics-0.1.6/src/gitlytics.egg-info/SOURCES.txt +40 -0
  31. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics.egg-info/requires.txt +0 -1
  32. {gitlytics-0.1.5 → gitlytics-0.1.6}/tests/test_api.py +6 -3
  33. {gitlytics-0.1.5 → gitlytics-0.1.6}/tests/test_core.py +1 -1
  34. {gitlytics-0.1.5 → gitlytics-0.1.6}/tests/test_process.py +3 -0
  35. gitlytics-0.1.6/tests/test_username.py +335 -0
  36. gitlytics-0.1.5/src/gitlytics/core.py +0 -288
  37. gitlytics-0.1.5/src/gitlytics/static/assets/index-CJlQrbYd.js +0 -44
  38. gitlytics-0.1.5/src/gitlytics/static/assets/index-Dpkz0yGK.css +0 -2
  39. gitlytics-0.1.5/src/gitlytics/static/index.html +0 -14
  40. gitlytics-0.1.5/src/gitlytics/static/logo.png +0 -0
  41. gitlytics-0.1.5/src/gitlytics.egg-info/SOURCES.txt +0 -25
  42. {gitlytics-0.1.5 → gitlytics-0.1.6}/setup.cfg +0 -0
  43. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics/__main__.py +0 -0
  44. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics.egg-info/dependency_links.txt +0 -0
  45. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics.egg-info/entry_points.txt +0 -0
  46. {gitlytics-0.1.5 → gitlytics-0.1.6}/src/gitlytics.egg-info/top_level.txt +0 -0
  47. {gitlytics-0.1.5 → gitlytics-0.1.6}/tests/test_automation.py +0 -0
  48. {gitlytics-0.1.5 → gitlytics-0.1.6}/tests/test_cli.py +0 -0
@@ -194,8 +194,22 @@
194
194
 
195
195
  http://www.apache.org/licenses/LICENSE-2.0
196
196
 
197
- Unless required by applicable law or agreed to in writing, software
198
- distributed under the License is distributed on an "AS IS" BASIS,
199
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
- See the License for the specific language governing permissions and
201
- limitations under the License.
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
203
+ -------------------------------------------------------------------------
204
+
205
+ "Commons Clause" License Condition v1.0
206
+
207
+ The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
208
+
209
+ Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
210
+
211
+ For purposes of the foregoing, "Sell" means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
212
+
213
+ Software: Gitlytics
214
+ License: Apache 2.0
215
+ Licensor: Ameya Sanjay Chopade
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlytics
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Monitor and automate your GitHub repository traffic analytics.
5
5
  Author-email: Ameya Chopade <ameyaccod171@gmail.com>
6
6
  License: Apache-2.0
@@ -27,15 +27,14 @@ Provides-Extra: dev
27
27
  Requires-Dist: pytest>=8.0.0; extra == "dev"
28
28
  Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
29
29
  Requires-Dist: httpx>=0.27.0; extra == "dev"
30
- Requires-Dist: Faker>=20.0.0; extra == "dev"
31
30
  Requires-Dist: anyio[trio]>=4.0.0; extra == "dev"
32
31
  Dynamic: license-file
33
32
 
34
33
  <div align="center">
35
34
 
36
- <img src="./assets/logo.png" alt="Gitlytics Logo" width="150" />
35
+ <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/logo.png" alt="Gitlytics Logo" width="150" />
37
36
 
38
- # <span style="color: #F05032">Git</span>lytics
37
+ # Gitlytics
39
38
  ### GitHub Traffic Analytics & Automation
40
39
 
41
40
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
@@ -48,6 +47,8 @@ Dynamic: license-file
48
47
  [![Live](https://img.shields.io/badge/Live%20Demo-dashboard.gitlytics.dev-success)](https://dashboard.gitlytics.dev)
49
48
  [![Docs](https://img.shields.io/badge/Docs-docs.gitlytics.dev-success)](https://docs.gitlytics.dev)
50
49
 
50
+ <br/>Please consider giving this project a ⭐ if you find it helpful! <br/>
51
+
51
52
  **Beautiful GitHub traffic analytics for all your repositories — public and private.** <br/> Track views, clones, referrers, and popular paths indefinitely.
52
53
 
53
54
  ✨ **[Try the live dashboard at dashboard.gitlytics.dev](https://dashboard.gitlytics.dev)** ✨
@@ -57,22 +58,16 @@ Dynamic: license-file
57
58
 
58
59
  > **🐍 Native Python API**
59
60
  >
60
- > You can import Gitlytics natively into your own Python applications to build custom integrations, run custom cron workflows, or serve the dashboard programmatically on your own cloud servers.
61
+ > You can import Gitlytics natively into your own Python applications to fetch live repository data like views, clones, stars, and referrers. Build custom integrations, run custom cron workflows, or serve the dashboard programmatically on your own cloud servers.
61
62
  >
62
63
  > 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
63
64
 
64
- Please consider giving this project a ⭐ if you find it helpful!
65
-
66
65
  </div>
67
66
 
68
67
  ---
69
68
 
70
69
  <div align="center">
71
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_1.png" width="49%" />
72
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_2.png" width="49%" />
73
- </div>
74
- <div align="center">
75
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_3.png" width="98.5%" />
70
+ <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_1.png" width="100%" />
76
71
  </div>
77
72
 
78
73
  ---
@@ -101,7 +96,7 @@ Please consider giving this project a ⭐ if you find it helpful!
101
96
 
102
97
  The full Gitlytics ecosystem spans across a few repositories. If you are looking for the live web dashboard or the automation cron job, check out the links below:
103
98
 
104
- - **[<span style="color: #F05032">Git</span>lytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production homepage, React Dashboard, and VitePress documentation site.
99
+ - **[Gitlytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production landing page, React Dashboard, and React Documentation site.
105
100
  - ⚙️ **[Gitlytics Automation](https://github.com/ameyac11/gitlytics-github-traffic-automation)**: The GitHub Action companion tool that automates fetching and saving to defeat GitHub's 14-day traffic limit.
106
101
 
107
102
  ---
@@ -147,6 +142,9 @@ Gitlytics is powered by 3 massive command-line tools. You can run them anywhere
147
142
  Fetch your live 14-day traffic and print a beautiful ASCII table directly in your console.
148
143
  ```bash
149
144
  gitlytics fetch --token ghp_your_token_here --print-table
145
+
146
+ # Fetch specific metrics only (e.g., views and clones)
147
+ gitlytics fetch --token ghp_your_token_here --print-table --metrics views clones
150
148
  ```
151
149
 
152
150
  ### 2️⃣ `gitlytics sync` (Background Database Cron)
@@ -155,6 +153,9 @@ Tired of losing data? Use `sync` to permanently append today's traffic to a CSV
155
153
  # Sync once
156
154
  gitlytics sync --token ghp_your_token --data-dir ./data
157
155
 
156
+ # Sync specific metrics only
157
+ gitlytics sync --token ghp_your_token --data-dir ./data --metrics views clones
158
+
158
159
  # Run permanently in the background as a cron job (runs at 11:00 PM every day)
159
160
  gitlytics sync --token ghp_your_token --data-dir ./data --schedule-cron "0 23 * * *"
160
161
  ```
@@ -212,6 +213,7 @@ gitlytics.fetch_traffic(
212
213
  | `print_table` | `bool` | `False` | If `True`, formats and prints a detailed ASCII traffic table to the console. |
213
214
  | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"` (chart-ready nested dict), or `"summary"` (per-repo totals dict). |
214
215
  | `save_file` | `str` | `None` | Optional. File path where the fetched data will be saved (CSV or JSON). |
216
+ | `metrics` | `list` | `None` | Optional. List of metrics to fetch (e.g., `["views", "clones"]`). |
215
217
 
216
218
  ---
217
219
 
@@ -248,6 +250,7 @@ gitlytics.sync(
248
250
  | `schedule_cron` | `str` | `None` | Optional cron expression (e.g., `"*/15 * * * *"`). If set, runs an infinite scheduler loop. |
249
251
  | `export_json` | `str` | `None` | Optional. Path to compile and export a consolidated history JSON for the frontend. |
250
252
  | `export_public_only` | `bool` | `True` | Security firewall: if `True`, strips private repository data from the compiled `export_json`. |
253
+ | `metrics` | `list` | `None` | Optional. List of metrics to sync (e.g., `["views", "clones"]`). |
251
254
 
252
255
  ---
253
256
 
@@ -279,16 +282,33 @@ gitlytics.serve_dashboard(
279
282
 
280
283
  ## 📊 CSV Output Columns
281
284
 
282
- When you sync data, the local CSV databases track 13 detailed metrics:
283
-
284
- | Column | Description | Column | Description |
285
- |---|---|---|---|
286
- | `repository` | Full repo name (`user/repo`) | `stars` | Current star count |
287
- | `is_private` | `True` / `False` | `forks` | Current fork count |
288
- | `views` | Page views today | `unique_visitors` | Unique visitors today |
289
- | `clones` | Clone count today | `unique_cloners` | Unique cloners today |
290
- | `top_referrer` | Highest-traffic referral source | `top_referrer_views` | Views from top referrer |
291
- | `top_path` | Most visited path | `top_path_views` | Views for top path |
285
+ When you sync data, the local CSV databases track 23 detailed metrics by default. If you customize the metrics using the `--metrics` CLI flag or `metrics` Python parameter, the CSV columns will dynamically include only the columns corresponding to your selection (along with the default `date`, `repository`, and `is_private` identification columns).
286
+
287
+ | Column | Type | Description |
288
+ |---|---|---|
289
+ | `date` | `str` | ISO date (`YYYY-MM-DD`) for this day's traffic snapshot. |
290
+ | `repository` | `str` | Full GitHub repository name (`owner/repo`). |
291
+ | `is_private` | `bool` | `True` if repository is private, `False` otherwise. |
292
+ | `views` | `int` | Total page views on this day. |
293
+ | `unique_visitors` | `int` | Unique visitors on this day. |
294
+ | `clones` | `int` | Total git clone operations on this day. |
295
+ | `unique_cloners` | `int` | Unique clone clients on this day. |
296
+ | `stars` | `int` | Current total star count snapshot. |
297
+ | `forks` | `int` | Current total fork count snapshot. |
298
+ | `language` | `str` | Primary programming language of the repository. |
299
+ | `topics` | `str` | JSON array containing repository tags/topics. |
300
+ | `watchers_count` | `int` | Total watchers of the repository. |
301
+ | `pushed_at` | `str` | Last push ISO timestamp. |
302
+ | `created_at` | `str` | Repository creation ISO timestamp. |
303
+ | `open_issues_count` | `int` | Total number of open issues. |
304
+ | `top_referrer` | `str` | Top external traffic referral source (14-day rolling window). |
305
+ | `top_referrer_views` | `int` | Views sent by the top referrer. |
306
+ | `top_referrer_uniques` | `int` | Uniques sent by the top referrer. |
307
+ | `_raw_referrers` | `str` | Raw JSON array of all referral sources. |
308
+ | `top_path` | `str` | Most visited repository file path (14-day rolling window). |
309
+ | `top_path_views` | `int` | Views for the top path. |
310
+ | `top_path_uniques` | `int` | Uniques for the top path. |
311
+ | `_raw_paths` | `str` | Raw JSON array of all popular paths. |
292
312
 
293
313
  ---
294
314
 
@@ -1,8 +1,8 @@
1
1
  <div align="center">
2
2
 
3
- <img src="./assets/logo.png" alt="Gitlytics Logo" width="150" />
3
+ <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/logo.png" alt="Gitlytics Logo" width="150" />
4
4
 
5
- # <span style="color: #F05032">Git</span>lytics
5
+ # Gitlytics
6
6
  ### GitHub Traffic Analytics & Automation
7
7
 
8
8
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
@@ -15,6 +15,8 @@
15
15
  [![Live](https://img.shields.io/badge/Live%20Demo-dashboard.gitlytics.dev-success)](https://dashboard.gitlytics.dev)
16
16
  [![Docs](https://img.shields.io/badge/Docs-docs.gitlytics.dev-success)](https://docs.gitlytics.dev)
17
17
 
18
+ <br/>Please consider giving this project a ⭐ if you find it helpful! <br/>
19
+
18
20
  **Beautiful GitHub traffic analytics for all your repositories — public and private.** <br/> Track views, clones, referrers, and popular paths indefinitely.
19
21
 
20
22
  ✨ **[Try the live dashboard at dashboard.gitlytics.dev](https://dashboard.gitlytics.dev)** ✨
@@ -24,22 +26,16 @@
24
26
 
25
27
  > **🐍 Native Python API**
26
28
  >
27
- > You can import Gitlytics natively into your own Python applications to build custom integrations, run custom cron workflows, or serve the dashboard programmatically on your own cloud servers.
29
+ > You can import Gitlytics natively into your own Python applications to fetch live repository data like views, clones, stars, and referrers. Build custom integrations, run custom cron workflows, or serve the dashboard programmatically on your own cloud servers.
28
30
  >
29
31
  > 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
30
32
 
31
- Please consider giving this project a ⭐ if you find it helpful!
32
-
33
33
  </div>
34
34
 
35
35
  ---
36
36
 
37
37
  <div align="center">
38
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_1.png" width="49%" />
39
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_2.png" width="49%" />
40
- </div>
41
- <div align="center">
42
- <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_3.png" width="98.5%" />
38
+ <img src="https://raw.githubusercontent.com/ameyac11/gitlytics/main/assets/gitlytics_thumbnail_1.png" width="100%" />
43
39
  </div>
44
40
 
45
41
  ---
@@ -68,7 +64,7 @@ Please consider giving this project a ⭐ if you find it helpful!
68
64
 
69
65
  The full Gitlytics ecosystem spans across a few repositories. If you are looking for the live web dashboard or the automation cron job, check out the links below:
70
66
 
71
- - **[<span style="color: #F05032">Git</span>lytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production homepage, React Dashboard, and VitePress documentation site.
67
+ - **[Gitlytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production landing page, React Dashboard, and React Documentation site.
72
68
  - ⚙️ **[Gitlytics Automation](https://github.com/ameyac11/gitlytics-github-traffic-automation)**: The GitHub Action companion tool that automates fetching and saving to defeat GitHub's 14-day traffic limit.
73
69
 
74
70
  ---
@@ -114,6 +110,9 @@ Gitlytics is powered by 3 massive command-line tools. You can run them anywhere
114
110
  Fetch your live 14-day traffic and print a beautiful ASCII table directly in your console.
115
111
  ```bash
116
112
  gitlytics fetch --token ghp_your_token_here --print-table
113
+
114
+ # Fetch specific metrics only (e.g., views and clones)
115
+ gitlytics fetch --token ghp_your_token_here --print-table --metrics views clones
117
116
  ```
118
117
 
119
118
  ### 2️⃣ `gitlytics sync` (Background Database Cron)
@@ -122,6 +121,9 @@ Tired of losing data? Use `sync` to permanently append today's traffic to a CSV
122
121
  # Sync once
123
122
  gitlytics sync --token ghp_your_token --data-dir ./data
124
123
 
124
+ # Sync specific metrics only
125
+ gitlytics sync --token ghp_your_token --data-dir ./data --metrics views clones
126
+
125
127
  # Run permanently in the background as a cron job (runs at 11:00 PM every day)
126
128
  gitlytics sync --token ghp_your_token --data-dir ./data --schedule-cron "0 23 * * *"
127
129
  ```
@@ -179,6 +181,7 @@ gitlytics.fetch_traffic(
179
181
  | `print_table` | `bool` | `False` | If `True`, formats and prints a detailed ASCII traffic table to the console. |
180
182
  | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"` (chart-ready nested dict), or `"summary"` (per-repo totals dict). |
181
183
  | `save_file` | `str` | `None` | Optional. File path where the fetched data will be saved (CSV or JSON). |
184
+ | `metrics` | `list` | `None` | Optional. List of metrics to fetch (e.g., `["views", "clones"]`). |
182
185
 
183
186
  ---
184
187
 
@@ -215,6 +218,7 @@ gitlytics.sync(
215
218
  | `schedule_cron` | `str` | `None` | Optional cron expression (e.g., `"*/15 * * * *"`). If set, runs an infinite scheduler loop. |
216
219
  | `export_json` | `str` | `None` | Optional. Path to compile and export a consolidated history JSON for the frontend. |
217
220
  | `export_public_only` | `bool` | `True` | Security firewall: if `True`, strips private repository data from the compiled `export_json`. |
221
+ | `metrics` | `list` | `None` | Optional. List of metrics to sync (e.g., `["views", "clones"]`). |
218
222
 
219
223
  ---
220
224
 
@@ -246,16 +250,33 @@ gitlytics.serve_dashboard(
246
250
 
247
251
  ## 📊 CSV Output Columns
248
252
 
249
- When you sync data, the local CSV databases track 13 detailed metrics:
250
-
251
- | Column | Description | Column | Description |
252
- |---|---|---|---|
253
- | `repository` | Full repo name (`user/repo`) | `stars` | Current star count |
254
- | `is_private` | `True` / `False` | `forks` | Current fork count |
255
- | `views` | Page views today | `unique_visitors` | Unique visitors today |
256
- | `clones` | Clone count today | `unique_cloners` | Unique cloners today |
257
- | `top_referrer` | Highest-traffic referral source | `top_referrer_views` | Views from top referrer |
258
- | `top_path` | Most visited path | `top_path_views` | Views for top path |
253
+ When you sync data, the local CSV databases track 23 detailed metrics by default. If you customize the metrics using the `--metrics` CLI flag or `metrics` Python parameter, the CSV columns will dynamically include only the columns corresponding to your selection (along with the default `date`, `repository`, and `is_private` identification columns).
254
+
255
+ | Column | Type | Description |
256
+ |---|---|---|
257
+ | `date` | `str` | ISO date (`YYYY-MM-DD`) for this day's traffic snapshot. |
258
+ | `repository` | `str` | Full GitHub repository name (`owner/repo`). |
259
+ | `is_private` | `bool` | `True` if repository is private, `False` otherwise. |
260
+ | `views` | `int` | Total page views on this day. |
261
+ | `unique_visitors` | `int` | Unique visitors on this day. |
262
+ | `clones` | `int` | Total git clone operations on this day. |
263
+ | `unique_cloners` | `int` | Unique clone clients on this day. |
264
+ | `stars` | `int` | Current total star count snapshot. |
265
+ | `forks` | `int` | Current total fork count snapshot. |
266
+ | `language` | `str` | Primary programming language of the repository. |
267
+ | `topics` | `str` | JSON array containing repository tags/topics. |
268
+ | `watchers_count` | `int` | Total watchers of the repository. |
269
+ | `pushed_at` | `str` | Last push ISO timestamp. |
270
+ | `created_at` | `str` | Repository creation ISO timestamp. |
271
+ | `open_issues_count` | `int` | Total number of open issues. |
272
+ | `top_referrer` | `str` | Top external traffic referral source (14-day rolling window). |
273
+ | `top_referrer_views` | `int` | Views sent by the top referrer. |
274
+ | `top_referrer_uniques` | `int` | Uniques sent by the top referrer. |
275
+ | `_raw_referrers` | `str` | Raw JSON array of all referral sources. |
276
+ | `top_path` | `str` | Most visited repository file path (14-day rolling window). |
277
+ | `top_path_views` | `int` | Views for the top path. |
278
+ | `top_path_uniques` | `int` | Uniques for the top path. |
279
+ | `_raw_paths` | `str` | Raw JSON array of all popular paths. |
259
280
 
260
281
  ---
261
282
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitlytics"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "Monitor and automate your GitHub repository traffic analytics."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -51,7 +51,6 @@ dev = [
51
51
  "pytest>=8.0.0",
52
52
  "pytest-cov>=5.0.0",
53
53
  "httpx>=0.27.0", # required by FastAPI TestClient (used in test_api.py)
54
- "Faker>=20.0.0", # used by pytest-faker plugin for generating test data
55
54
  "anyio[trio]>=4.0.0" # async test support
56
55
  ]
57
56
 
@@ -8,7 +8,9 @@ import json
8
8
 
9
9
  # Single source of truth for the package version.
10
10
  # Mirrors the version in pyproject.toml — keep them in sync.
11
- __version__ = "0.1.5"
11
+ __version__ = "0.1.6"
12
+
13
+ __all__ = ["fetch_traffic", "sync", "serve_dashboard", "__version__"]
12
14
 
13
15
  # Import the internal building blocks — users never call these directly
14
16
  from .core import fetch_traffic_data, print_repo_table
@@ -20,7 +22,7 @@ logger = logging.getLogger(__name__)
20
22
  logger.addHandler(logging.NullHandler())
21
23
 
22
24
 
23
- def fetch_traffic(token: str, repo_name=None, print_table: bool = False, return_format: str = "dataframe", save_file: str = None):
25
+ def fetch_traffic(token: str, repo_name=None, print_table: bool = False, return_format: str = "dataframe", save_file: str = None, metrics: list = None):
24
26
  """
25
27
  Fetches the last 14 days of traffic data for one or all repositories.
26
28
 
@@ -35,13 +37,14 @@ def fetch_traffic(token: str, repo_name=None, print_table: bool = False, return_
35
37
  ``"summary"`` — returns a per-repo totals dict.
36
38
  save_file: Optional path to save the output. Extension determines
37
39
  format: ``.json`` writes JSON, anything else writes CSV.
40
+ metrics: Optional list of metrics to fetch (e.g., ``["views", "clones"]``).
38
41
 
39
42
  Returns:
40
43
  A ``pandas.DataFrame`` when ``return_format="dataframe"``, otherwise
41
44
  a ``dict`` matching the requested format.
42
45
  """
43
46
  # Hit the GitHub API and get back a tidy DataFrame (one row per day per repo)
44
- df = fetch_traffic_data(token, repo_name)
47
+ df = fetch_traffic_data(token, repo_name, metrics)
45
48
 
46
49
  # Print the ASCII table to the console if the user asked for it
47
50
  if print_table:
@@ -79,7 +82,7 @@ def fetch_traffic(token: str, repo_name=None, print_table: bool = False, return_
79
82
  return payload
80
83
 
81
84
 
82
- def sync(token: str, repo_name=None, data_dir: str = "./data", output_mode: str = "monthly", schedule_cron: str = None, export_json: str = None, export_public_only: bool = True):
85
+ def sync(token: str, repo_name=None, data_dir: str = "./data", output_mode: str = "monthly", schedule_cron: str = None, export_json: str = None, export_public_only: bool = True, metrics: list = None):
83
86
  """
84
87
  Fetches data and appends it to a local CSV database, optionally running as a permanent background daemon.
85
88
 
@@ -93,6 +96,7 @@ def sync(token: str, repo_name=None, data_dir: str = "./data", output_mode: str
93
96
  export_json: Path to export the merged historical database as a JSON file.
94
97
  export_public_only: If ``True`` (default), strips private repos from the
95
98
  exported JSON — acts as a security firewall.
99
+ metrics: Optional list of metrics to fetch (e.g., ``["views", "clones"]``).
96
100
  """
97
101
  # Hand off to the automation engine — it handles deduplication and schema migration
98
102
  run_sync(
@@ -102,7 +106,8 @@ def sync(token: str, repo_name=None, data_dir: str = "./data", output_mode: str
102
106
  output_mode=output_mode,
103
107
  schedule_cron=schedule_cron,
104
108
  export_json=export_json,
105
- export_public_only=export_public_only
109
+ export_public_only=export_public_only,
110
+ metrics=metrics
106
111
  )
107
112
 
108
113
 
@@ -130,11 +135,21 @@ def serve_dashboard(host: str = "127.0.0.1", port: int = 8000, token: str = None
130
135
  "Install them with: pip install \"gitlytics[dashboard]\""
131
136
  )
132
137
 
133
- # Pass the token and data folder to the FastAPI app via environment variables
134
- if token:
135
- os.environ["GITLYTICS_TOKEN"] = token
136
- if data_dir:
137
- os.environ["GITLYTICS_DATA_DIR"] = os.path.abspath(data_dir)
138
-
139
- # Start the web server — it won't return until the user presses Ctrl+C
140
- uvicorn.run("gitlytics.api:app", host=host, port=port, reload=False)
138
+ # M-7: save original values so they are restored when the server stops
139
+ _orig_token = os.environ.get("GITLYTICS_TOKEN")
140
+ _orig_data_dir = os.environ.get("GITLYTICS_DATA_DIR")
141
+ try:
142
+ if token:
143
+ os.environ["GITLYTICS_TOKEN"] = token
144
+ if data_dir:
145
+ os.environ["GITLYTICS_DATA_DIR"] = os.path.abspath(data_dir)
146
+ uvicorn.run("gitlytics.api:app", host=host, port=port, reload=False)
147
+ finally:
148
+ if _orig_token is None:
149
+ os.environ.pop("GITLYTICS_TOKEN", None)
150
+ else:
151
+ os.environ["GITLYTICS_TOKEN"] = _orig_token
152
+ if _orig_data_dir is None:
153
+ os.environ.pop("GITLYTICS_DATA_DIR", None)
154
+ else:
155
+ os.environ["GITLYTICS_DATA_DIR"] = _orig_data_dir
@@ -2,8 +2,10 @@
2
2
  gitlytics/api.py
3
3
  Powers the FastAPI backend — serves traffic data and the React dashboard to the browser.
4
4
  """
5
+ import hashlib
5
6
  import logging
6
7
  import os
8
+ import time as _time
7
9
  from pathlib import Path
8
10
 
9
11
  import pandas as pd
@@ -12,14 +14,21 @@ from fastapi.responses import FileResponse, JSONResponse
12
14
  from fastapi.middleware.cors import CORSMiddleware
13
15
  from fastapi.staticfiles import StaticFiles
14
16
 
15
- from gitlytics.core import validate_token, get_user_profile, fetch_traffic_data
17
+ from gitlytics.core import (
18
+ validate_token,
19
+ get_user_profile,
20
+ get_public_user,
21
+ get_public_repos,
22
+ fetch_traffic_data,
23
+ fetch_deep_stats_for_top,
24
+ )
16
25
  from gitlytics.process import process_uploaded_csv, build_react_payload
17
26
 
18
27
  logger = logging.getLogger(__name__)
19
28
 
20
29
  app = FastAPI(title="GitHub Traffic API")
21
30
 
22
- # Only allow requests from localhost — this dashboard is never deployed publicly
31
+ # Only allow requests from localhost — never deployed publicly
23
32
  _ALLOWED_ORIGINS = [
24
33
  "http://localhost",
25
34
  "http://localhost:3000",
@@ -40,14 +49,33 @@ app.add_middleware(
40
49
  )
41
50
 
42
51
 
52
+ _auth_cache: dict = {} # sha256_prefix -> (valid, username, expires_at)
53
+ _AUTH_CACHE_TTL = 300 # 5 minutes
54
+
55
+
43
56
  def _get_token(token: str = None) -> str:
44
- # Use the token from the request body, or fall back to the one set in the environment
45
- return token or os.environ.get("GITLYTICS_TOKEN")
57
+ # C-2: explicit empty string must not fall through to the env token
58
+ if token and token.strip():
59
+ return token.strip()
60
+ return os.environ.get("GITLYTICS_TOKEN")
61
+
62
+
63
+ def _validate_token_cached(token: str):
64
+ # M-1: cache validation results to avoid a double HTTP round-trip on every /api/traffic call
65
+ key = hashlib.sha256(token.encode()).hexdigest()[:16]
66
+ now = _time.time()
67
+ if key in _auth_cache:
68
+ valid, username, expires = _auth_cache[key]
69
+ if now < expires:
70
+ return valid, username
71
+ from gitlytics.core import validate_token
72
+ valid, username = validate_token(token)
73
+ _auth_cache[key] = (valid, username, now + _AUTH_CACHE_TTL)
74
+ return valid, username
46
75
 
47
76
 
48
77
  @app.get("/api/config")
49
78
  def get_config():
50
- # Lets the frontend know if it's running in headless/TV mode with a pre-set token
51
79
  return {
52
80
  "has_token": bool(os.environ.get("GITLYTICS_TOKEN")),
53
81
  "has_data_dir": bool(os.environ.get("GITLYTICS_DATA_DIR"))
@@ -63,21 +91,39 @@ def auth(token: str = Body("", embed=True)):
63
91
 
64
92
  ok, username = validate_token(active_token)
65
93
  if not ok:
66
- # Log a warning without echoing the token value into logs
67
94
  logger.warning("Authentication attempt failed for a provided token.")
68
95
  raise HTTPException(status_code=401, detail=username)
69
96
 
70
- # Fetch the real display name and avatar URL — validate_token only gives us the login
71
97
  profile = get_user_profile(active_token)
72
98
 
73
99
  return {
74
100
  "authenticated": True,
75
101
  "username": profile["login"] or username,
76
- "name": profile["name"] or username, # Real display name, e.g. "Ameya Chopade"
77
- "avatar_url": profile["avatar_url"], # Real GitHub avatar URL
102
+ "name": profile["name"] or username,
103
+ "avatar_url": profile["avatar_url"],
104
+ "bio": profile.get("bio"),
105
+ "location": profile.get("location"),
106
+ "followers": profile.get("followers", 0),
107
+ "following": profile.get("following", 0),
78
108
  }
79
109
 
80
110
 
111
+ @app.post("/api/username")
112
+ def get_username_data(username: str = Body("", embed=True)):
113
+ """Fetches public profile and repos for any GitHub username — no token required."""
114
+ if not username or not username.strip():
115
+ raise HTTPException(status_code=400, detail="Username is required.")
116
+ try:
117
+ profile = get_public_user(username.strip())
118
+ repos = get_public_repos(username.strip())
119
+ return {"profile": profile, "repos": repos}
120
+ except ValueError as exc:
121
+ raise HTTPException(status_code=404, detail=str(exc))
122
+ except Exception as exc:
123
+ logger.warning(f"Username fetch failed for {username}: {exc}")
124
+ raise HTTPException(status_code=500, detail="Failed to fetch GitHub data.")
125
+
126
+
81
127
  @app.post("/api/traffic")
82
128
  def get_traffic(token: str = Body("", embed=True)):
83
129
  # Serve traffic data — either from the historical CSV database or live from GitHub
@@ -85,13 +131,11 @@ def get_traffic(token: str = Body("", embed=True)):
85
131
  if not active_token:
86
132
  raise HTTPException(status_code=401, detail="No token provided")
87
133
 
88
- ok, _ = validate_token(active_token)
134
+ ok, _ = _validate_token_cached(active_token)
89
135
  if not ok:
90
136
  raise HTTPException(status_code=401, detail="Invalid token")
91
-
92
137
  data_dir = os.environ.get("GITLYTICS_DATA_DIR")
93
138
  if data_dir:
94
- # Load from the historical CSV database (headless/TV mode)
95
139
  data_dir_path = Path(data_dir)
96
140
  csv_files = list(data_dir_path.glob("traffic_*.csv")) if data_dir_path.exists() else []
97
141
  dfs = []
@@ -102,43 +146,47 @@ def get_traffic(token: str = Body("", embed=True)):
102
146
  logger.warning(f"Skipping unreadable CSV '{f}': {exc}")
103
147
  if dfs:
104
148
  df = pd.concat(dfs, ignore_index=True)
105
- # Clean up any duplicate day-repo rows that crept in somehow
106
149
  df = df.drop_duplicates(subset=["date", "repository"], keep="last")
107
150
  else:
108
- # No CSVs found — fall through to a live fetch
109
151
  df = fetch_traffic_data(active_token)
110
152
  else:
111
- # Default: hit GitHub and get the live 14-day window
112
153
  df = fetch_traffic_data(active_token)
113
154
 
114
- # Replace any infinity or NaN values before JSON serialisation
115
155
  df = df.replace([float('inf'), float('-inf')], None).where(pd.notnull(df), None)
116
156
 
117
- # Transform the DataFrame into the array of objects the React app expects
118
- payload = build_react_payload(df)
157
+ # Build a quick view-sum map to find the top 20 repos for deep fetching
158
+ repos_with_views = []
159
+ if not df.empty and "repository" in df.columns and "views" in df.columns:
160
+ for repo_name, group in df.groupby("repository"):
161
+ repos_with_views.append({"repository": repo_name, "total_views": int(group["views"].sum())})
162
+
163
+ # Fetch deep stats concurrently for the top 20 most-viewed repos
164
+ deep_stats = {}
165
+ if repos_with_views and active_token:
166
+ deep_stats = fetch_deep_stats_for_top(active_token, repos_with_views, top_n=20)
167
+
168
+ payload = build_react_payload(df, deep_stats=deep_stats)
119
169
  return payload
120
170
 
121
171
 
122
172
  @app.post("/api/upload-csv")
123
173
  def upload_csv(file: UploadFile = File(...)):
124
- # Accept a user-uploaded CSV and convert it to the same format as the API response
174
+ # Accept a user-uploaded CSV deep stats not available in CSV mode
125
175
  try:
126
176
  df = process_uploaded_csv(file.file)
127
177
  df = df.replace([float('inf'), float('-inf')], None).where(pd.notnull(df), None)
128
- payload = build_react_payload(df)
178
+ payload = build_react_payload(df, deep_stats=None)
129
179
  return payload
130
180
  except Exception as e:
131
181
  raise HTTPException(status_code=400, detail=str(e))
132
182
 
133
183
 
134
- # ── Static file serving ───────────────────────────────────────────────────────
135
- # The React build output lands in gitlytics/static/ after `npm run build`
184
+ # Static file serving
136
185
  frontend_dir = Path(__file__).parent / "static"
137
186
 
138
187
 
139
188
  @app.get("/")
140
189
  def serve_index():
141
- # Serve the React app's index.html for the root URL
142
190
  index_file = frontend_dir / "index.html"
143
191
  if index_file.exists():
144
192
  return FileResponse(index_file)
@@ -150,17 +198,11 @@ def serve_index():
150
198
 
151
199
  @app.get("/{full_path:path}")
152
200
  def serve_spa_fallback(full_path: str):
153
- """
154
- SPA catch-all — any URL that doesn't match an API route returns index.html
155
- so React Router can handle client-side navigation on hard refresh.
156
- Real static assets (JS/CSS) are served by the StaticFiles mount first.
157
- """
158
- # Serve the actual file if it exists (e.g. a JS or CSS asset)
201
+ """SPA catch-all — returns index.html so React Router handles navigation."""
159
202
  asset_file = frontend_dir / full_path
160
203
  if asset_file.exists() and asset_file.is_file():
161
204
  return FileResponse(asset_file)
162
205
 
163
- # For everything else (like /repos/my-repo), hand control to React Router
164
206
  index_file = frontend_dir / "index.html"
165
207
  if index_file.exists():
166
208
  return FileResponse(index_file)
@@ -171,7 +213,6 @@ def serve_spa_fallback(full_path: str):
171
213
  )
172
214
 
173
215
 
174
- # Mount the /assets directory for compiled JS and CSS — must come after route definitions
175
216
  assets_dir = frontend_dir / "assets"
176
217
  if assets_dir.exists():
177
218
  app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")