Skip to content
This repository was archived by the owner on Sep 2, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Send query parameters in addition to the path.

## [1.0.2] - 2022-01-16

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def my_apilytics_middleware(request, get_response):
with ApilyticsSender(
api_key=api_key,
path=request.path,
query=request.query_string,
method=request.method,
) as sender:
response = get_response(request)
Expand Down
6 changes: 6 additions & 0 deletions apilytics/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ApilyticsSender:
with ApilyticsSender(
api_key="<your-api-key>",
path=request.path,
query=request.query_string,
method=request.method,
) as sender:
response = get_response(request)
Expand All @@ -45,6 +46,7 @@ def __init__(
api_key: str,
path: str,
method: str,
query: Optional[str] = None,
apilytics_integration: Optional[str] = None,
integrated_library: Optional[str] = None,
) -> None:
Expand All @@ -54,6 +56,8 @@ def __init__(
Args:
api_key: The API key for your Apilytics origin.
path: Path of the user's HTTP request, e.g. "/foo/bar/123".
query: Optional query string of the user's HTTP request e.g. "key=val&other=123".
An empty string and None are treated equally. Can have an optional "?" at the start.
method: Method of the user's HTTP request, e.g. "GET".
apilytics_integration: Name of the Apilytics integration that's calling this,
e.g. "apilytics-python-django". No need to pass this when calling from user code.
Expand All @@ -63,6 +67,7 @@ def __init__(
self._api_key = api_key
self._path = path
self._method = method
self._query = query
self._status_code: Optional[int] = None

self._apilytics_version = self._apilytics_version_template.format(
Expand Down Expand Up @@ -115,6 +120,7 @@ def _send_metrics(self) -> None:
)
data = {
"path": self._path,
**({"query": self._query} if self._query else {}),
"method": self._method,
"statusCode": self._status_code,
"timeMillis": (self._end_time_ns - self._start_time_ns) // 1_000_000,
Expand Down
3 changes: 2 additions & 1 deletion apilytics/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ def __call__(self, request: django.http.HttpRequest) -> django.http.HttpResponse
with apilytics.core.ApilyticsSender(
api_key=self.api_key,
path=request.path,
method=request.method or "",
query=request.META.get("QUERY_STRING"),
method=request.method or "", # Typed as Optional, should never be None.
apilytics_integration="apilytics-python-django",
integrated_library=f"django/{django.__version__}",
) as sender:
Expand Down
1 change: 1 addition & 0 deletions apilytics/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async def dispatch(
with apilytics.core.ApilyticsSender(
api_key=self.api_key,
path=request.url.path,
query=request.url.query,
method=request.method,
apilytics_integration="apilytics-python-fastapi",
integrated_library=f"fastapi/{fastapi.__version__}",
Expand Down
3 changes: 2 additions & 1 deletion tests/django/test_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_middleware_should_call_apilytics_api(
assert isinstance(data["timeMillis"], int)


def test_middleware_should_not_send_query_params(
def test_middleware_should_send_query_params(
mocked_urlopen: unittest.mock.MagicMock,
) -> None:
client.handler.load_middleware()
Expand All @@ -55,6 +55,7 @@ def test_middleware_should_not_send_query_params(
data = tests.conftest.decode_request_data(call_kwargs["data"])
assert data["method"] == "POST"
assert data["path"] == "/dummy/123/path/"
assert data["query"] == "param=foo&param2=bar"
assert data["statusCode"] == 201
assert isinstance(data["timeMillis"], int)

Expand Down
3 changes: 2 additions & 1 deletion tests/fastapi/test_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_middleware_should_call_apilytics_api(
assert isinstance(data["timeMillis"], int)


def test_middleware_should_not_send_query_params(
def test_middleware_should_send_query_params(
mocked_urlopen: unittest.mock.MagicMock,
) -> None:
response = client.post("/dummy/123/path/?param=foo&param2=bar")
Expand All @@ -54,6 +54,7 @@ def test_middleware_should_not_send_query_params(
data = tests.conftest.decode_request_data(call_kwargs["data"])
assert data["method"] == "POST"
assert data["path"] == "/dummy/123/path/"
assert data["query"] == "param=foo&param2=bar"
assert data["statusCode"] == 201
assert isinstance(data["timeMillis"], int)

Expand Down
48 changes: 48 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,54 @@ def test_apilytics_sender_should_call_apilytics_api(
assert isinstance(data["timeMillis"], int)


def test_middleware_should_send_query_params(
mocked_urlopen: unittest.mock.MagicMock,
) -> None:
with apilytics.core.ApilyticsSender(
api_key="dummy-key",
path="/path",
query="key=value?other=123",
method="PUT",
) as sender:
sender.set_response_info(status_code=200)

assert mocked_urlopen.call_count == 1
__, call_kwargs = mocked_urlopen.call_args
data = tests.conftest.decode_request_data(call_kwargs["data"])
assert data["path"] == "/path"
assert data["query"] == "key=value?other=123"


def test_middleware_should_not_send_empty_query_params(
mocked_urlopen: unittest.mock.MagicMock,
) -> None:
with apilytics.core.ApilyticsSender(
api_key="dummy-key",
path="/",
query="",
method="GET",
) as sender:
sender.set_response_info(status_code=200)

assert mocked_urlopen.call_count == 1
__, call_kwargs = mocked_urlopen.call_args
data = tests.conftest.decode_request_data(call_kwargs["data"])
assert "query" not in data

with apilytics.core.ApilyticsSender(
api_key="dummy-key",
path="/",
query=None,
method="GET",
) as sender:
sender.set_response_info(status_code=200)

assert mocked_urlopen.call_count == 2
__, call_kwargs = mocked_urlopen.call_args
data = tests.conftest.decode_request_data(call_kwargs["data"])
assert "query" not in data


@unittest.mock.patch(
"apilytics.core.urllib.request.urlopen",
side_effect=urllib.error.URLError("testing"),
Expand Down