-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds visits find and find_one (#163)
- Loading branch information
Showing
6 changed files
with
499 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass, make_dataclass | ||
from typing import Generator, List | ||
|
||
import requests | ||
|
||
# The maximum page size supported by the API. | ||
_MAX_PAGE_SIZE = 500 | ||
|
||
|
||
@dataclass | ||
class CursorPage: | ||
paging: dict | ||
results: List[dict] | ||
|
||
|
||
class CursorPaginator: | ||
def __init__(self, session: requests.Session, url: str, params: dict = {}) -> None: | ||
self.session = session | ||
self.url = url | ||
self.params = params | ||
|
||
def fetch_results(self) -> List[dict]: | ||
"""Fetch results. | ||
Collects all results from all pages. | ||
Returns | ||
------- | ||
List[dict] | ||
A coalesced list of all results. | ||
""" | ||
results = [] | ||
for page in self.fetch_pages(): | ||
results.extend(page.results) | ||
return results | ||
|
||
def fetch_pages(self) -> Generator[CursorPage, None, None]: | ||
"""Fetch pages. | ||
Yields | ||
------ | ||
Generator[Page, None, None] | ||
""" | ||
next = None | ||
while True: | ||
page = self.fetch_page(next) | ||
yield page | ||
cursors: dict = page.paging.get("cursors", {}) | ||
next = cursors.get("next") | ||
if not next: | ||
# stop if a next page is not defined | ||
return | ||
|
||
def fetch_page(self, next: str | None = None) -> CursorPage: | ||
"""Fetch a page. | ||
Parameters | ||
---------- | ||
next : str | None, optional | ||
the next page identifier or None to fetch the first page, by default None | ||
Returns | ||
------- | ||
Page | ||
""" | ||
params = { | ||
**self.params, | ||
"next": next, | ||
"limit": _MAX_PAGE_SIZE, | ||
} | ||
response = self.session.get(self.url, params=params) | ||
return CursorPage(**response.json()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
from __future__ import annotations | ||
|
||
from typing import List, overload | ||
|
||
from . import urls | ||
|
||
from .cursors import CursorPaginator | ||
from .resources import Resource, Resources | ||
|
||
|
||
class Visit(Resource): | ||
@property | ||
def content_guid(self) -> str: | ||
"""The associated unique content identifier. | ||
Returns | ||
------- | ||
str | ||
""" | ||
return self["content_guid"] | ||
|
||
@property | ||
def user_guid(self) -> str: | ||
"""The associated unique user identifier. | ||
Returns | ||
------- | ||
str | ||
""" | ||
return self["user_guid"] | ||
|
||
@property | ||
def rendering_id(self) -> int | None: | ||
"""The render id associated with the visit. | ||
Returns | ||
------- | ||
int | None | ||
The render id, or None if the associated content type is static. | ||
""" | ||
return self["rendering_id"] | ||
|
||
@property | ||
def bundle_id(self) -> int: | ||
"""The bundle id associated with the visit. | ||
Returns | ||
------- | ||
int | ||
""" | ||
return self["bundle_id"] | ||
|
||
@property | ||
def variant_key(self) -> str | None: | ||
"""The variant key associated with the visit. | ||
Returns | ||
------- | ||
str | None | ||
The variant key, or None if the associated content type is static. | ||
""" | ||
return self.get("variant_key") | ||
|
||
@property | ||
def time(self) -> str: | ||
"""The visit timestamp. | ||
Returns | ||
------- | ||
str | ||
""" | ||
return self["time"] | ||
|
||
@property | ||
def data_version(self) -> int: | ||
"""The data version. | ||
Returns | ||
------- | ||
int | ||
""" | ||
return self["data_version"] | ||
|
||
@property | ||
def path(self) -> str: | ||
"""The path requested by the user. | ||
Returns | ||
------- | ||
str | ||
""" | ||
return self["path"] | ||
|
||
|
||
class Visits(Resources): | ||
@overload | ||
def find( | ||
self, | ||
content_guid: str = ..., | ||
min_data_version: int = ..., | ||
start: str = ..., | ||
end: str = ..., | ||
) -> List[Visit]: | ||
"""Find visits. | ||
Parameters | ||
---------- | ||
content_guid : str, optional | ||
Filter by an associated unique content identifer, by default ... | ||
min_data_version : int, optional | ||
Filter by a minimum data version, by default ... | ||
start : str, optional | ||
Filter by the start time, by default ... | ||
end : str, optional | ||
Filter by the end time, by default ... | ||
Returns | ||
------- | ||
List[Visit] | ||
""" | ||
... | ||
|
||
@overload | ||
def find(self, *args, **kwargs) -> List[Visit]: | ||
"""Find visits. | ||
Returns | ||
------- | ||
List[Visit] | ||
""" | ||
... | ||
|
||
def find(self, *args, **kwargs) -> List[Visit]: | ||
"""Find visits. | ||
Returns | ||
------- | ||
List[Visit] | ||
""" | ||
params = dict(*args, **kwargs) | ||
params = rename_params(params) | ||
|
||
path = "/v1/instrumentation/content/visits" | ||
url = urls.append_path(self.config.url, path) | ||
paginator = CursorPaginator(self.session, url, params=params) | ||
results = paginator.fetch_results() | ||
return [ | ||
Visit( | ||
config=self.config, | ||
session=self.session, | ||
**result, | ||
) | ||
for result in results | ||
] | ||
|
||
@overload | ||
def find_one( | ||
self, | ||
content_guid: str = ..., | ||
min_data_version: int = ..., | ||
start: str = ..., | ||
end: str = ..., | ||
) -> Visit | None: | ||
"""Find a visit. | ||
Parameters | ||
---------- | ||
content_guid : str, optional | ||
Filter by an associated unique content identifer, by default ... | ||
min_data_version : int, optional | ||
Filter by a minimum data version, by default ... | ||
start : str, optional | ||
Filter by the start time, by default ... | ||
end : str, optional | ||
Filter by the end time, by default ... | ||
Returns | ||
------- | ||
Visit | None | ||
_description_ | ||
""" | ||
... | ||
|
||
@overload | ||
def find_one(self, *args, **kwargs) -> Visit | None: | ||
"""Find a visit. | ||
Returns | ||
------- | ||
Visit | None | ||
""" | ||
... | ||
|
||
def find_one(self, *args, **kwargs) -> Visit | None: | ||
"""Find a visit. | ||
Returns | ||
------- | ||
Visit | None | ||
""" | ||
params = dict(*args, **kwargs) | ||
params = rename_params(params) | ||
path = "/v1/instrumentation/content/visits" | ||
url = urls.append_path(self.config.url, path) | ||
paginator = CursorPaginator(self.session, url, params=params) | ||
pages = paginator.fetch_pages() | ||
results = (result for page in pages for result in page.results) | ||
visits = ( | ||
Visit( | ||
config=self.config, | ||
session=self.session, | ||
**result, | ||
) | ||
for result in results | ||
) | ||
return next(visits, None) | ||
|
||
|
||
def rename_params(params: dict) -> dict: | ||
"""Rename params from the internal to the external signature. | ||
The API accepts `from` as a querystring parameter. Since `from` is a reserved word in Python, the SDK uses the name `start` instead. The querystring parameter `to` takes the same form for consistency. | ||
Parameters | ||
---------- | ||
params : dict | ||
Returns | ||
------- | ||
dict | ||
""" | ||
if "start" in params: | ||
params["from"] = params["start"] | ||
del params["start"] | ||
|
||
if "end" in params: | ||
params["to"] = params["end"] | ||
del params["end"] | ||
|
||
return params |
10 changes: 10 additions & 0 deletions
10
...s/posit/connect/__api__/v1/instrumentation/content/visits?limit=500&next=23948901087.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"paging": { | ||
"cursors": { | ||
"previous": "23948901087" | ||
}, | ||
"first": "http://localhost:3443/__api__/v1/instrumentation/content/visits", | ||
"previous": "http://localhost:3443/__api__/v1/instrumentation/content/visits?previous=23948901087" | ||
}, | ||
"results": [] | ||
} |
24 changes: 24 additions & 0 deletions
24
tests/posit/connect/__api__/v1/instrumentation/content/visits?limit=500.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"paging": { | ||
"cursors": { | ||
"previous": "23948901087", | ||
"next": "23948901087" | ||
}, | ||
"first": "http://localhost:3443/__api__/v1/instrumentation/content/visits", | ||
"previous": "http://localhost:3443/__api__/v1/instrumentation/content/visits?previous=23948901087", | ||
"next": "http://localhost:3443/__api__/v1/instrumentation/content/visits?next=23948901087", | ||
"last": "http://localhost:3443/__api__/v1/instrumentation/content/visits?last=true" | ||
}, | ||
"results": [ | ||
{ | ||
"content_guid": "bd1d2285-6c80-49af-8a83-a200effe3cb3", | ||
"user_guid": "08e3a41d-1f8e-47f2-8855-f05ea3b0d4b2", | ||
"variant_key": "HidI2Kwq", | ||
"rendering_id": 7, | ||
"bundle_id": 33, | ||
"time": "2018-09-15T18:00:00-05:00", | ||
"data_version": 1, | ||
"path": "/logs" | ||
} | ||
] | ||
} |
Oops, something went wrong.