Skip to content

Commit cc7111a

Browse files
committed
complete implementation
1 parent c32ce39 commit cc7111a

File tree

11 files changed

+281
-96
lines changed

11 files changed

+281
-96
lines changed

src/posit/connect/metrics/hits.py

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,105 @@
1+
from __future__ import annotations
2+
13
from typing_extensions import (
24
Iterable,
3-
List,
4-
Literal,
55
Protocol,
6-
overload,
76
)
87

9-
from ..context import requires
10-
from ..resources import Resource, ResourceSequence
8+
from ..resources import Resource, ResourceSequence, _ResourceSequence
9+
from .rename_params import rename_params
1110

1211

1312
class Hit(Resource, Protocol):
1413
pass
1514

1615

1716
class Hits(ResourceSequence[Hit], Protocol):
18-
pass
17+
def fetch(
18+
self,
19+
*,
20+
start: str = ...,
21+
end: str = ...,
22+
) -> Iterable[Hit]:
23+
"""
24+
Fetch all content hit records matching the specified conditions.
25+
26+
Parameters
27+
----------
28+
start : str, not required
29+
The timestamp that starts the time window of interest in RFC 3339 format.
30+
Any hit information that happened prior to this timestamp will not be returned.
31+
Example: "2025-05-01T00:00:00Z"
32+
end : str, not required
33+
The timestamp that ends the time window of interest in RFC 3339 format.
34+
Any hit information that happened after this timestamp will not be returned.
35+
Example: "2025-05-02T00:00:00Z"
36+
37+
Returns
38+
-------
39+
Iterable[Hit]
40+
All content hit records matching the specified conditions.
41+
"""
42+
...
43+
44+
def find_by(
45+
self,
46+
*,
47+
id: str = ..., # noqa: A002
48+
content_guid: str = ...,
49+
user_guid: str = ...,
50+
timestamp: str = ...,
51+
) -> Hit | None:
52+
"""
53+
Find the first hit record matching the specified conditions.
54+
55+
There is no implied ordering, so if order matters, you should specify it yourself.
56+
57+
Parameters
58+
----------
59+
id : str, not required
60+
The ID of the activity record.
61+
content_guid : str, not required
62+
The GUID, in RFC4122 format, of the content this information pertains to.
63+
user_guid : str, not required
64+
The GUID, in RFC4122 format, of the user that visited the content.
65+
May be null when the target content does not require a user session.
66+
timestamp : str, not required
67+
The timestamp, in RFC 3339 format, when the user visited the content.
68+
69+
Returns
70+
-------
71+
Hit | None
72+
The first hit record matching the specified conditions, or `None` if no such record exists.
73+
"""
74+
...
75+
76+
77+
class _Hits(_ResourceSequence, Hits):
78+
def fetch(
79+
self,
80+
**kwargs,
81+
) -> Iterable[Hit]:
82+
"""
83+
Fetch all content hit records matching the specified conditions.
84+
85+
Parameters
86+
----------
87+
start : str, not required
88+
The timestamp that starts the time window of interest in RFC 3339 format.
89+
Any hit information that happened prior to this timestamp will not be returned.
90+
Example: "2025-05-01T00:00:00Z"
91+
end : str, not required
92+
The timestamp that ends the time window of interest in RFC 3339 format.
93+
Any hit information that happened after this timestamp will not be returned.
94+
Example: "2025-05-02T00:00:00Z"
95+
96+
Returns
97+
-------
98+
Iterable[Hit]
99+
All content hit records matching the specified conditions.
100+
"""
101+
params = rename_params(kwargs)
19102

103+
result = super().fetch(**params)
20104

21-
# TODO:
22-
# fetch, find_by documentation
23-
# - fetch function args are gonna be the query params
24-
# - find_by is the object props
25-
# if the server fails with extra query params that'd be bad.
26-
# tests
27-
# - reference packages_test file
105+
return result

src/posit/connect/metrics/metrics.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
"""Metric resources."""
22

3-
import posixpath
4-
53
from .. import resources
64
from ..context import requires
7-
from .hits import Hits
5+
from .hits import Hits, _Hits
86
from .usage import Usage
97

108

@@ -24,4 +22,4 @@ def usage(self) -> Usage:
2422
@property
2523
@requires(version="2025.04.0")
2624
def hits(self) -> Hits:
27-
return resources._ResourceSequence(self._ctx, "v1/instrumentation/content/hits", uid="id")
25+
return _Hits(self._ctx, "v1/instrumentation/content/hits", uid="id")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
def rename_params(params: dict) -> dict:
2+
"""Rename params from the internal to the external signature.
3+
4+
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.
5+
6+
Parameters
7+
----------
8+
params : dict
9+
10+
Returns
11+
-------
12+
dict
13+
"""
14+
if "start" in params:
15+
params["from"] = params["start"]
16+
del params["start"]
17+
18+
if "end" in params:
19+
params["to"] = params["end"]
20+
del params["end"]
21+
22+
return params

src/posit/connect/metrics/shiny_usage.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ..cursors import CursorPaginator
66
from ..resources import BaseResource, Resources
7+
from .rename_params import rename_params
78

89

910
class ShinyUsageEvent(BaseResource):
@@ -171,27 +172,3 @@ def find_one(self, **kwargs) -> ShinyUsageEvent | None:
171172
for result in results
172173
)
173174
return next(visits, None)
174-
175-
176-
def rename_params(params: dict) -> dict:
177-
"""Rename params from the internal to the external signature.
178-
179-
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.
180-
181-
Parameters
182-
----------
183-
params : dict
184-
185-
Returns
186-
-------
187-
dict
188-
"""
189-
if "start" in params:
190-
params["from"] = params["start"]
191-
del params["start"]
192-
193-
if "end" in params:
194-
params["to"] = params["end"]
195-
del params["end"]
196-
197-
return params

src/posit/connect/metrics/visits.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ..cursors import CursorPaginator
66
from ..resources import BaseResource, Resources
7+
from .rename_params import rename_params
78

89

910
class VisitEvent(BaseResource):
@@ -203,27 +204,3 @@ def find_one(self, **kwargs) -> VisitEvent | None:
203204
for result in results
204205
)
205206
return next(visits, None)
206-
207-
208-
def rename_params(params: dict) -> dict:
209-
"""Rename params from the internal to the external signature.
210-
211-
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.
212-
213-
Parameters
214-
----------
215-
params : dict
216-
217-
Returns
218-
-------
219-
dict
220-
"""
221-
if "start" in params:
222-
params["from"] = params["start"]
223-
del params["start"]
224-
225-
if "end" in params:
226-
params["to"] = params["end"]
227-
del params["end"]
228-
229-
return params

src/posit/connect/resources.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def create(self, **attributes: Any) -> Any:
136136
response = self._ctx.client.post(self._path, json=attributes)
137137
result = response.json()
138138
uid = result[self._uid]
139-
path = posixpath.join(self._path, uid)
139+
path = posixpath.join(self._path, str(uid))
140140
return _Resource(self._ctx, path, **result)
141141

142142
def fetch(self, **conditions) -> Iterable[Any]:
@@ -145,7 +145,7 @@ def fetch(self, **conditions) -> Iterable[Any]:
145145
resources = []
146146
for result in results:
147147
uid = result[self._uid]
148-
path = posixpath.join(self._path, uid)
148+
path = posixpath.join(self._path, str(uid))
149149
resource = _Resource(self._ctx, path, **result)
150150
resources.append(resource)
151151

@@ -188,7 +188,7 @@ def fetch(self, **conditions):
188188
results = page.results
189189
for result in results:
190190
uid = result[self._uid]
191-
path = posixpath.join(self._path, uid)
191+
path = posixpath.join(self._path, str(uid))
192192
resource = _Resource(self._ctx, path, **result)
193193
resources.append(resource)
194194
yield from resources
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"id": 1001,
4+
"content_guid": "bd1d2285-6c80-49af-8a83-a200effe3cb3",
5+
"user_guid": "08e3a41d-1f8e-47f2-8855-f05ea3b0d4b2",
6+
"timestamp": "2025-05-01T10:00:00-05:00",
7+
"data": {
8+
"path": "/dashboard",
9+
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
10+
}
11+
},
12+
{
13+
"id": 1002,
14+
"content_guid": "bd1d2285-6c80-49af-8a83-a200effe3cb3",
15+
"user_guid": "a5e2b41d-3f8e-47f2-9955-f05ea3b0d5c3",
16+
"timestamp": "2025-05-01T10:05:00-05:00",
17+
"data": {
18+
"path": "/dashboard/details",
19+
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
20+
}
21+
}
22+
]

0 commit comments

Comments
 (0)