hakai-api 2.0.1__tar.gz → 2.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.
- {hakai_api-2.0.1 → hakai_api-2.0.2}/.github/workflows/test.yaml +2 -2
- {hakai_api-2.0.1 → hakai_api-2.0.2}/PKG-INFO +5 -5
- {hakai_api-2.0.1 → hakai_api-2.0.2}/README.md +2 -2
- {hakai_api-2.0.1 → hakai_api-2.0.2}/pyproject.toml +3 -3
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/auth/base.py +1 -1
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/auth/desktop.py +1 -1
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/auth/web.py +1 -1
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/client.py +3 -3
- {hakai_api-2.0.1 → hakai_api-2.0.2}/tests/test_auth_strategies.py +8 -7
- {hakai_api-2.0.1 → hakai_api-2.0.2}/tests/test_client.py +19 -19
- hakai_api-2.0.2/uv.lock +470 -0
- hakai_api-2.0.1/uv.lock +0 -758
- {hakai_api-2.0.1 → hakai_api-2.0.2}/.github/workflows/lint.yaml +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/.github/workflows/release.yaml +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/.gitignore +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/.pre-commit-config.yaml +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/CODEOWNERS +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/CONTRIBUTING.md +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/LICENSE +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/example.py +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/__init__.py +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/auth/__init__.py +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/src/hakai_api/auth/desktop_callback.html +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/tests/__init__.py +0 -0
- {hakai_api-2.0.1 → hakai_api-2.0.2}/tests/test_backwards_compatability.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: hakai_api
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Get Hakai database resources using http calls
|
|
5
5
|
Author-email: Taylor Denouden <taylor.denouden@hakai.org>, Chris Davis <chris.davis@hakai.org>, Nate Rosenstock <nate.rosenstock@hakai.org>, Sam Albers <sam.albers@hakai.org>
|
|
6
6
|
Maintainer-email: Taylor Denouden <taylor.denouden@hakai.org>, Sam Albers <sam.albers@hakai.org>
|
|
7
7
|
License-Expression: MIT
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
10
|
Requires-Dist: loguru>=0.7.3
|
|
11
11
|
Requires-Dist: pkce>=1.0.3
|
|
12
12
|
Requires-Dist: pytz>=2025.2
|
|
@@ -22,7 +22,7 @@ The exported `Client` class extends the functionality of the
|
|
|
22
22
|
Python [requests library](https://docs.python-requests.org/en/master/) to supply Hakai
|
|
23
23
|
OAuth2 credentials with url requests.
|
|
24
24
|
|
|
25
|
-

|
|
25
|
+
[](https://pypi.org/project/hakai_api/) [](https://github.com/HakaiInstitute/hakai-api-client-py/actions/workflows/test.yaml) [](https://opensource.org/licenses/MIT)
|
|
26
26
|
|
|
27
27
|
<details>
|
|
28
28
|
|
|
@@ -49,7 +49,7 @@ OAuth2 credentials with url requests.
|
|
|
49
49
|
|
|
50
50
|
# Installation
|
|
51
51
|
|
|
52
|
-
Python 3.
|
|
52
|
+
Python 3.11 or higher is required. Install with pip:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
pip install hakai-api
|
|
@@ -6,7 +6,7 @@ The exported `Client` class extends the functionality of the
|
|
|
6
6
|
Python [requests library](https://docs.python-requests.org/en/master/) to supply Hakai
|
|
7
7
|
OAuth2 credentials with url requests.
|
|
8
8
|
|
|
9
|
-

|
|
9
|
+
[](https://pypi.org/project/hakai_api/) [](https://github.com/HakaiInstitute/hakai-api-client-py/actions/workflows/test.yaml) [](https://opensource.org/licenses/MIT)
|
|
10
10
|
|
|
11
11
|
<details>
|
|
12
12
|
|
|
@@ -33,7 +33,7 @@ OAuth2 credentials with url requests.
|
|
|
33
33
|
|
|
34
34
|
# Installation
|
|
35
35
|
|
|
36
|
-
Python 3.
|
|
36
|
+
Python 3.11 or higher is required. Install with pip:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
pip install hakai-api
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hakai_api"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.2"
|
|
4
4
|
description = "Get Hakai database resources using http calls"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Taylor Denouden", email = "taylor.denouden@hakai.org" },
|
|
@@ -15,7 +15,7 @@ maintainers = [
|
|
|
15
15
|
repository = "https://github.com/HakaiInstitute/hakai-api-client-py"
|
|
16
16
|
license = "MIT"
|
|
17
17
|
readme = "README.md"
|
|
18
|
-
requires-python = ">=3.
|
|
18
|
+
requires-python = ">=3.11"
|
|
19
19
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"loguru>=0.7.3",
|
|
@@ -75,7 +75,7 @@ convention = "google"
|
|
|
75
75
|
combine-as-imports = true
|
|
76
76
|
|
|
77
77
|
[tool.mypy]
|
|
78
|
-
python_version = "3.
|
|
78
|
+
python_version = "3.11"
|
|
79
79
|
warn_return_any = false
|
|
80
80
|
warn_unused_configs = true
|
|
81
81
|
strict = true
|
|
@@ -45,7 +45,7 @@ class AuthStrategy(ABC):
|
|
|
45
45
|
@property
|
|
46
46
|
@abstractmethod
|
|
47
47
|
def client_type(self) -> str:
|
|
48
|
-
"""
|
|
48
|
+
"""The client type for this authentication strategy.
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
51
51
|
The client type string (e.g., 'web', 'desktop').
|
|
@@ -257,7 +257,7 @@ class DesktopAuthStrategy(AuthStrategy):
|
|
|
257
257
|
|
|
258
258
|
@property
|
|
259
259
|
def client_type(self) -> str:
|
|
260
|
-
"""
|
|
260
|
+
"""The client type for the desktop authentication strategy."""
|
|
261
261
|
return "desktop"
|
|
262
262
|
|
|
263
263
|
# refresh_token method is now inherited from AuthStrategy base class
|
|
@@ -88,7 +88,7 @@ class WebAuthStrategy(AuthStrategy):
|
|
|
88
88
|
|
|
89
89
|
@property
|
|
90
90
|
def client_type(self) -> str:
|
|
91
|
-
"""
|
|
91
|
+
"""The client type for the web authentication strategy."""
|
|
92
92
|
return "web"
|
|
93
93
|
|
|
94
94
|
# refresh_token method is now inherited from AuthStrategy base class
|
|
@@ -151,7 +151,7 @@ class Client(OAuth2Session):
|
|
|
151
151
|
|
|
152
152
|
@property
|
|
153
153
|
def api_root(self) -> str:
|
|
154
|
-
"""
|
|
154
|
+
"""The API base URL.
|
|
155
155
|
|
|
156
156
|
Returns:
|
|
157
157
|
The base URL of the Hakai API.
|
|
@@ -160,7 +160,7 @@ class Client(OAuth2Session):
|
|
|
160
160
|
|
|
161
161
|
@property
|
|
162
162
|
def login_page(self) -> str:
|
|
163
|
-
"""
|
|
163
|
+
"""The login page URL.
|
|
164
164
|
|
|
165
165
|
Returns:
|
|
166
166
|
The URL of the login page.
|
|
@@ -169,7 +169,7 @@ class Client(OAuth2Session):
|
|
|
169
169
|
|
|
170
170
|
@property
|
|
171
171
|
def credentials(self) -> dict:
|
|
172
|
-
"""
|
|
172
|
+
"""The credentials object.
|
|
173
173
|
|
|
174
174
|
Returns:
|
|
175
175
|
Credentials object.
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import tempfile
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from datetime import UTC, datetime
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from time import mktime
|
|
9
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
10
11
|
from unittest.mock import Mock, patch
|
|
11
12
|
|
|
12
13
|
import pytest
|
|
@@ -18,7 +19,7 @@ class ConcreteAuthStrategy(AuthStrategy):
|
|
|
18
19
|
"""Concrete implementation for testing the abstract base class."""
|
|
19
20
|
|
|
20
21
|
def get_credentials(self) -> dict:
|
|
21
|
-
now_utc = datetime.now(
|
|
22
|
+
now_utc = datetime.now(UTC)
|
|
22
23
|
return {
|
|
23
24
|
"access_token": "test_token",
|
|
24
25
|
"token_type": "bearer",
|
|
@@ -58,7 +59,7 @@ def valid_credentials() -> dict:
|
|
|
58
59
|
Returns:
|
|
59
60
|
dict: Dictionary containing valid test credentials.
|
|
60
61
|
"""
|
|
61
|
-
now_utc = datetime.now(
|
|
62
|
+
now_utc = datetime.now(UTC)
|
|
62
63
|
future_timestamp = int(now_utc.timestamp() + 3600) # Use UTC timestamp
|
|
63
64
|
return {
|
|
64
65
|
"access_token": "test_access_token",
|
|
@@ -75,7 +76,7 @@ def expired_credentials() -> dict:
|
|
|
75
76
|
Returns:
|
|
76
77
|
dict: Dictionary containing expired test credentials.
|
|
77
78
|
"""
|
|
78
|
-
now_utc = datetime.now(
|
|
79
|
+
now_utc = datetime.now(UTC)
|
|
79
80
|
past_timestamp = int(now_utc.timestamp() - 3600) # Use UTC timestamp
|
|
80
81
|
return {
|
|
81
82
|
"access_token": "expired_access_token",
|
|
@@ -216,7 +217,7 @@ class TestWebAuthStrategy:
|
|
|
216
217
|
def test_get_credentials_from_env(self) -> None:
|
|
217
218
|
"""Test getting credentials from environment variable."""
|
|
218
219
|
# Use a future timestamp to ensure credentials aren't expired
|
|
219
|
-
future_timestamp = int(mktime(datetime.now(
|
|
220
|
+
future_timestamp = int(mktime(datetime.now(UTC).timetuple()) + 3600)
|
|
220
221
|
env_value = f"access_token=env_token&token_type=bearer&expires_at={future_timestamp}&expires_in=3600"
|
|
221
222
|
|
|
222
223
|
with patch.dict(os.environ, {"HAKAI_API_CREDENTIALS": env_value}):
|
|
@@ -303,7 +304,7 @@ class TestDesktopAuthStrategy:
|
|
|
303
304
|
def test_get_credentials_from_env(self) -> None:
|
|
304
305
|
"""Test getting credentials from environment variable."""
|
|
305
306
|
# Use a future timestamp to ensure credentials aren't expired
|
|
306
|
-
future_timestamp = int(mktime(datetime.now(
|
|
307
|
+
future_timestamp = int(mktime(datetime.now(UTC).timetuple()) + 3600)
|
|
307
308
|
env_value = f"access_token=env_token&token_type=bearer&expires_at={future_timestamp}&expires_in=3600"
|
|
308
309
|
|
|
309
310
|
with patch.dict(os.environ, {"HAKAI_API_CREDENTIALS": env_value}):
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import tempfile
|
|
5
|
-
from datetime import
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
6
|
from time import mktime
|
|
7
7
|
from typing import Any, NoReturn
|
|
8
8
|
from unittest.mock import Mock, patch
|
|
@@ -15,7 +15,7 @@ from hakai_api import Client
|
|
|
15
15
|
def test_get_valid_credentials_from_file(temp_credentials_file):
|
|
16
16
|
"""Test that credentials can be read from a file."""
|
|
17
17
|
# Create a client object with a temporary credentials file
|
|
18
|
-
now = datetime.now(
|
|
18
|
+
now = datetime.now(UTC)
|
|
19
19
|
client = Client(
|
|
20
20
|
credentials={
|
|
21
21
|
"token_type": "Bearer",
|
|
@@ -45,7 +45,7 @@ def test_get_valid_credentials_from_file(temp_credentials_file):
|
|
|
45
45
|
def test_expired_credentials_are_handled(temp_credentials_file):
|
|
46
46
|
"""Test that expired credentials are removed properly."""
|
|
47
47
|
# Create a client object with expired credentials and a temporary credentials file
|
|
48
|
-
now = datetime.now(
|
|
48
|
+
now = datetime.now(UTC)
|
|
49
49
|
client = Client(
|
|
50
50
|
credentials={
|
|
51
51
|
"token_type": "Bearer",
|
|
@@ -69,7 +69,7 @@ def test_expired_credentials_are_handled(temp_credentials_file):
|
|
|
69
69
|
def test_custom_api_root(temp_credentials_file):
|
|
70
70
|
"""Test that a customized api root can be set."""
|
|
71
71
|
# Create a client object with temporary credentials file
|
|
72
|
-
now = datetime.now(
|
|
72
|
+
now = datetime.now(UTC)
|
|
73
73
|
client = Client(
|
|
74
74
|
credentials={
|
|
75
75
|
"token_type": "Bearer",
|
|
@@ -88,7 +88,7 @@ def test_custom_api_root(temp_credentials_file):
|
|
|
88
88
|
def test_custom_login_page(temp_credentials_file):
|
|
89
89
|
"""Test that a customized login page can be set."""
|
|
90
90
|
# Create a client object with temporary credentials file
|
|
91
|
-
now = datetime.now(
|
|
91
|
+
now = datetime.now(UTC)
|
|
92
92
|
client = Client(
|
|
93
93
|
credentials={
|
|
94
94
|
"token_type": "Bearer",
|
|
@@ -107,7 +107,7 @@ def test_custom_login_page(temp_credentials_file):
|
|
|
107
107
|
def test_credentials_from_env_variable(temp_credentials_file):
|
|
108
108
|
"""Test setting credentials with HAKAI_API_CREDENTIALS environment variable."""
|
|
109
109
|
# Create a client object with temporary credentials file
|
|
110
|
-
now = datetime.now(
|
|
110
|
+
now = datetime.now(UTC)
|
|
111
111
|
os.environ["HAKAI_API_CREDENTIALS"] = "&".join([
|
|
112
112
|
"token_type=Bearer",
|
|
113
113
|
"access_token=test_access_token",
|
|
@@ -145,7 +145,7 @@ def test_user_agent_header(tmp_path):
|
|
|
145
145
|
del os.environ[Client.USER_AGENT_ENV_VAR]
|
|
146
146
|
|
|
147
147
|
# Create a client object with default User-Agent
|
|
148
|
-
now = datetime.now(
|
|
148
|
+
now = datetime.now(UTC)
|
|
149
149
|
client = Client(
|
|
150
150
|
credentials={
|
|
151
151
|
"token_type": "Bearer",
|
|
@@ -201,7 +201,7 @@ def temp_credentials_file():
|
|
|
201
201
|
@pytest.fixture
|
|
202
202
|
def valid_credentials():
|
|
203
203
|
"""Sample valid credentials for testing."""
|
|
204
|
-
future_timestamp = int(mktime(datetime.now(
|
|
204
|
+
future_timestamp = int(mktime(datetime.now(UTC).timetuple()) + 3600)
|
|
205
205
|
return {
|
|
206
206
|
"access_token": "valid_token",
|
|
207
207
|
"token_type": "Bearer",
|
|
@@ -213,7 +213,7 @@ def valid_credentials():
|
|
|
213
213
|
@pytest.fixture
|
|
214
214
|
def expired_credentials():
|
|
215
215
|
"""Sample expired credentials for testing."""
|
|
216
|
-
now_utc = datetime.now(
|
|
216
|
+
now_utc = datetime.now(UTC)
|
|
217
217
|
past_timestamp = int(now_utc.timestamp() - 3600) # Use UTC timestamp
|
|
218
218
|
return {
|
|
219
219
|
"access_token": "expired_token",
|
|
@@ -226,7 +226,7 @@ def expired_credentials():
|
|
|
226
226
|
@pytest.fixture
|
|
227
227
|
def credentials_with_refresh():
|
|
228
228
|
"""Sample credentials with refresh token."""
|
|
229
|
-
future_timestamp = int(mktime(datetime.now(
|
|
229
|
+
future_timestamp = int(mktime(datetime.now(UTC).timetuple()) + 3600)
|
|
230
230
|
return {
|
|
231
231
|
"access_token": "token_with_refresh",
|
|
232
232
|
"token_type": "Bearer",
|
|
@@ -325,7 +325,7 @@ class TestClientCredentialScenarios:
|
|
|
325
325
|
return {
|
|
326
326
|
"access_token": "new_token",
|
|
327
327
|
"token_type": "Bearer",
|
|
328
|
-
"expires_at": int(mktime(datetime.now(
|
|
328
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
329
329
|
"expires_in": 3600,
|
|
330
330
|
}
|
|
331
331
|
|
|
@@ -367,7 +367,7 @@ class TestClientCredentialScenarios:
|
|
|
367
367
|
return {
|
|
368
368
|
"access_token": "new_token",
|
|
369
369
|
"token_type": "Bearer",
|
|
370
|
-
"expires_at": int(mktime(datetime.now(
|
|
370
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
371
371
|
"expires_in": 3600,
|
|
372
372
|
}
|
|
373
373
|
|
|
@@ -414,7 +414,7 @@ class TestClientCredentialScenarios:
|
|
|
414
414
|
def test_expired_env_credentials_are_rejected(self, tmp_path):
|
|
415
415
|
"""Test that expired environment credentials are rejected."""
|
|
416
416
|
# Set expired credentials in environment
|
|
417
|
-
now_utc = datetime.now(
|
|
417
|
+
now_utc = datetime.now(UTC)
|
|
418
418
|
past_timestamp = int(now_utc.timestamp() - 3600) # Use UTC timestamp
|
|
419
419
|
expired_env_creds = (
|
|
420
420
|
f"access_token=expired_env_token&token_type=Bearer&expires_at={past_timestamp}&expires_in=3600"
|
|
@@ -473,7 +473,7 @@ class TestClientCredentialScenarios:
|
|
|
473
473
|
return {
|
|
474
474
|
"access_token": "new_token",
|
|
475
475
|
"token_type": "Bearer",
|
|
476
|
-
"expires_at": int(mktime(datetime.now(
|
|
476
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
477
477
|
"expires_in": 3600,
|
|
478
478
|
}
|
|
479
479
|
|
|
@@ -514,7 +514,7 @@ class TestClientRefreshTokenScenarios:
|
|
|
514
514
|
credentials_with_refresh = {
|
|
515
515
|
"access_token": "old_token",
|
|
516
516
|
"token_type": "Bearer",
|
|
517
|
-
"expires_at": int(mktime(datetime.now(
|
|
517
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
518
518
|
"expires_in": 3600,
|
|
519
519
|
"refresh_token": "refresh123",
|
|
520
520
|
}
|
|
@@ -524,7 +524,7 @@ class TestClientRefreshTokenScenarios:
|
|
|
524
524
|
return {
|
|
525
525
|
"access_token": "refreshed_token",
|
|
526
526
|
"token_type": "Bearer",
|
|
527
|
-
"expires_at": int(mktime(datetime.now(
|
|
527
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 7200),
|
|
528
528
|
"expires_in": 7200,
|
|
529
529
|
"refresh_token": "refresh123",
|
|
530
530
|
}
|
|
@@ -544,7 +544,7 @@ class TestClientRefreshTokenScenarios:
|
|
|
544
544
|
credentials_with_refresh = {
|
|
545
545
|
"access_token": "old_token",
|
|
546
546
|
"token_type": "Bearer",
|
|
547
|
-
"expires_at": int(mktime(datetime.now(
|
|
547
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
548
548
|
"expires_in": 3600,
|
|
549
549
|
"refresh_token": "refresh123",
|
|
550
550
|
}
|
|
@@ -554,7 +554,7 @@ class TestClientRefreshTokenScenarios:
|
|
|
554
554
|
mock_response.status_code = 200
|
|
555
555
|
mock_response.json.return_value = {
|
|
556
556
|
"access_token": "refreshed_web_token",
|
|
557
|
-
"expires_at": int(mktime(datetime.now(
|
|
557
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 7200),
|
|
558
558
|
"expires_in": 7200,
|
|
559
559
|
}
|
|
560
560
|
return mock_response
|
|
@@ -581,7 +581,7 @@ class TestClientRefreshTokenScenarios:
|
|
|
581
581
|
credentials_with_refresh = {
|
|
582
582
|
"access_token": "old_token",
|
|
583
583
|
"token_type": "Bearer",
|
|
584
|
-
"expires_at": int(mktime(datetime.now(
|
|
584
|
+
"expires_at": int(mktime(datetime.now(UTC).timetuple()) + 3600),
|
|
585
585
|
"expires_in": 3600,
|
|
586
586
|
"refresh_token": "invalid_refresh",
|
|
587
587
|
}
|