libtimed 0.6.4__tar.gz → 0.7.3__tar.gz
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.
- {libtimed-0.6.4 → libtimed-0.7.3}/PKG-INFO +6 -3
- {libtimed-0.6.4 → libtimed-0.7.3}/pyproject.toml +35 -11
- {libtimed-0.6.4 → libtimed-0.7.3}/src/libtimed/models.py +5 -17
- {libtimed-0.6.4 → libtimed-0.7.3}/src/libtimed/oidc.py +15 -14
- {libtimed-0.6.4 → libtimed-0.7.3}/src/libtimed/transforms.py +4 -19
- {libtimed-0.6.4 → libtimed-0.7.3}/LICENSE +0 -0
- {libtimed-0.6.4 → libtimed-0.7.3}/README.md +0 -0
- {libtimed-0.6.4 → libtimed-0.7.3}/src/libtimed/__init__.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: libtimed
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Library to intreact with timed webapp.
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: Arthur Deierlein
|
|
6
7
|
Author-email: arthur.deierlein@adfinis.com
|
|
7
8
|
Requires-Python: >=3.10,<4.0
|
|
@@ -9,8 +10,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
15
|
Requires-Dist: inflection (>=0.5.1,<0.6.0)
|
|
13
|
-
Requires-Dist: keyring (>=24.1
|
|
16
|
+
Requires-Dist: keyring (>=24.1,<26.0)
|
|
14
17
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
15
18
|
Requires-Dist: requests-cache (>=1.1.0,<2.0.0)
|
|
16
19
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "libtimed"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.3"
|
|
4
4
|
description = "Library to intreact with timed webapp."
|
|
5
5
|
authors = [
|
|
6
6
|
"Arthur Deierlein <arthur.deierlein@adfinis.com>",
|
|
@@ -12,16 +12,15 @@ packages = [{ include = "libtimed", from = "src" }]
|
|
|
12
12
|
[tool.poetry.dependencies]
|
|
13
13
|
python = "^3.10"
|
|
14
14
|
requests = "^2.31.0"
|
|
15
|
-
keyring = "
|
|
15
|
+
keyring = ">=24.1,<26.0"
|
|
16
16
|
inflection = "^0.5.1"
|
|
17
17
|
requests-cache = "^1.1.0"
|
|
18
18
|
|
|
19
19
|
[tool.poetry.group.dev.dependencies]
|
|
20
|
-
|
|
21
|
-
pytest = "^7.3.2"
|
|
20
|
+
pytest = ">=7.3.2,<9.0.0"
|
|
22
21
|
pytest-coverage = "^0.0"
|
|
23
22
|
pdbpp = "^0.10.3"
|
|
24
|
-
ruff = "
|
|
23
|
+
ruff = ">=0.3.4,<0.5.0"
|
|
25
24
|
|
|
26
25
|
# Dependencies only used in examples
|
|
27
26
|
[tool.poetry.group.example.dependencies]
|
|
@@ -29,9 +28,16 @@ pyfzf = "^0.3.1"
|
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
[tool.ruff]
|
|
32
|
-
line-length =
|
|
33
|
-
select = ["F","E","W","I","N", "D", "B", "Q", "C4", "UP", "PT", "T20", "RET", "RUF", "SLF", "SIM", "C90"]
|
|
31
|
+
line-length = 100
|
|
34
32
|
src = ["src", "examples", "test"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
quote-style = "double"
|
|
36
|
+
indent-style = "space"
|
|
37
|
+
docstring-code-format = true
|
|
38
|
+
|
|
39
|
+
[tool.ruff.lint]
|
|
40
|
+
select = ["F","E","W","I","N", "D", "B", "Q", "C4", "UP", "PT", "T20", "RET", "RUF", "SLF", "SIM", "C90"]
|
|
35
41
|
ignore = [
|
|
36
42
|
# make docstrings optional
|
|
37
43
|
"D100",
|
|
@@ -45,19 +51,37 @@ ignore = [
|
|
|
45
51
|
"D211",
|
|
46
52
|
"D213",
|
|
47
53
|
"RUF012",
|
|
48
|
-
# line length is handled by
|
|
54
|
+
# line length is handled by formatter
|
|
49
55
|
"E501",
|
|
50
56
|
# disable this for now
|
|
51
57
|
"SLF001",
|
|
58
|
+
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
|
59
|
+
"W191",
|
|
60
|
+
"E111",
|
|
61
|
+
"E114",
|
|
62
|
+
"E117",
|
|
63
|
+
"D206",
|
|
64
|
+
"D300",
|
|
65
|
+
"Q000",
|
|
66
|
+
"Q001",
|
|
67
|
+
"Q002",
|
|
68
|
+
"Q003",
|
|
69
|
+
"COM812",
|
|
70
|
+
"COM819",
|
|
71
|
+
"ISC001",
|
|
72
|
+
"ISC002",
|
|
73
|
+
# this conflicts for some reason
|
|
74
|
+
"D203"
|
|
52
75
|
]
|
|
53
76
|
|
|
54
|
-
[tool.ruff.mccabe]
|
|
77
|
+
[tool.ruff.lint.mccabe]
|
|
55
78
|
max-complexity = 8
|
|
56
79
|
|
|
57
|
-
[tool.ruff.extend-per-file-ignores]
|
|
80
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
58
81
|
"examples/*.py" = ["T201"]
|
|
82
|
+
"src/libtimed/oidc.py" = ["T201"]
|
|
59
83
|
|
|
60
|
-
[tool.ruff.isort]
|
|
84
|
+
[tool.ruff.lint.isort]
|
|
61
85
|
known-first-party = ["libtimed"]
|
|
62
86
|
combine-as-imports = true
|
|
63
87
|
|
|
@@ -46,16 +46,10 @@ class BaseModel:
|
|
|
46
46
|
def _deserialize(self, item, included): # noqa: C901
|
|
47
47
|
for key, value in item["attributes"].items():
|
|
48
48
|
transform = next(
|
|
49
|
-
(
|
|
50
|
-
transform
|
|
51
|
-
for name, _, transform in self.__class__.attributes
|
|
52
|
-
if name == key
|
|
53
|
-
),
|
|
49
|
+
(transform for name, _, transform in self.__class__.attributes if name == key),
|
|
54
50
|
None,
|
|
55
51
|
)
|
|
56
|
-
item["attributes"][key] = (
|
|
57
|
-
(transform).deserialize(value) if transform else value
|
|
58
|
-
)
|
|
52
|
+
item["attributes"][key] = (transform).deserialize(value) if transform else value
|
|
59
53
|
relationships = item.get("relationships")
|
|
60
54
|
if not relationships:
|
|
61
55
|
return item
|
|
@@ -79,9 +73,7 @@ class BaseModel:
|
|
|
79
73
|
None,
|
|
80
74
|
)
|
|
81
75
|
if included_item:
|
|
82
|
-
included_model = self.client._type_map[item_type](
|
|
83
|
-
self.client
|
|
84
|
-
)
|
|
76
|
+
included_model = self.client._type_map[item_type](self.client)
|
|
85
77
|
included_items.append(
|
|
86
78
|
included_model._deserialize(included_item, included)
|
|
87
79
|
)
|
|
@@ -130,9 +122,7 @@ class BaseModel:
|
|
|
130
122
|
item = self._deserialize(item, resp.get("included", []))
|
|
131
123
|
return resp if raw else resp.get("data")
|
|
132
124
|
|
|
133
|
-
def post(
|
|
134
|
-
self, attributes: dict | None = None, relationships: dict | None = None
|
|
135
|
-
) -> Response:
|
|
125
|
+
def post(self, attributes: dict | None = None, relationships: dict | None = None) -> Response:
|
|
136
126
|
json = self._parse_post_json(attributes, relationships)
|
|
137
127
|
return self.client.session.post(self.url, json=json)
|
|
138
128
|
|
|
@@ -151,9 +141,7 @@ class BaseModel:
|
|
|
151
141
|
def resource_name(cls):
|
|
152
142
|
return underscore(cls.__name__).replace("_", "-")
|
|
153
143
|
|
|
154
|
-
def _parse_post_json(
|
|
155
|
-
self, attributes: dict | None, relationships: dict | None
|
|
156
|
-
) -> dict:
|
|
144
|
+
def _parse_post_json(self, attributes: dict | None, relationships: dict | None) -> dict:
|
|
157
145
|
return {
|
|
158
146
|
"data": {
|
|
159
147
|
"attributes": self._parse_attributes(attributes),
|
|
@@ -6,7 +6,7 @@ import http.server
|
|
|
6
6
|
import json
|
|
7
7
|
import time
|
|
8
8
|
import webbrowser
|
|
9
|
-
from urllib.parse import urlparse
|
|
9
|
+
from urllib.parse import parse_qs, urlparse
|
|
10
10
|
|
|
11
11
|
import keyring
|
|
12
12
|
import requests
|
|
@@ -19,14 +19,12 @@ class OIDCHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|
|
19
19
|
url_path = self.path
|
|
20
20
|
# get the "code" parameter from the query string
|
|
21
21
|
try:
|
|
22
|
-
OIDCHTTPRequestHandler.code = urlparse(url_path).query
|
|
22
|
+
OIDCHTTPRequestHandler.code = parse_qs(urlparse(url_path).query)["code"][0]
|
|
23
23
|
except IndexError:
|
|
24
24
|
self.send_response(400)
|
|
25
25
|
self.send_header("Content-type", "text/html")
|
|
26
26
|
self.end_headers()
|
|
27
|
-
self.wfile.write(
|
|
28
|
-
b"<html><body><h1>Authorization failed.</h1></body></html>"
|
|
29
|
-
)
|
|
27
|
+
self.wfile.write(b"<html><body><h1>Authorization failed.</h1></body></html>")
|
|
30
28
|
return
|
|
31
29
|
# send the response
|
|
32
30
|
self.send_response(200)
|
|
@@ -93,12 +91,17 @@ class OIDCClient:
|
|
|
93
91
|
|
|
94
92
|
resp = {}
|
|
95
93
|
while "access_token" not in resp:
|
|
96
|
-
resp =
|
|
94
|
+
resp = requests.post(
|
|
95
|
+
self.token_endpoint,
|
|
96
|
+
data={
|
|
97
|
+
"client_id": self.client_id,
|
|
98
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
99
|
+
"device_code": device_code,
|
|
100
|
+
},
|
|
101
|
+
).json()
|
|
97
102
|
print(resp)
|
|
98
103
|
time.sleep(5)
|
|
99
|
-
|
|
100
|
-
return access_token
|
|
101
|
-
|
|
104
|
+
return resp["access_token"]
|
|
102
105
|
|
|
103
106
|
def get_token(self):
|
|
104
107
|
# construct the token request
|
|
@@ -112,10 +115,8 @@ class OIDCClient:
|
|
|
112
115
|
token_response = requests.post(self.token_endpoint, data=token_request)
|
|
113
116
|
# check for errors
|
|
114
117
|
if token_response.status_code != 200:
|
|
115
|
-
print(
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
print(token_response.text) # noqa: T201
|
|
118
|
+
print(f"Error: {token_response.status_code} {token_response.reason}\n")
|
|
119
|
+
print(token_response.text)
|
|
119
120
|
return False
|
|
120
121
|
# get the access token
|
|
121
122
|
return token_response.json()["access_token"]
|
|
@@ -151,7 +152,7 @@ class OIDCClient:
|
|
|
151
152
|
|
|
152
153
|
self.autoconfig()
|
|
153
154
|
if self.use_device_flow:
|
|
154
|
-
if token:= self.start_device_flow():
|
|
155
|
+
if token := self.start_device_flow():
|
|
155
156
|
self.keyring_set(token)
|
|
156
157
|
return token
|
|
157
158
|
return False
|
|
@@ -6,12 +6,10 @@ TIME_FORMAT = "%H:%M:%S"
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class SerializationError(ValueError):
|
|
9
|
-
|
|
10
9
|
"""Error raised only inside of Transforms when trying to (de)serialize values."""
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class BaseTransform:
|
|
14
|
-
|
|
15
13
|
"""Base class for serializers."""
|
|
16
14
|
|
|
17
15
|
@staticmethod
|
|
@@ -26,12 +24,9 @@ class BaseTransform:
|
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
class Type(BaseTransform):
|
|
29
|
-
|
|
30
27
|
"""Transform for types."""
|
|
31
28
|
|
|
32
|
-
def __init__(
|
|
33
|
-
self, type: type, allow_none: bool = True, pipe: Callable = lambda x: x
|
|
34
|
-
):
|
|
29
|
+
def __init__(self, type: type, allow_none: bool = True, pipe: Callable = lambda x: x):
|
|
35
30
|
self.type = type
|
|
36
31
|
self.pipe = pipe
|
|
37
32
|
self.allow_none = allow_none
|
|
@@ -51,7 +46,6 @@ class Type(BaseTransform):
|
|
|
51
46
|
|
|
52
47
|
|
|
53
48
|
class Duration(BaseTransform):
|
|
54
|
-
|
|
55
49
|
"""Transform for durations."""
|
|
56
50
|
|
|
57
51
|
@staticmethod
|
|
@@ -83,7 +77,6 @@ class RelationShipProperty:
|
|
|
83
77
|
|
|
84
78
|
|
|
85
79
|
class Relationship(BaseTransform):
|
|
86
|
-
|
|
87
80
|
"""Transform for relationships. This is very hacky and should be replaced with a better solution."""
|
|
88
81
|
|
|
89
82
|
def __init__(self, related_model) -> None:
|
|
@@ -102,9 +95,7 @@ class Relationship(BaseTransform):
|
|
|
102
95
|
data = {}
|
|
103
96
|
if isinstance(value, RelationShipProperty):
|
|
104
97
|
if not client:
|
|
105
|
-
raise SerializationError(
|
|
106
|
-
"Client has to be passed when using a property"
|
|
107
|
-
)
|
|
98
|
+
raise SerializationError("Client has to be passed when using a property")
|
|
108
99
|
try:
|
|
109
100
|
data["data"] = getattr(self.related_model(client), value.property_name)
|
|
110
101
|
except AttributeError as e:
|
|
@@ -118,9 +109,7 @@ class Relationship(BaseTransform):
|
|
|
118
109
|
|
|
119
110
|
def deserialize(self, value: dict) -> dict | None:
|
|
120
111
|
data = value.get("data") or {}
|
|
121
|
-
if (
|
|
122
|
-
recieved_type := (data or {}).get("type")
|
|
123
|
-
) != self.related_model.resource_name:
|
|
112
|
+
if (recieved_type := (data or {}).get("type")) != self.related_model.resource_name:
|
|
124
113
|
if recieved_type is None:
|
|
125
114
|
return None
|
|
126
115
|
raise SerializationError(
|
|
@@ -130,7 +119,6 @@ class Relationship(BaseTransform):
|
|
|
130
119
|
|
|
131
120
|
|
|
132
121
|
class Date(BaseTransform):
|
|
133
|
-
|
|
134
122
|
"""Transform for dates."""
|
|
135
123
|
|
|
136
124
|
@staticmethod
|
|
@@ -150,7 +138,6 @@ class Date(BaseTransform):
|
|
|
150
138
|
|
|
151
139
|
|
|
152
140
|
class Time(BaseTransform):
|
|
153
|
-
|
|
154
141
|
"""Transform for times."""
|
|
155
142
|
|
|
156
143
|
def __init__(self, return_datetime: bool = False):
|
|
@@ -175,9 +162,7 @@ class Time(BaseTransform):
|
|
|
175
162
|
return value.strftime(TIME_FORMAT) if value else None
|
|
176
163
|
|
|
177
164
|
def deserialize(self, value) -> time | datetime | None:
|
|
178
|
-
return (
|
|
179
|
-
self._return_value(datetime.strptime(value, "%H:%M:%S")) if value else None
|
|
180
|
-
)
|
|
165
|
+
return self._return_value(datetime.strptime(value, "%H:%M:%S")) if value else None
|
|
181
166
|
|
|
182
167
|
|
|
183
168
|
class Enum(BaseTransform):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|