Skip to content
This repository was archived by the owner on Sep 2, 2022. It is now read-only.

Commit 22b09e3

Browse files
committed
Send Apilytics version info together with metrics
1 parent 4f848b4 commit 22b09e3

9 files changed

Lines changed: 83 additions & 2 deletions

File tree

.github/workflows/cd.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ jobs:
4040
poetry version ${{ github.event.inputs.release-type }}
4141
echo "::set-output name=version::$(poetry version --short)"
4242
43+
- name: "Bump apilytics.__version__ to new version"
44+
run: sed -i 's/^__version__ *=.*/__version__ = "${{ steps.bump.outputs.version }}"/' apilytics/__init__.py
45+
4346
- name: "Build the wheel"
4447
run: poetry build
4548

@@ -62,7 +65,7 @@ jobs:
6265
- name: "Commit and tag the changes"
6366
uses: EndBug/add-and-commit@8c12ff729a98cfbcd3fe38b49f55eceb98a5ec02 # v7.5.0
6467
with:
65-
add: '["pyproject.toml", "CHANGELOG.md"]'
68+
add: '["pyproject.toml", "apilytics/__init__.py", "CHANGELOG.md"]'
6669
message: 'Release ${{ steps.bump.outputs.version }}'
6770
tag: 'v${{ steps.bump.outputs.version }} --annotate --file /dev/null'
6871
default_author: github_actions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Send Apilytics version info together with metrics.
13+
1014
## [1.0.1] - 2022-01-12
1115

1216
### Fixed

apilytics/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "1.0.1"

apilytics/core.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import concurrent.futures
22
import json
3+
import platform
34
import time
45
import types
56
import urllib.error
67
import urllib.request
78
from typing import ClassVar, Optional, Type
89

10+
import apilytics
11+
912
__all__ = ["ApilyticsSender"]
1013

1114

@@ -32,20 +35,42 @@ class ApilyticsSender:
3235

3336
_executor: ClassVar[concurrent.futures.Executor]
3437

35-
def __init__(self, *, api_key: str, path: str, method: str) -> None:
38+
_apilytics_version_template: ClassVar[
39+
str
40+
] = f"{{integration}}/{apilytics.__version__};python/{platform.python_version()}"
41+
42+
def __init__(
43+
self,
44+
*,
45+
api_key: str,
46+
path: str,
47+
method: str,
48+
apilytics_integration: Optional[str] = None,
49+
integrated_library: Optional[str] = None,
50+
) -> None:
3651
"""
3752
Initialize the context manager with info from the HTTP request object.
3853
3954
Args:
4055
api_key: The API key for your Apilytics origin.
4156
path: Path of the user's HTTP request, e.g. "/foo/bar/123".
4257
method: Method of the user's HTTP request, e.g. "GET".
58+
apilytics_integration: Name of the Apilytics integration that's calling this,
59+
e.g. "apilytics-python-django". No need to pass this when calling from user code.
60+
integrated_library: Name and version of the integration that this is used in,
61+
e.g. "django/3.2.1". No need to pass this when calling from user code.
4362
"""
4463
self._api_key = api_key
4564
self._path = path
4665
self._method = method
4766
self._status_code: Optional[int] = None
4867

68+
self._apilytics_version = self._apilytics_version_template.format(
69+
integration=apilytics_integration or "apilytics-python-core"
70+
)
71+
if integrated_library:
72+
self._apilytics_version += f";{integrated_library}"
73+
4974
def __enter__(self) -> "ApilyticsSender":
5075
"""Start the timer, measuring how long the ``with`` block takes to execute."""
5176
self._start_time_ns = time.perf_counter_ns()
@@ -85,6 +110,7 @@ def _send_metrics(self) -> None:
85110
headers={
86111
"Content-Type": "application/json",
87112
"X-API-Key": self._api_key,
113+
"Apilytics-Version": self._apilytics_version,
88114
},
89115
)
90116
data = {

apilytics/django.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Callable
22

3+
import django
34
import django.conf
45
import django.core.exceptions
56
import django.http
@@ -42,6 +43,8 @@ def __call__(self, request: django.http.HttpRequest) -> django.http.HttpResponse
4243
api_key=self.api_key,
4344
path=request.path,
4445
method=request.method or "",
46+
apilytics_integration="apilytics-python-django",
47+
integrated_library=f"django/{django.__version__}",
4548
) as sender:
4649
response = self.get_response(request)
4750
sender.set_response_info(status_code=response.status_code)

apilytics/fastapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ async def dispatch(
5252
api_key=self.api_key,
5353
path=request.url.path,
5454
method=request.method,
55+
apilytics_integration="apilytics-python-fastapi",
56+
integrated_library=f"fastapi/{fastapi.__version__}",
5557
) as sender:
5658
response = await call_next(request)
5759
sender.set_response_info(status_code=response.status_code)

tests/django/test_django.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import platform
12
import unittest.mock
23

34
import django.test
45

6+
import apilytics
57
import tests.conftest
68

79
client = django.test.client.Client()
@@ -30,6 +32,7 @@ def test_middleware_should_call_apilytics_api(
3032
# urllib calls `capitalize()` on the header keys.
3133
"Content-type": "application/json",
3234
"X-api-key": "dummy-key",
35+
"Apilytics-version": f"apilytics-python-django/{apilytics.__version__};python/{platform.python_version()};django/{django.__version__}",
3336
}
3437

3538
data = tests.conftest.decode_request_data(call_kwargs["data"])

tests/fastapi/test_fastapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import platform
12
import unittest.mock
23

34
import fastapi.middleware
@@ -31,6 +32,7 @@ def test_middleware_should_call_apilytics_api(
3132
# urllib calls `capitalize()` on the header keys.
3233
"Content-type": "application/json",
3334
"X-api-key": "dummy-key",
35+
"Apilytics-version": f"apilytics-python-fastapi/{apilytics.__version__};python/{platform.python_version()};fastapi/{fastapi.__version__}",
3436
}
3537

3638
data = tests.conftest.decode_request_data(call_kwargs["data"])

tests/test_core.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
1+
import platform
12
import unittest.mock
23
import urllib.error
34

45
import apilytics.core
6+
import tests.conftest
7+
8+
9+
def test_apilytics_sender_should_call_apilytics_api(
10+
mocked_urlopen: unittest.mock.MagicMock,
11+
) -> None:
12+
with apilytics.core.ApilyticsSender(
13+
api_key="dummy-key",
14+
path="/",
15+
method="GET",
16+
) as sender:
17+
sender.set_response_info(status_code=200)
18+
19+
assert mocked_urlopen.call_count == 1
20+
21+
call_args, call_kwargs = mocked_urlopen.call_args
22+
assert not call_args
23+
assert call_kwargs.keys() == {"url", "data"}
24+
25+
api_request = call_kwargs["url"]
26+
27+
assert api_request.full_url == "https://www.apilytics.io/api/v1/middleware"
28+
assert api_request.method == "POST"
29+
assert api_request.headers == {
30+
# urllib calls `capitalize()` on the header keys.
31+
"Content-type": "application/json",
32+
"X-api-key": "dummy-key",
33+
"Apilytics-version": f"apilytics-python-core/{apilytics.__version__};python/{platform.python_version()}",
34+
}
35+
36+
data = tests.conftest.decode_request_data(call_kwargs["data"])
37+
assert data.keys() == {"path", "method", "statusCode", "timeMillis"}
38+
assert data["path"] == "/"
39+
assert data["method"] == "GET"
40+
assert data["statusCode"] == 200
41+
assert isinstance(data["timeMillis"], int)
542

643

744
@unittest.mock.patch(

0 commit comments

Comments
 (0)