gitlytics 0.1.2__tar.gz → 0.1.4__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 (40) hide show
  1. {gitlytics-0.1.2/src/gitlytics.egg-info → gitlytics-0.1.4}/PKG-INFO +44 -19
  2. gitlytics-0.1.2/PKG-INFO → gitlytics-0.1.4/README.md +28 -36
  3. gitlytics-0.1.4/pyproject.toml +75 -0
  4. gitlytics-0.1.4/src/gitlytics/__init__.py +140 -0
  5. gitlytics-0.1.4/src/gitlytics/__main__.py +9 -0
  6. gitlytics-0.1.4/src/gitlytics/api.py +177 -0
  7. gitlytics-0.1.4/src/gitlytics/automation.py +217 -0
  8. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics/cli.py +35 -7
  9. gitlytics-0.1.4/src/gitlytics/core.py +288 -0
  10. gitlytics-0.1.4/src/gitlytics/process.py +237 -0
  11. gitlytics-0.1.4/src/gitlytics/static/assets/index-CRiPPhX0.js +44 -0
  12. gitlytics-0.1.4/src/gitlytics/static/assets/index-i_G_CLdY.css +2 -0
  13. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics/static/index.html +2 -2
  14. gitlytics-0.1.2/README.md → gitlytics-0.1.4/src/gitlytics.egg-info/PKG-INFO +61 -9
  15. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics.egg-info/SOURCES.txt +6 -4
  16. gitlytics-0.1.4/src/gitlytics.egg-info/requires.txt +16 -0
  17. gitlytics-0.1.4/tests/test_api.py +264 -0
  18. gitlytics-0.1.4/tests/test_automation.py +178 -0
  19. gitlytics-0.1.4/tests/test_cli.py +130 -0
  20. gitlytics-0.1.4/tests/test_core.py +226 -0
  21. gitlytics-0.1.4/tests/test_process.py +321 -0
  22. gitlytics-0.1.2/pyproject.toml +0 -52
  23. gitlytics-0.1.2/src/gitlytics/__init__.py +0 -73
  24. gitlytics-0.1.2/src/gitlytics/api.py +0 -112
  25. gitlytics-0.1.2/src/gitlytics/automation.py +0 -153
  26. gitlytics-0.1.2/src/gitlytics/core.py +0 -188
  27. gitlytics-0.1.2/src/gitlytics/process.py +0 -117
  28. gitlytics-0.1.2/src/gitlytics/static/assets/index-BPCIBQz4.js +0 -44
  29. gitlytics-0.1.2/src/gitlytics/static/assets/index-DytQw1pB.css +0 -2
  30. gitlytics-0.1.2/src/gitlytics/static/icons.svg +0 -24
  31. gitlytics-0.1.2/src/gitlytics.egg-info/requires.txt +0 -13
  32. gitlytics-0.1.2/tests/test_automation.py +0 -72
  33. gitlytics-0.1.2/tests/test_cli.py +0 -56
  34. gitlytics-0.1.2/tests/test_core.py +0 -55
  35. {gitlytics-0.1.2 → gitlytics-0.1.4}/LICENSE +0 -0
  36. {gitlytics-0.1.2 → gitlytics-0.1.4}/setup.cfg +0 -0
  37. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics/static/favicon.svg +0 -0
  38. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics.egg-info/dependency_links.txt +0 -0
  39. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics.egg-info/entry_points.txt +0 -0
  40. {gitlytics-0.1.2 → gitlytics-0.1.4}/src/gitlytics.egg-info/top_level.txt +0 -0
@@ -1,10 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlytics
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Monitor and automate your GitHub repository traffic analytics.
5
- Author: Ameya Chopade
5
+ Author-email: Ameya Chopade <ameyaccod171@gmail.com>
6
6
  License: Apache-2.0
7
- Project-URL: Homepage, https://github.com/ameyac11/gitlytics
7
+ Project-URL: Homepage, https://gitlytics.dev
8
+ Project-URL: Documentation, https://docs.gitlytics.dev
9
+ Project-URL: Repository, https://github.com/ameyac11/gitlytics
10
+ Project-URL: Bug Tracker, https://github.com/ameyac11/gitlytics/issues
8
11
  Keywords: github,traffic,analytics,automation,cli
9
12
  Classifier: Programming Language :: Python :: 3
10
13
  Classifier: License :: OSI Approved :: Apache Software License
@@ -12,17 +15,20 @@ Classifier: Operating System :: OS Independent
12
15
  Requires-Python: >=3.9
13
16
  Description-Content-Type: text/markdown
14
17
  License-File: LICENSE
15
- Requires-Dist: requests
16
- Requires-Dist: pandas
17
- Requires-Dist: python-dotenv
18
- Requires-Dist: croniter
18
+ Requires-Dist: requests>=2.32.0
19
+ Requires-Dist: pandas>=2.2.0
20
+ Requires-Dist: python-dotenv>=1.0.1
21
+ Requires-Dist: croniter>=2.0.0
19
22
  Provides-Extra: dashboard
20
- Requires-Dist: fastapi; extra == "dashboard"
21
- Requires-Dist: uvicorn; extra == "dashboard"
22
- Requires-Dist: python-multipart; extra == "dashboard"
23
+ Requires-Dist: fastapi>=0.111.0; extra == "dashboard"
24
+ Requires-Dist: uvicorn>=0.30.0; extra == "dashboard"
25
+ Requires-Dist: python-multipart>=0.0.9; extra == "dashboard"
23
26
  Provides-Extra: dev
24
27
  Requires-Dist: pytest>=8.0.0; extra == "dev"
25
28
  Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
29
+ Requires-Dist: httpx>=0.27.0; extra == "dev"
30
+ Requires-Dist: Faker>=20.0.0; extra == "dev"
31
+ Requires-Dist: anyio[trio]>=4.0.0; extra == "dev"
26
32
  Dynamic: license-file
27
33
 
28
34
  <div align="center">
@@ -36,17 +42,26 @@ Dynamic: license-file
36
42
  [![React](https://img.shields.io/badge/UI-React-61dafb?logo=react)](https://react.dev/)
37
43
  [![FastAPI](https://img.shields.io/badge/Backend-FastAPI-009688?logo=fastapi)](https://fastapi.tiangolo.com/)
38
44
  [![Automation](https://img.shields.io/badge/Data%20Backup-Automation%20Tool-purple?logo=github-actions)](https://github.com/ameyac11/gitlytics-github-traffic-automation)
45
+ [![Homepage](https://img.shields.io/badge/Homepage-gitlytics.dev-success)](https://gitlytics.dev)
46
+ [![Live](https://img.shields.io/badge/Live%20Demo-dashboard.gitlytics.dev-success)](https://dashboard.gitlytics.dev)
47
+ [![Docs](https://img.shields.io/badge/Docs-docs.gitlytics.dev-success)](https://docs.gitlytics.dev)
39
48
 
40
49
  **Beautiful GitHub traffic analytics for all your repositories — public and private.** <br/> Track views, clones, referrers, and popular paths indefinitely.
41
50
 
42
- Please consider giving this project a if you find it helpful!
51
+ **[Try the live dashboard at dashboard.gitlytics.dev](https://dashboard.gitlytics.dev)**
52
+ 📚 **[Read the documentation at docs.gitlytics.dev](https://docs.gitlytics.dev)**
43
53
 
44
- </div>
54
+ <br/>
45
55
 
46
- ---
56
+ > **🐍 Native Python API**
57
+ >
58
+ > 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.
59
+ >
60
+ > 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
47
61
 
48
- > **⚠️ NOTE: V0.1.2 ARCHITECTURE UPGRADE!** <br/>
49
- > Formerly `github-traffic-monitor`, we have officially rebranded to **`gitlytics`**! We completely migrated away from Streamlit. The dashboard is now a **React + Vite** SPA, powered by a **FastAPI** backend!
62
+ Please consider giving this project a ⭐ if you find it helpful!
63
+
64
+ </div>
50
65
 
51
66
  ---
52
67
 
@@ -62,7 +77,8 @@ Please consider giving this project a ⭐ if you find it helpful!
62
77
 
63
78
  ## 📌 Table of Contents
64
79
 
65
- - [🚨 The 14-Day Catch (And How We Fix It)](#the-14-day-catch-and-how-we-fix-it)
80
+ - [🔗 The Gitlytics Ecosystem](#-the-gitlytics-ecosystem)
81
+ - [🚨 The 14-Day Catch (And How We Fix It)](#-the-14-day-catch-and-how-we-fix-it)
66
82
  - [🛠️ Installation](#installation)
67
83
  - [🔑 Generating a GitHub Personal Access Token](#generating-a-github-personal-access-token)
68
84
  - [⌨️ The 3 Core CLI Commands](#the-3-core-cli-commands)
@@ -79,6 +95,15 @@ Please consider giving this project a ⭐ if you find it helpful!
79
95
 
80
96
  ---
81
97
 
98
+ ## 🔗 The Gitlytics Ecosystem
99
+
100
+ 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:
101
+
102
+ - 🌐 **[Gitlytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production homepage, React Dashboard, and VitePress documentation site.
103
+ - ⚙️ **[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.
104
+
105
+ ---
106
+
82
107
  ## 🚨 The 14-Day Catch (And How We Fix It)
83
108
 
84
109
  > **⚠️ Did you know?** GitHub normally **only saves your repository traffic data for 14 days**. After two weeks, your valuable views and clones data is permanently deleted.
@@ -148,7 +173,7 @@ gitlytics dashboard
148
173
 
149
174
  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.
150
175
 
151
- 📚 **[Read the Full API Documentation](docs/api_documentation.md)**
176
+ 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
152
177
 
153
178
  ### 1️⃣ `gitlytics.fetch_traffic()`
154
179
  Fetches the last 14 days of traffic data (views, clones, referrers, paths) for one or more repositories.
@@ -159,7 +184,7 @@ import gitlytics
159
184
  # Fetch traffic for all repositories accessible by the token
160
185
  df = gitlytics.fetch_traffic(
161
186
  token="ghp_your_token",
162
- return_format="dataframe" # Options: "dataframe" (Pandas), "timeseries" (React Chart-ready dict), or "raw" (JSON API payload)
187
+ return_format="dataframe" # Options: "dataframe" (Pandas), "timeseries" (chart-ready dict), or "summary" (per-repo totals dict)
163
188
  )
164
189
 
165
190
  # Fetch traffic for a single specific repository and print the table to stdout
@@ -183,7 +208,7 @@ gitlytics.fetch_traffic(
183
208
  | `token` | `str` | *Required* | GitHub Personal Access Token with `repo` scope enabled. |
184
209
  | `repo_name` | `str` | `None` | Specific repository name (e.g. `"user/repo"`). If `None`, fetches all repositories. |
185
210
  | `print_table` | `bool` | `False` | If `True`, formats and prints a detailed ASCII traffic table to the console. |
186
- | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"`, or `"raw"`. |
211
+ | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"` (chart-ready nested dict), or `"summary"` (per-repo totals dict). |
187
212
  | `save_file` | `str` | `None` | Optional. File path where the fetched data will be saved (CSV or JSON). |
188
213
 
189
214
  ---
@@ -1,30 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: gitlytics
3
- Version: 0.1.2
4
- Summary: Monitor and automate your GitHub repository traffic analytics.
5
- Author: Ameya Chopade
6
- License: Apache-2.0
7
- Project-URL: Homepage, https://github.com/ameyac11/gitlytics
8
- Keywords: github,traffic,analytics,automation,cli
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: Apache Software License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: requests
16
- Requires-Dist: pandas
17
- Requires-Dist: python-dotenv
18
- Requires-Dist: croniter
19
- Provides-Extra: dashboard
20
- Requires-Dist: fastapi; extra == "dashboard"
21
- Requires-Dist: uvicorn; extra == "dashboard"
22
- Requires-Dist: python-multipart; extra == "dashboard"
23
- Provides-Extra: dev
24
- Requires-Dist: pytest>=8.0.0; extra == "dev"
25
- Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
26
- Dynamic: license-file
27
-
28
1
  <div align="center">
29
2
 
30
3
  # 📊 Gitlytics
@@ -36,17 +9,26 @@ Dynamic: license-file
36
9
  [![React](https://img.shields.io/badge/UI-React-61dafb?logo=react)](https://react.dev/)
37
10
  [![FastAPI](https://img.shields.io/badge/Backend-FastAPI-009688?logo=fastapi)](https://fastapi.tiangolo.com/)
38
11
  [![Automation](https://img.shields.io/badge/Data%20Backup-Automation%20Tool-purple?logo=github-actions)](https://github.com/ameyac11/gitlytics-github-traffic-automation)
12
+ [![Homepage](https://img.shields.io/badge/Homepage-gitlytics.dev-success)](https://gitlytics.dev)
13
+ [![Live](https://img.shields.io/badge/Live%20Demo-dashboard.gitlytics.dev-success)](https://dashboard.gitlytics.dev)
14
+ [![Docs](https://img.shields.io/badge/Docs-docs.gitlytics.dev-success)](https://docs.gitlytics.dev)
39
15
 
40
16
  **Beautiful GitHub traffic analytics for all your repositories — public and private.** <br/> Track views, clones, referrers, and popular paths indefinitely.
41
17
 
42
- Please consider giving this project a if you find it helpful!
18
+ **[Try the live dashboard at dashboard.gitlytics.dev](https://dashboard.gitlytics.dev)**
19
+ 📚 **[Read the documentation at docs.gitlytics.dev](https://docs.gitlytics.dev)**
43
20
 
44
- </div>
21
+ <br/>
45
22
 
46
- ---
23
+ > **🐍 Native Python API**
24
+ >
25
+ > 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.
26
+ >
27
+ > 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
28
+
29
+ Please consider giving this project a ⭐ if you find it helpful!
47
30
 
48
- > **⚠️ NOTE: V0.1.2 ARCHITECTURE UPGRADE!** <br/>
49
- > Formerly `github-traffic-monitor`, we have officially rebranded to **`gitlytics`**! We completely migrated away from Streamlit. The dashboard is now a **React + Vite** SPA, powered by a **FastAPI** backend!
31
+ </div>
50
32
 
51
33
  ---
52
34
 
@@ -62,7 +44,8 @@ Please consider giving this project a ⭐ if you find it helpful!
62
44
 
63
45
  ## 📌 Table of Contents
64
46
 
65
- - [🚨 The 14-Day Catch (And How We Fix It)](#the-14-day-catch-and-how-we-fix-it)
47
+ - [🔗 The Gitlytics Ecosystem](#-the-gitlytics-ecosystem)
48
+ - [🚨 The 14-Day Catch (And How We Fix It)](#-the-14-day-catch-and-how-we-fix-it)
66
49
  - [🛠️ Installation](#installation)
67
50
  - [🔑 Generating a GitHub Personal Access Token](#generating-a-github-personal-access-token)
68
51
  - [⌨️ The 3 Core CLI Commands](#the-3-core-cli-commands)
@@ -79,6 +62,15 @@ Please consider giving this project a ⭐ if you find it helpful!
79
62
 
80
63
  ---
81
64
 
65
+ ## 🔗 The Gitlytics Ecosystem
66
+
67
+ 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:
68
+
69
+ - 🌐 **[Gitlytics Web Ecosystem](https://github.com/ameyac11/gitlytics-deployement)**: The production homepage, React Dashboard, and VitePress documentation site.
70
+ - ⚙️ **[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.
71
+
72
+ ---
73
+
82
74
  ## 🚨 The 14-Day Catch (And How We Fix It)
83
75
 
84
76
  > **⚠️ Did you know?** GitHub normally **only saves your repository traffic data for 14 days**. After two weeks, your valuable views and clones data is permanently deleted.
@@ -148,7 +140,7 @@ gitlytics dashboard
148
140
 
149
141
  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.
150
142
 
151
- 📚 **[Read the Full API Documentation](docs/api_documentation.md)**
143
+ 📚 **[Read the Full API Documentation](https://docs.gitlytics.dev)**
152
144
 
153
145
  ### 1️⃣ `gitlytics.fetch_traffic()`
154
146
  Fetches the last 14 days of traffic data (views, clones, referrers, paths) for one or more repositories.
@@ -159,7 +151,7 @@ import gitlytics
159
151
  # Fetch traffic for all repositories accessible by the token
160
152
  df = gitlytics.fetch_traffic(
161
153
  token="ghp_your_token",
162
- return_format="dataframe" # Options: "dataframe" (Pandas), "timeseries" (React Chart-ready dict), or "raw" (JSON API payload)
154
+ return_format="dataframe" # Options: "dataframe" (Pandas), "timeseries" (chart-ready dict), or "summary" (per-repo totals dict)
163
155
  )
164
156
 
165
157
  # Fetch traffic for a single specific repository and print the table to stdout
@@ -183,7 +175,7 @@ gitlytics.fetch_traffic(
183
175
  | `token` | `str` | *Required* | GitHub Personal Access Token with `repo` scope enabled. |
184
176
  | `repo_name` | `str` | `None` | Specific repository name (e.g. `"user/repo"`). If `None`, fetches all repositories. |
185
177
  | `print_table` | `bool` | `False` | If `True`, formats and prints a detailed ASCII traffic table to the console. |
186
- | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"`, or `"raw"`. |
178
+ | `return_format` | `str` | `"dataframe"` | The format of returned data: `"dataframe"` (Pandas DataFrame), `"timeseries"` (chart-ready nested dict), or `"summary"` (per-repo totals dict). |
187
179
  | `save_file` | `str` | `None` | Optional. File path where the fetched data will be saved (CSV or JSON). |
188
180
 
189
181
  ---
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gitlytics"
7
+ version = "0.1.4"
8
+ description = "Monitor and automate your GitHub repository traffic analytics."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [
13
+ { name = "Ameya Chopade", email = "ameyaccod171@gmail.com" }
14
+ ]
15
+ # Fix #20: Add version lower-bounds to prevent silent breakage on old environments.
16
+ # These match the constraints already documented in requirements.txt.
17
+ dependencies = [
18
+ "requests>=2.32.0",
19
+ "pandas>=2.2.0",
20
+ "python-dotenv>=1.0.1",
21
+ "croniter>=2.0.0"
22
+ ]
23
+ keywords = [
24
+ "github",
25
+ "traffic",
26
+ "analytics",
27
+ "automation",
28
+ "cli"
29
+ ]
30
+ classifiers = [
31
+ "Programming Language :: Python :: 3",
32
+ "License :: OSI Approved :: Apache Software License",
33
+ "Operating System :: OS Independent",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://gitlytics.dev"
38
+ Documentation = "https://docs.gitlytics.dev"
39
+ Repository = "https://github.com/ameyac11/gitlytics"
40
+ "Bug Tracker" = "https://github.com/ameyac11/gitlytics/issues"
41
+
42
+ [project.optional-dependencies]
43
+ # Fix #20: dashboard extras are optional — base package works without them.
44
+ # requirements.txt was including them as mandatory; that was wrong.
45
+ dashboard = [
46
+ "fastapi>=0.111.0",
47
+ "uvicorn>=0.30.0",
48
+ "python-multipart>=0.0.9"
49
+ ]
50
+ dev = [
51
+ "pytest>=8.0.0",
52
+ "pytest-cov>=5.0.0",
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
+ "anyio[trio]>=4.0.0" # async test support
56
+ ]
57
+
58
+
59
+ [project.scripts]
60
+ gitlytics = "gitlytics.cli:main"
61
+
62
+ [tool.setuptools.packages.find]
63
+ where = ["src"]
64
+ include = ["gitlytics*"]
65
+
66
+ [tool.setuptools.package-data]
67
+ "gitlytics" = ["static/**/*", "static/*"]
68
+
69
+ [tool.pytest.ini_options]
70
+ # Only collect files that match the offline test naming convention.
71
+ # live_*.py scripts are excluded — they need a real GitHub token.
72
+ testpaths = ["tests"]
73
+ python_files = ["test_*.py"]
74
+ python_classes = ["Test*"]
75
+ python_functions = ["test_*"]
@@ -0,0 +1,140 @@
1
+ """
2
+ gitlytics/__init__.py
3
+ The public API for the gitlytics package.
4
+ """
5
+ import os
6
+ import logging
7
+ import json
8
+
9
+ # Single source of truth for the package version.
10
+ # Mirrors the version in pyproject.toml — keep them in sync.
11
+ __version__ = "0.1.4"
12
+
13
+ # Import the internal building blocks — users never call these directly
14
+ from .core import fetch_traffic_data, print_repo_table
15
+ from .automation import run_sync
16
+ from .process import build_json_payload
17
+
18
+ # Set up a silent logger so gitlytics never messes with your app's logging
19
+ logger = logging.getLogger(__name__)
20
+ logger.addHandler(logging.NullHandler())
21
+
22
+
23
+ def fetch_traffic(token: str, repo_name=None, print_table: bool = False, return_format: str = "dataframe", save_file: str = None):
24
+ """
25
+ Fetches the last 14 days of traffic data for one or all repositories.
26
+
27
+ Args:
28
+ token: GitHub Personal Access Token with `repo` scope.
29
+ repo_name: Specific repo name (e.g. ``"user/repo"``) or list of names.
30
+ If ``None``, fetches all repositories accessible by the token.
31
+ print_table: If ``True``, prints an ASCII summary table to stdout.
32
+ return_format: Shape of the returned data. One of:
33
+ ``"dataframe"`` (default) — returns a ``pandas.DataFrame``.
34
+ ``"timeseries"`` — returns a nested JSON-serialisable dict.
35
+ ``"summary"`` — returns a per-repo totals dict.
36
+ save_file: Optional path to save the output. Extension determines
37
+ format: ``.json`` writes JSON, anything else writes CSV.
38
+
39
+ Returns:
40
+ A ``pandas.DataFrame`` when ``return_format="dataframe"``, otherwise
41
+ a ``dict`` matching the requested format.
42
+ """
43
+ # Hit the GitHub API and get back a tidy DataFrame (one row per day per repo)
44
+ df = fetch_traffic_data(token, repo_name)
45
+
46
+ # Print the ASCII table to the console if the user asked for it
47
+ if print_table:
48
+ print_repo_table(df)
49
+
50
+ # --- dataframe mode: just return the raw DataFrame, optionally save it ---
51
+ if return_format == "dataframe":
52
+ if save_file:
53
+ if save_file.endswith(".json"):
54
+ # Save as a chart-ready JSON file
55
+ payload = build_json_payload(df, return_format="timeseries", export_public_only=True)
56
+ with open(save_file, "w", encoding="utf-8") as f:
57
+ json.dump(payload, f, indent=2)
58
+ else:
59
+ # Save as a standard CSV file
60
+ df.to_csv(save_file, index=False)
61
+ return df
62
+
63
+ # Reject anything that isn't a known format before doing more work
64
+ valid_formats = {"timeseries", "summary"}
65
+ if return_format not in valid_formats:
66
+ raise ValueError(
67
+ f"Invalid return_format={return_format!r}. "
68
+ f"Choose one of: 'dataframe', 'timeseries', 'summary'."
69
+ )
70
+
71
+ # Build the JSON-serialisable payload in the requested shape
72
+ payload = build_json_payload(df, return_format=return_format, export_public_only=False)
73
+
74
+ # Save to disk if the user gave us a file path
75
+ if save_file:
76
+ with open(save_file, "w", encoding="utf-8") as f:
77
+ json.dump(payload, f, indent=2)
78
+
79
+ return payload
80
+
81
+
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):
83
+ """
84
+ Fetches data and appends it to a local CSV database, optionally running as a permanent background daemon.
85
+
86
+ Args:
87
+ token: GitHub Personal Access Token.
88
+ repo_name: Specific repository name(s) to sync. If ``None``, syncs all.
89
+ data_dir: Directory where CSV files are stored.
90
+ output_mode: ``"monthly"`` (``traffic_YYYY-MM.csv``) or ``"yearly"`` (``traffic_YYYY.csv``).
91
+ schedule_cron: Standard cron expression (e.g. ``"0 23 * * *"``). If set,
92
+ runs an infinite scheduler loop.
93
+ export_json: Path to export the merged historical database as a JSON file.
94
+ export_public_only: If ``True`` (default), strips private repos from the
95
+ exported JSON — acts as a security firewall.
96
+ """
97
+ # Hand off to the automation engine — it handles deduplication and schema migration
98
+ run_sync(
99
+ token=token,
100
+ repo_names=repo_name,
101
+ data_dir=data_dir,
102
+ output_mode=output_mode,
103
+ schedule_cron=schedule_cron,
104
+ export_json=export_json,
105
+ export_public_only=export_public_only
106
+ )
107
+
108
+
109
+ def serve_dashboard(host: str = "127.0.0.1", port: int = 8000, token: str = None, data_dir: str = None):
110
+ """
111
+ Starts the React + FastAPI dashboard server.
112
+
113
+ ``uvicorn`` and ``fastapi`` are optional dependencies installed via::
114
+
115
+ pip install "gitlytics[dashboard]"
116
+
117
+ Args:
118
+ host: Host IP to bind. Use ``"0.0.0.0"`` to listen on all interfaces.
119
+ port: Port number (default ``8000``).
120
+ token: Optional GitHub token — pre-authenticates the dashboard session.
121
+ data_dir: Optional path to the historical CSV database directory.
122
+ """
123
+ # Only import uvicorn when the user actually calls serve_dashboard,
124
+ # so the base `pip install gitlytics` doesn't crash without it
125
+ try:
126
+ import uvicorn
127
+ except ImportError:
128
+ raise ImportError(
129
+ "The dashboard requires additional dependencies. "
130
+ "Install them with: pip install \"gitlytics[dashboard]\""
131
+ )
132
+
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)
@@ -0,0 +1,9 @@
1
+ """
2
+ gitlytics/__main__.py
3
+ Makes `python -m gitlytics` work identically to the `gitlytics` console command.
4
+ This is the entry point Python calls when the package is run with -m.
5
+ """
6
+ from gitlytics.cli import main
7
+
8
+ # Run the CLI when invoked as `python -m gitlytics`
9
+ main()
@@ -0,0 +1,177 @@
1
+ """
2
+ gitlytics/api.py
3
+ Powers the FastAPI backend — serves traffic data and the React dashboard to the browser.
4
+ """
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+
9
+ import pandas as pd
10
+ from fastapi import FastAPI, HTTPException, Body, File, UploadFile
11
+ from fastapi.responses import FileResponse, JSONResponse
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from fastapi.staticfiles import StaticFiles
14
+
15
+ from gitlytics.core import validate_token, get_user_profile, fetch_traffic_data
16
+ from gitlytics.process import process_uploaded_csv, build_react_payload
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ app = FastAPI(title="GitHub Traffic API")
21
+
22
+ # Only allow requests from localhost — this dashboard is never deployed publicly
23
+ _ALLOWED_ORIGINS = [
24
+ "http://localhost",
25
+ "http://localhost:3000",
26
+ "http://localhost:5173",
27
+ "http://localhost:8000",
28
+ "http://127.0.0.1",
29
+ "http://127.0.0.1:3000",
30
+ "http://127.0.0.1:5173",
31
+ "http://127.0.0.1:8000",
32
+ ]
33
+
34
+ app.add_middleware(
35
+ CORSMiddleware,
36
+ allow_origins=_ALLOWED_ORIGINS,
37
+ allow_credentials=True,
38
+ allow_methods=["GET", "POST"],
39
+ allow_headers=["Content-Type"],
40
+ )
41
+
42
+
43
+ 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")
46
+
47
+
48
+ @app.get("/api/config")
49
+ def get_config():
50
+ # Lets the frontend know if it's running in headless/TV mode with a pre-set token
51
+ return {
52
+ "has_token": bool(os.environ.get("GITLYTICS_TOKEN")),
53
+ "has_data_dir": bool(os.environ.get("GITLYTICS_DATA_DIR"))
54
+ }
55
+
56
+
57
+ @app.post("/api/auth")
58
+ def auth(token: str = Body("", embed=True)):
59
+ # Validate the token and return the user's GitHub profile info
60
+ active_token = _get_token(token)
61
+ if not active_token:
62
+ raise HTTPException(status_code=401, detail="No token provided and no environment token found.")
63
+
64
+ ok, username = validate_token(active_token)
65
+ if not ok:
66
+ # Log a warning without echoing the token value into logs
67
+ logger.warning("Authentication attempt failed for a provided token.")
68
+ raise HTTPException(status_code=401, detail=username)
69
+
70
+ # Fetch the real display name and avatar URL — validate_token only gives us the login
71
+ profile = get_user_profile(active_token)
72
+
73
+ return {
74
+ "authenticated": True,
75
+ "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
78
+ }
79
+
80
+
81
+ @app.post("/api/traffic")
82
+ def get_traffic(token: str = Body("", embed=True)):
83
+ # Serve traffic data — either from the historical CSV database or live from GitHub
84
+ active_token = _get_token(token)
85
+ if not active_token:
86
+ raise HTTPException(status_code=401, detail="No token provided")
87
+
88
+ ok, _ = validate_token(active_token)
89
+ if not ok:
90
+ raise HTTPException(status_code=401, detail="Invalid token")
91
+
92
+ data_dir = os.environ.get("GITLYTICS_DATA_DIR")
93
+ if data_dir:
94
+ # Load from the historical CSV database (headless/TV mode)
95
+ data_dir_path = Path(data_dir)
96
+ csv_files = list(data_dir_path.glob("traffic_*.csv")) if data_dir_path.exists() else []
97
+ dfs = []
98
+ for f in csv_files:
99
+ try:
100
+ dfs.append(pd.read_csv(f))
101
+ except Exception as exc:
102
+ logger.warning(f"Skipping unreadable CSV '{f}': {exc}")
103
+ if dfs:
104
+ df = pd.concat(dfs, ignore_index=True)
105
+ # Clean up any duplicate day-repo rows that crept in somehow
106
+ df = df.drop_duplicates(subset=["date", "repository"], keep="last")
107
+ else:
108
+ # No CSVs found — fall through to a live fetch
109
+ df = fetch_traffic_data(active_token)
110
+ else:
111
+ # Default: hit GitHub and get the live 14-day window
112
+ df = fetch_traffic_data(active_token)
113
+
114
+ # Replace any infinity or NaN values before JSON serialisation
115
+ df = df.replace([float('inf'), float('-inf')], None).where(pd.notnull(df), None)
116
+
117
+ # Transform the DataFrame into the array of objects the React app expects
118
+ payload = build_react_payload(df)
119
+ return payload
120
+
121
+
122
+ @app.post("/api/upload-csv")
123
+ def upload_csv(file: UploadFile = File(...)):
124
+ # Accept a user-uploaded CSV and convert it to the same format as the API response
125
+ try:
126
+ df = process_uploaded_csv(file.file)
127
+ df = df.replace([float('inf'), float('-inf')], None).where(pd.notnull(df), None)
128
+ payload = build_react_payload(df)
129
+ return payload
130
+ except Exception as e:
131
+ raise HTTPException(status_code=400, detail=str(e))
132
+
133
+
134
+ # ── Static file serving ───────────────────────────────────────────────────────
135
+ # The React build output lands in gitlytics/static/ after `npm run build`
136
+ frontend_dir = Path(__file__).parent / "static"
137
+
138
+
139
+ @app.get("/")
140
+ def serve_index():
141
+ # Serve the React app's index.html for the root URL
142
+ index_file = frontend_dir / "index.html"
143
+ if index_file.exists():
144
+ return FileResponse(index_file)
145
+ return JSONResponse(
146
+ status_code=503,
147
+ content={"error": "Dashboard not found. Run 'npm run build' in the dashboard directory."}
148
+ )
149
+
150
+
151
+ @app.get("/{full_path:path}")
152
+ 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)
159
+ asset_file = frontend_dir / full_path
160
+ if asset_file.exists() and asset_file.is_file():
161
+ return FileResponse(asset_file)
162
+
163
+ # For everything else (like /repos/my-repo), hand control to React Router
164
+ index_file = frontend_dir / "index.html"
165
+ if index_file.exists():
166
+ return FileResponse(index_file)
167
+
168
+ return JSONResponse(
169
+ status_code=503,
170
+ content={"error": "Dashboard not found. Run 'npm run build' in the dashboard directory."}
171
+ )
172
+
173
+
174
+ # Mount the /assets directory for compiled JS and CSS — must come after route definitions
175
+ assets_dir = frontend_dir / "assets"
176
+ if assets_dir.exists():
177
+ app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")