cloudflare-analytics 0.1.1__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.
@@ -0,0 +1,26 @@
1
+ """
2
+ Cloudflare Analytics GraphQL API client.
3
+
4
+ API docs: https://developers.cloudflare.com/analytics/graphql-api/
5
+ """
6
+
7
+ import logging
8
+ import os
9
+
10
+ from .client import CloudflareAnalyticsClient, GraphQLResponse, get_analytics_client
11
+
12
+ logging.basicConfig(
13
+ level=os.environ.get("LOG_LEVEL", "INFO").upper(),
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ __all__ = [
19
+ "CloudflareAnalyticsClient",
20
+ "GraphQLResponse",
21
+ "get_analytics_client",
22
+ ]
23
+
24
+
25
+ def main():
26
+ logger.info("Cloudflare Analytics Client - use as a library")
@@ -0,0 +1,157 @@
1
+ """
2
+ Cloudflare Analytics GraphQL API client.
3
+
4
+ API docs: https://developers.cloudflare.com/analytics/graphql-api/
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ import httpx
11
+ from pydantic import BaseModel
12
+ from tenacity import (
13
+ before_sleep_log,
14
+ retry,
15
+ retry_if_exception_type,
16
+ stop_after_attempt,
17
+ wait_exponential,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ _analytics_client: "CloudflareAnalyticsClient | None" = None
23
+
24
+
25
+ class GraphQLResponse(BaseModel):
26
+ data: dict[str, Any] | None = None
27
+ errors: list[dict[str, Any]] | None = None
28
+
29
+
30
+ class CloudflareAnalyticsClient:
31
+ """
32
+ Client for interacting with the Cloudflare GraphQL Analytics API using httpx.
33
+
34
+ Example usage:
35
+
36
+ from cloudflare_analytics import CloudflareAnalyticsClient
37
+
38
+ client = CloudflareAnalyticsClient(api_token="your_token")
39
+
40
+ query = '''
41
+ query GetStreamMinutes($accountTag: string!, $start: Date, $end: Date) {
42
+ viewer {
43
+ accounts(filter: { accountTag: $accountTag }) {
44
+ streamMinutesViewedAdaptiveGroups(
45
+ filter: { date_geq: $start, date_lt: $end }
46
+ orderBy: [date_ASC]
47
+ ) {
48
+ dimensions { date }
49
+ sum { minutesViewed }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ '''
55
+
56
+ response = client.query(
57
+ query,
58
+ variables={
59
+ "accountTag": "your_account_id",
60
+ "start": "2025-10-01",
61
+ "end": "2025-10-28"
62
+ }
63
+ )
64
+
65
+ if response.errors:
66
+ logger.error("graphql errors", extra={"errors": response.errors})
67
+ elif response.data:
68
+ groups = response.data["viewer"]["accounts"][0]["streamMinutesViewedAdaptiveGroups"]
69
+ for group in groups:
70
+ minutes = group["sum"]["minutesViewed"]
71
+ date = group["dimensions"]["date"]
72
+ logger.info("stream data", extra={"date": date, "minutes": minutes})
73
+ """
74
+
75
+ def __init__(self, api_token: str):
76
+ if not api_token:
77
+ raise ValueError("API token must be provided.")
78
+
79
+ self.api_token = api_token
80
+ self.base_url = "https://api.cloudflare.com/client/v4"
81
+
82
+ @retry(
83
+ stop=stop_after_attempt(6),
84
+ wait=wait_exponential(multiplier=1, min=0, max=32),
85
+ retry=retry_if_exception_type(httpx.HTTPError),
86
+ before_sleep=before_sleep_log(logger, logging.INFO),
87
+ reraise=True,
88
+ )
89
+ def _make_request(self, payload: dict[str, Any]) -> dict[str, Any]:
90
+ """
91
+ Internal method to make the HTTP POST request to the GraphQL endpoint.
92
+
93
+ Args:
94
+ payload: Dictionary containing query and optional variables
95
+
96
+ Returns:
97
+ The JSON response as a dictionary
98
+ """
99
+ url = f"{self.base_url}/graphql"
100
+ headers = {"Authorization": f"Bearer {self.api_token}"}
101
+
102
+ logger.debug(
103
+ "cloudflare graphql request", extra={"url": url, "payload": payload}
104
+ )
105
+
106
+ response = httpx.post(url, json=payload, headers=headers)
107
+ response.raise_for_status()
108
+
109
+ json_data = response.json()
110
+ logger.info(
111
+ "cloudflare graphql response", extra={"status": response.status_code}
112
+ )
113
+
114
+ return json_data
115
+
116
+ def query(
117
+ self, query: str, variables: dict[str, Any] | None = None
118
+ ) -> GraphQLResponse:
119
+ """Execute a GraphQL query against the Cloudflare Analytics API.
120
+
121
+ Args:
122
+ query: GraphQL query string
123
+ variables: Optional variables for the query
124
+
125
+ Returns:
126
+ GraphQLResponse containing data or errors
127
+
128
+ Raises:
129
+ httpx.HTTPError: If the request fails after retries
130
+ """
131
+ payload: dict[str, Any] = {"query": query}
132
+ if variables:
133
+ payload["variables"] = variables
134
+
135
+ json_data = self._make_request(payload)
136
+
137
+ return GraphQLResponse(
138
+ data=json_data.get("data"), errors=json_data.get("errors")
139
+ )
140
+
141
+
142
+ def get_analytics_client(api_token: str) -> CloudflareAnalyticsClient:
143
+ """
144
+ Get or create the global Cloudflare Analytics client instance.
145
+
146
+ Args:
147
+ api_token: Cloudflare API token
148
+
149
+ Returns:
150
+ CloudflareAnalyticsClient: The configured analytics client instance
151
+ """
152
+ global _analytics_client
153
+
154
+ if _analytics_client is None:
155
+ _analytics_client = CloudflareAnalyticsClient(api_token=api_token)
156
+
157
+ return _analytics_client
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.3
2
+ Name: cloudflare-analytics
3
+ Version: 0.1.1
4
+ Summary: Cloudflare Analytics GraphQL API client
5
+ Keywords: cloudflare,analytics,graphql
6
+ Author: Michael Bianco
7
+ Author-email: Michael Bianco <mike@mikebian.co>
8
+ Requires-Dist: httpx>=0.27.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Requires-Dist: tenacity>=8.0.0
11
+ Requires-Python: >=3.10
12
+ Project-URL: Repository, https://github.com/iloveitaly/cloudflare-analytics
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Python Cloudflare Analytics Client
16
+
17
+ A Python client for interacting with the [Cloudflare GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/).
18
+
19
+ > **Note:** The official Cloudflare Python library does not support the GraphQL Analytics API endpoints. This library provides dedicated support for querying Cloudflare analytics data.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ uv add cloudflare-analytics
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from cloudflare_analytics import client
31
+
32
+ # Execute a GraphQL query
33
+ query = '''
34
+ query GetStreamMinutes($accountTag: string!, $start: Date, $end: Date) {
35
+ viewer {
36
+ accounts(filter: { accountTag: $accountTag }) {
37
+ streamMinutesViewedAdaptiveGroups(
38
+ filter: { date_geq: $start, date_lt: $end }
39
+ orderBy: [date_ASC]
40
+ ) {
41
+ dimensions { date }
42
+ sum { minutesViewed }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ '''
48
+
49
+ response = client.query(
50
+ query,
51
+ variables={
52
+ "accountTag": "your_account_id",
53
+ "start": "2025-10-01",
54
+ "end": "2025-10-28"
55
+ },
56
+ api_token="your_cloudflare_api_token"
57
+ )
58
+
59
+ if response.errors:
60
+ print(f"Errors: {response.errors}")
61
+ elif response.data:
62
+ groups = response.data["viewer"]["accounts"][0]["streamMinutesViewedAdaptiveGroups"]
63
+ for group in groups:
64
+ minutes = group["sum"]["minutesViewed"]
65
+ date = group["dimensions"]["date"]
66
+ print(f"Date: {date}, Minutes: {minutes}")
67
+ ```
68
+
69
+ ## Features
70
+
71
+ - Simple, clean API for Cloudflare GraphQL Analytics
72
+ - Built-in retry logic with exponential backoff
73
+ - Type-safe responses using Pydantic models
74
+ - Comprehensive error handling
75
+
76
+ ## [MIT License](LICENSE.md)
@@ -0,0 +1,6 @@
1
+ cloudflare_analytics/__init__.py,sha256=YjAPfeP55HEhOmpQfevStNG1svRvgJ6k7ScTr7kOoDQ,525
2
+ cloudflare_analytics/client.py,sha256=GGcl7fZtCJMKldi91AMxe1jOlh6G6I_ni4KC-2VASys,4517
3
+ cloudflare_analytics-0.1.1.dist-info/WHEEL,sha256=Pi5uDq5Fdo_Rr-HD5h9BiPn9Et29Y9Sh8NhcJNnFU1c,79
4
+ cloudflare_analytics-0.1.1.dist-info/entry_points.txt,sha256=GHeI44VGvKgmiH2T36QidTL4bZDbkcCqjPUi0FYOEdk,68
5
+ cloudflare_analytics-0.1.1.dist-info/METADATA,sha256=HlgNWdQDxYIvDy8K2svZgEtXamvq7OAB8GYdXZ13l0o,2086
6
+ cloudflare_analytics-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.17
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ cloudflare-analytics = cloudflare_analytics:main
3
+