act365 1.0.2__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.
- act365-1.0.2/PKG-INFO +15 -0
- act365-1.0.2/README.md +0 -0
- act365-1.0.2/pyproject.toml +26 -0
- act365-1.0.2/src/act365/__init__.py +0 -0
- act365-1.0.2/src/act365/client.py +74 -0
act365-1.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: act365
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Python Client for [ACT365](https://www.act365.eu/)
|
|
5
|
+
Author: Simon McCartney
|
|
6
|
+
Author-email: simon@mccartney.ie
|
|
7
|
+
Requires-Python: >=3.12,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
12
|
+
Requires-Dist: json5 (==0.9.25)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
|
act365-1.0.2/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "act365"
|
|
3
|
+
version = "1.0.2"
|
|
4
|
+
description = "Python Client for [ACT365](https://www.act365.eu/)"
|
|
5
|
+
authors = ["Simon McCartney <simon@mccartney.ie>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "act365", from = "src"}]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.scripts]
|
|
10
|
+
act365cli = "act365.client:run"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.12"
|
|
14
|
+
httpx = "^0.27.0"
|
|
15
|
+
json5 = "0.9.25"
|
|
16
|
+
|
|
17
|
+
[tool.poetry.group.dev.dependencies]
|
|
18
|
+
pytest = "^8.3.1"
|
|
19
|
+
pytest-httpx = "^0.30.0"
|
|
20
|
+
httpx = {extras = ["cli"], version = "^0.27.0"}
|
|
21
|
+
flake8 = "^7.1.1"
|
|
22
|
+
isort = "^5.13.2"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["poetry-core"]
|
|
26
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from time import sleep
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
logging.basicConfig(filename="act365.log", filemode="w", level=logging.INFO)
|
|
7
|
+
LOG = logging.getLogger("act635")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Act365Auth(httpx.Auth):
|
|
11
|
+
requires_response_body = True
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
username,
|
|
16
|
+
password,
|
|
17
|
+
grant_type="password",
|
|
18
|
+
url="https://userapi.act365.eu/api",
|
|
19
|
+
):
|
|
20
|
+
|
|
21
|
+
if username is None or password is None:
|
|
22
|
+
raise Exception
|
|
23
|
+
self.username = username
|
|
24
|
+
self.password = password
|
|
25
|
+
self.grant_type = grant_type
|
|
26
|
+
self.url = url
|
|
27
|
+
|
|
28
|
+
self.access_token = None
|
|
29
|
+
|
|
30
|
+
def get_token(self):
|
|
31
|
+
data = {
|
|
32
|
+
"grant_type": self.grant_type,
|
|
33
|
+
"username": self.username,
|
|
34
|
+
"password": self.password,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
while self.access_token is None:
|
|
38
|
+
# Set Content-Type: application/x-www-form-urlencoded via headers
|
|
39
|
+
# as apiary complains about case mismatch
|
|
40
|
+
response = httpx.post(
|
|
41
|
+
self.url + "/account/login",
|
|
42
|
+
data=data,
|
|
43
|
+
timeout=90,
|
|
44
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
45
|
+
)
|
|
46
|
+
LOG.debug(f"Response: {response.status_code} {response.text}")
|
|
47
|
+
LOG.info(f"Response Headers: {response.headers}")
|
|
48
|
+
|
|
49
|
+
if response.status_code == httpx.codes.OK:
|
|
50
|
+
# "token_type":"bearer","expires_in":86399,
|
|
51
|
+
# ".issued":"Wed, 24 Jul 2024 11:37:56 GMT",
|
|
52
|
+
# ".expires":"Thu, 25 Jul 2024 11:37:56 GMT"}'
|
|
53
|
+
self.access_token = response.json().get("access_token", None)
|
|
54
|
+
self.token_type = response.json().get("token_type", None)
|
|
55
|
+
self.expires_in = response.json().get("expires_in", None)
|
|
56
|
+
self.issued = response.json().get(".issued", None)
|
|
57
|
+
self.expires = response.json().get(".expires", None)
|
|
58
|
+
elif response.status_code == httpx.codes.TOO_MANY_REQUESTS:
|
|
59
|
+
sleep(65)
|
|
60
|
+
else:
|
|
61
|
+
raise Exception
|
|
62
|
+
|
|
63
|
+
def auth_flow(self, request):
|
|
64
|
+
if self.access_token is None:
|
|
65
|
+
self.get_token()
|
|
66
|
+
|
|
67
|
+
request.headers["Authorization"] = "Bearer " + self.access_token
|
|
68
|
+
response = yield request
|
|
69
|
+
|
|
70
|
+
if response.status_code == 401:
|
|
71
|
+
self.get_token()
|
|
72
|
+
request.headers["Authorization"] = "Bearer " + self.access_token
|
|
73
|
+
|
|
74
|
+
yield request
|