arkindex-client 1.1.5__tar.gz → 1.2.1__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.
- {arkindex-client-1.1.5/arkindex_client.egg-info → arkindex_client-1.2.1}/PKG-INFO +15 -2
- arkindex_client-1.2.1/VERSION +1 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/mock.py +5 -2
- {arkindex-client-1.1.5 → arkindex_client-1.2.1/arkindex_client.egg-info}/PKG-INFO +15 -2
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex_client.egg-info/SOURCES.txt +7 -4
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex_client.egg-info/top_level.txt +0 -1
- arkindex_client-1.2.1/tests/test_auth.py +54 -0
- arkindex_client-1.2.1/tests/test_env.py +74 -0
- arkindex_client-1.2.1/tests/test_init.py +85 -0
- arkindex_client-1.2.1/tests/test_mock.py +118 -0
- arkindex_client-1.2.1/tests/test_pagination.py +336 -0
- arkindex_client-1.2.1/tests/test_request.py +26 -0
- arkindex-client-1.1.5/VERSION +0 -1
- arkindex-client-1.1.5/apistar/__init__.py +0 -9
- arkindex-client-1.1.5/apistar/exceptions.py +0 -13
- arkindex-client-1.1.5/requirements-docs.txt +0 -3
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/LICENSE +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/MANIFEST.in +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/README.md +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/__init__.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/auth.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/client/__init__.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/client/client.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/client/decoders.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/compat.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/document.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/exceptions.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/pagination.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/schema/__init__.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/schema/openapi.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex/schema/validator.py +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex_client.egg-info/dependency_links.txt +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex_client.egg-info/requires.txt +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/requirements.txt +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/setup.cfg +0 -0
- {arkindex-client-1.1.5 → arkindex_client-1.2.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: arkindex-client
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: API client for the Arkindex project
|
|
5
5
|
Home-page: https://gitlab.teklia.com/arkindex/api-client
|
|
6
6
|
Author: Teklia <contact@teklia.com>
|
|
@@ -20,5 +20,18 @@ Classifier: Topic :: Text Processing :: Indexing
|
|
|
20
20
|
Classifier: Topic :: Text Processing :: Linguistic
|
|
21
21
|
Requires-Python: >=3.8
|
|
22
22
|
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.30
|
|
24
|
+
Requires-Dist: tenacity>=9.1.0
|
|
25
|
+
Requires-Dist: typesystem==0.4.1
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
23
36
|
|
|
24
37
|
Documentation is available at https://api.arkindex.org
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.1
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import collections
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
|
+
from arkindex import ArkindexClient
|
|
5
6
|
from arkindex.exceptions import ErrorResponse
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
@@ -9,10 +10,12 @@ logger = logging.getLogger(__name__)
|
|
|
9
10
|
MockRequest = collections.namedtuple("MockRequest", "operation, body, args, kwargs")
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class MockApiClient(
|
|
13
|
+
class MockApiClient(ArkindexClient):
|
|
13
14
|
"""A mockup of the Arkindex API Client to build unit tests"""
|
|
14
15
|
|
|
15
|
-
def __init__(self):
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
16
19
|
self.history = []
|
|
17
20
|
self.responses = []
|
|
18
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: arkindex-client
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: API client for the Arkindex project
|
|
5
5
|
Home-page: https://gitlab.teklia.com/arkindex/api-client
|
|
6
6
|
Author: Teklia <contact@teklia.com>
|
|
@@ -20,5 +20,18 @@ Classifier: Topic :: Text Processing :: Indexing
|
|
|
20
20
|
Classifier: Topic :: Text Processing :: Linguistic
|
|
21
21
|
Requires-Python: >=3.8
|
|
22
22
|
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.30
|
|
24
|
+
Requires-Dist: tenacity>=9.1.0
|
|
25
|
+
Requires-Dist: typesystem==0.4.1
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
23
36
|
|
|
24
37
|
Documentation is available at https://api.arkindex.org
|
|
@@ -2,12 +2,9 @@ LICENSE
|
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.md
|
|
4
4
|
VERSION
|
|
5
|
-
requirements-docs.txt
|
|
6
5
|
requirements.txt
|
|
7
6
|
setup.cfg
|
|
8
7
|
setup.py
|
|
9
|
-
apistar/__init__.py
|
|
10
|
-
apistar/exceptions.py
|
|
11
8
|
arkindex/__init__.py
|
|
12
9
|
arkindex/auth.py
|
|
13
10
|
arkindex/compat.py
|
|
@@ -25,4 +22,10 @@ arkindex_client.egg-info/PKG-INFO
|
|
|
25
22
|
arkindex_client.egg-info/SOURCES.txt
|
|
26
23
|
arkindex_client.egg-info/dependency_links.txt
|
|
27
24
|
arkindex_client.egg-info/requires.txt
|
|
28
|
-
arkindex_client.egg-info/top_level.txt
|
|
25
|
+
arkindex_client.egg-info/top_level.txt
|
|
26
|
+
tests/test_auth.py
|
|
27
|
+
tests/test_env.py
|
|
28
|
+
tests/test_init.py
|
|
29
|
+
tests/test_mock.py
|
|
30
|
+
tests/test_pagination.py
|
|
31
|
+
tests/test_request.py
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from responses import matchers
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_login(responses, dummy_client):
|
|
6
|
+
responses.add(
|
|
7
|
+
responses.POST,
|
|
8
|
+
"https://dummy.test/api/v1/user/login/",
|
|
9
|
+
json={"auth_token": "tolkien"},
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
assert dummy_client.session.auth.token is None
|
|
13
|
+
dummy_client.session.auth.scheme = "Beer"
|
|
14
|
+
|
|
15
|
+
assert dummy_client.login("user@user.user", "Pa$$w0rd") == {"auth_token": "tolkien"}
|
|
16
|
+
|
|
17
|
+
assert dummy_client.session.auth.token == "tolkien"
|
|
18
|
+
assert dummy_client.session.auth.scheme == "Token"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_default_auth_scheme(responses, dummy_client):
|
|
22
|
+
responses.add(
|
|
23
|
+
responses.GET,
|
|
24
|
+
"https://dummy.test/api/v1/elements/",
|
|
25
|
+
match=[matchers.header_matcher({"Authorization": "Token ofgratitude"})],
|
|
26
|
+
json={"count": 0, "previous": None, "next": None, "results": []},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
dummy_client.configure(token="ofgratitude")
|
|
30
|
+
|
|
31
|
+
assert dummy_client.request("ListElements") == {
|
|
32
|
+
"count": 0,
|
|
33
|
+
"previous": None,
|
|
34
|
+
"next": None,
|
|
35
|
+
"results": [],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_custom_auth_scheme(responses, dummy_client):
|
|
40
|
+
responses.add(
|
|
41
|
+
responses.GET,
|
|
42
|
+
"https://dummy.test/api/v1/elements/",
|
|
43
|
+
match=[matchers.header_matcher({"Authorization": "Digest Food"})],
|
|
44
|
+
json={"count": 0, "previous": None, "next": None, "results": []},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
dummy_client.configure(token="Food", auth_scheme="Digest")
|
|
48
|
+
|
|
49
|
+
assert dummy_client.request("ListElements") == {
|
|
50
|
+
"count": 0,
|
|
51
|
+
"previous": None,
|
|
52
|
+
"next": None,
|
|
53
|
+
"results": [],
|
|
54
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from arkindex import options_from_env
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.parametrize(
|
|
10
|
+
"env,expected",
|
|
11
|
+
[
|
|
12
|
+
({}, {}),
|
|
13
|
+
({"ARKINDEX_API_URL": "http://lol"}, {"base_url": "http://lol"}),
|
|
14
|
+
({"ARKINDEX_API_SCHEMA_URL": "http://lol"}, {"schema_url": "http://lol"}),
|
|
15
|
+
({"ARKINDEX_API_AUTH_SCHEME": "Bribe"}, {"auth_scheme": "Bribe"}),
|
|
16
|
+
(
|
|
17
|
+
{"ARKINDEX_API_CSRF_COOKIE": "raisin-oatmeal"},
|
|
18
|
+
{"csrf_cookie": "raisin-oatmeal"},
|
|
19
|
+
),
|
|
20
|
+
(
|
|
21
|
+
{"ARKINDEX_API_TOKEN": "ofGoodWill"},
|
|
22
|
+
{"auth_scheme": "Token", "token": "ofGoodWill"},
|
|
23
|
+
),
|
|
24
|
+
(
|
|
25
|
+
{"ARKINDEX_TASK_TOKEN": "Kimonos"},
|
|
26
|
+
{"auth_scheme": "Ponos", "token": "Kimonos"},
|
|
27
|
+
),
|
|
28
|
+
# The task token should override the API token
|
|
29
|
+
(
|
|
30
|
+
{"ARKINDEX_API_TOKEN": "ofGoodWill", "ARKINDEX_TASK_TOKEN": "Kimonos"},
|
|
31
|
+
{"auth_scheme": "Ponos", "token": "Kimonos"},
|
|
32
|
+
),
|
|
33
|
+
# The auth scheme can override the scheme set by the API token
|
|
34
|
+
(
|
|
35
|
+
{"ARKINDEX_API_TOKEN": "Bad", "ARKINDEX_API_AUTH_SCHEME": "Breaking"},
|
|
36
|
+
{"auth_scheme": "Breaking", "token": "Bad"},
|
|
37
|
+
),
|
|
38
|
+
# The auth scheme can override the scheme set by the task token
|
|
39
|
+
(
|
|
40
|
+
{"ARKINDEX_TASK_TOKEN": "Bad", "ARKINDEX_API_AUTH_SCHEME": "Breaking"},
|
|
41
|
+
{"auth_scheme": "Breaking", "token": "Bad"},
|
|
42
|
+
),
|
|
43
|
+
# Also try with both tokens set
|
|
44
|
+
(
|
|
45
|
+
{
|
|
46
|
+
"ARKINDEX_API_AUTH_SCHEME": "It",
|
|
47
|
+
"ARKINDEX_API_TOKEN": "Fails",
|
|
48
|
+
"ARKINDEX_TASK_TOKEN": "Works",
|
|
49
|
+
},
|
|
50
|
+
{"auth_scheme": "It", "token": "Works"},
|
|
51
|
+
),
|
|
52
|
+
# Set everything at once
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
"ARKINDEX_API_URL": "http://lol",
|
|
56
|
+
"ARKINDEX_API_SCHEMA_URL": "http://lul",
|
|
57
|
+
"ARKINDEX_API_AUTH_SCHEME": "Roll",
|
|
58
|
+
"ARKINDEX_API_TOKEN": "d6",
|
|
59
|
+
"ARKINDEX_TASK_TOKEN": "d20",
|
|
60
|
+
"ARKINDEX_API_CSRF_COOKIE": "thin-mint",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"base_url": "http://lol",
|
|
64
|
+
"schema_url": "http://lul",
|
|
65
|
+
"auth_scheme": "Roll",
|
|
66
|
+
"token": "d20",
|
|
67
|
+
"csrf_cookie": "thin-mint",
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
def test_options_from_env(mocker, env, expected):
|
|
73
|
+
mocker.patch.dict(os.environ, env, clear=True)
|
|
74
|
+
assert options_from_env() == expected
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import responses
|
|
6
|
+
|
|
7
|
+
from arkindex import ArkindexClient
|
|
8
|
+
from arkindex.exceptions import SchemaError
|
|
9
|
+
|
|
10
|
+
DUMMY_SCHEMA = Path(__file__).absolute().parent / "schema.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_invalid_url():
|
|
14
|
+
with pytest.raises(
|
|
15
|
+
SchemaError,
|
|
16
|
+
match=r"Could not retrieve a proper OpenAPI schema from http://aaa/api/v1/openapi/\?format=json",
|
|
17
|
+
):
|
|
18
|
+
ArkindexClient(base_url="http://aaa")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_http_error():
|
|
22
|
+
responses.add(
|
|
23
|
+
responses.GET,
|
|
24
|
+
"https://dummy.test/api/v1/openapi/?format=json",
|
|
25
|
+
status=418,
|
|
26
|
+
)
|
|
27
|
+
with pytest.raises(SchemaError):
|
|
28
|
+
ArkindexClient(base_url="https://dummy.test/")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_invalid_json():
|
|
32
|
+
responses.add(
|
|
33
|
+
responses.GET,
|
|
34
|
+
"https://dummy.test/api/v1/openapi/?format=json",
|
|
35
|
+
body="{",
|
|
36
|
+
)
|
|
37
|
+
with pytest.raises(SchemaError):
|
|
38
|
+
ArkindexClient(base_url="https://dummy.test/")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_no_endpoints():
|
|
42
|
+
responses.add(
|
|
43
|
+
responses.GET,
|
|
44
|
+
"https://dummy.test/api/v1/openapi/?format=json",
|
|
45
|
+
json={
|
|
46
|
+
"openapi": "3.0.2",
|
|
47
|
+
"info": {"title": "/dev/null", "version": "0"},
|
|
48
|
+
"paths": {},
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
with pytest.raises(
|
|
52
|
+
SchemaError,
|
|
53
|
+
match="An OpenAPI document must contain at least one valid operation.",
|
|
54
|
+
):
|
|
55
|
+
ArkindexClient(base_url="https://dummy.test/")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_schema_url():
|
|
59
|
+
with DUMMY_SCHEMA.open() as f:
|
|
60
|
+
responses.add(
|
|
61
|
+
responses.GET,
|
|
62
|
+
"http://dev/null",
|
|
63
|
+
body=f.read(),
|
|
64
|
+
)
|
|
65
|
+
cli = ArkindexClient(base_url="https://dummy.test/", schema_url="http://dev/null")
|
|
66
|
+
assert cli.document.url == "https://dummy.test/"
|
|
67
|
+
assert list(cli.document.links.keys()) == [
|
|
68
|
+
"ListElements",
|
|
69
|
+
"ListProcessElements",
|
|
70
|
+
"UploadDataFile",
|
|
71
|
+
"ListElementMetaData",
|
|
72
|
+
"Login",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_local_path():
|
|
77
|
+
cli = ArkindexClient(base_url="https://dummy.test/", schema_url=str(DUMMY_SCHEMA))
|
|
78
|
+
assert cli.document.url == "https://dummy.test/"
|
|
79
|
+
assert list(cli.document.links.keys()) == [
|
|
80
|
+
"ListElements",
|
|
81
|
+
"ListProcessElements",
|
|
82
|
+
"UploadDataFile",
|
|
83
|
+
"ListElementMetaData",
|
|
84
|
+
"Login",
|
|
85
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from arkindex.exceptions import ErrorResponse
|
|
8
|
+
from arkindex.mock import MockApiClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_mock_client(responses):
|
|
12
|
+
# Allow accessing remote API schemas, defaulting to the prod environment
|
|
13
|
+
schema_url = os.environ.get(
|
|
14
|
+
"ARKINDEX_API_SCHEMA_URL",
|
|
15
|
+
"https://arkindex.teklia.com/api/v1/openapi/?format=json",
|
|
16
|
+
)
|
|
17
|
+
responses.add_passthru(schema_url)
|
|
18
|
+
|
|
19
|
+
# Configure mock for dummy endpoints
|
|
20
|
+
mock = MockApiClient()
|
|
21
|
+
mock.add_response(
|
|
22
|
+
"ListAnimals",
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
"type": "cat",
|
|
26
|
+
"name": "Henri",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "dog",
|
|
30
|
+
"name": "Rick",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
invalid_body = mock.add_response(
|
|
35
|
+
"CreateTag",
|
|
36
|
+
name="Henri",
|
|
37
|
+
body={"tag": "This is not the right payload"},
|
|
38
|
+
response={
|
|
39
|
+
"id": "1234-cat",
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
mock.add_response(
|
|
43
|
+
"CreateTag",
|
|
44
|
+
name="Henri",
|
|
45
|
+
body={"tag": "yyy"},
|
|
46
|
+
response={
|
|
47
|
+
"id": "1234-cat",
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
extra_response = mock.add_response(
|
|
51
|
+
"CreateTag",
|
|
52
|
+
name="Nope",
|
|
53
|
+
response={
|
|
54
|
+
"id": "shouldNotAppear",
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
mock.add_response(
|
|
58
|
+
"CreateTag",
|
|
59
|
+
name="Rick",
|
|
60
|
+
body={"tag": "xxx"},
|
|
61
|
+
response={
|
|
62
|
+
"id": "5678-dog",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
mock.add_error_response(
|
|
66
|
+
"DeleteTag",
|
|
67
|
+
name="Henri",
|
|
68
|
+
body={"tag": "yyy"},
|
|
69
|
+
status_code=403,
|
|
70
|
+
title="Forbidden",
|
|
71
|
+
content="You cannot perform this action",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Call the endpoints - this simulates business code
|
|
75
|
+
animals = mock.paginate("ListAnimals")
|
|
76
|
+
assert len(animals) == 2
|
|
77
|
+
assert animals == [
|
|
78
|
+
{
|
|
79
|
+
"type": "cat",
|
|
80
|
+
"name": "Henri",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "dog",
|
|
84
|
+
"name": "Rick",
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
assert mock.request("CreateTag", name="Rick", body={"tag": "xxx"}) == {
|
|
89
|
+
"id": "5678-dog",
|
|
90
|
+
}
|
|
91
|
+
assert mock.request("CreateTag", name="Henri", body={"tag": "yyy"}) == {
|
|
92
|
+
"id": "1234-cat",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
with pytest.raises(ErrorResponse) as e_info:
|
|
96
|
+
mock.request("DeleteTag", name="Henri", body={"tag": "yyy"})
|
|
97
|
+
error_response = e_info.value
|
|
98
|
+
assert error_response.status_code == 403
|
|
99
|
+
assert error_response.title == "Forbidden"
|
|
100
|
+
assert error_response.content == "You cannot perform this action"
|
|
101
|
+
|
|
102
|
+
# A request not mocked should raise an exception
|
|
103
|
+
with pytest.raises(ErrorResponse):
|
|
104
|
+
mock.request("DoSomething", parameter="value")
|
|
105
|
+
|
|
106
|
+
# Check the mockup history
|
|
107
|
+
assert len(mock.history) == 5
|
|
108
|
+
assert [req.operation for req in mock.history] == [
|
|
109
|
+
"ListAnimals",
|
|
110
|
+
"CreateTag",
|
|
111
|
+
"CreateTag",
|
|
112
|
+
"DeleteTag",
|
|
113
|
+
"DoSomething",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
# Almost all responses have been consumed
|
|
117
|
+
assert len(mock.responses) == 2
|
|
118
|
+
assert mock.responses == [invalid_body, extra_response]
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# -*G- coding: utf-8 -*-
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import responses
|
|
7
|
+
from responses import matchers
|
|
8
|
+
|
|
9
|
+
from arkindex import ArkindexClient
|
|
10
|
+
from arkindex.pagination import PaginationMode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_pagination_empty(mock_schema):
|
|
14
|
+
responses.add(
|
|
15
|
+
responses.GET,
|
|
16
|
+
"https://dummy.test/api/v1/elements/",
|
|
17
|
+
json={"count": 0, "previous": None, "next": None, "results": []},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
21
|
+
with pytest.raises(StopIteration):
|
|
22
|
+
next(cli.paginate("ListElements"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_page_mode_detection(mock_schema):
|
|
26
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
27
|
+
pagination = cli.paginate("ListElements")
|
|
28
|
+
responses.assert_call_count(
|
|
29
|
+
count=1, url="https://dummy.test/api/v1/openapi/?format=json"
|
|
30
|
+
)
|
|
31
|
+
assert pagination.mode is PaginationMode.PageNumber
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_cursor_mode_detection(mock_schema):
|
|
35
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
36
|
+
pagination = cli.paginate("ListProcessElements", id="process_id")
|
|
37
|
+
responses.assert_call_count(
|
|
38
|
+
count=1, url="https://dummy.test/api/v1/openapi/?format=json"
|
|
39
|
+
)
|
|
40
|
+
assert pagination.mode is PaginationMode.Cursor
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_page_pagination_with_missing_data(mock_schema, monkeypatch):
|
|
44
|
+
"""
|
|
45
|
+
Page number pagination skips erroneous pages when allow_missing_data parameter is set
|
|
46
|
+
"""
|
|
47
|
+
base_url = "https://dummy.test/api/v1/elements/"
|
|
48
|
+
|
|
49
|
+
# Disable sleeps
|
|
50
|
+
monkeypatch.setattr(time, "sleep", lambda x: None)
|
|
51
|
+
|
|
52
|
+
# Page 1
|
|
53
|
+
responses.add(
|
|
54
|
+
responses.GET,
|
|
55
|
+
base_url,
|
|
56
|
+
match=[matchers.query_param_matcher({})],
|
|
57
|
+
json={
|
|
58
|
+
"count": 9,
|
|
59
|
+
"number": 1,
|
|
60
|
+
"previous": None,
|
|
61
|
+
"next": f"{base_url}?page=2",
|
|
62
|
+
"results": [1, 2, 3],
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Page 2 is erroneous
|
|
67
|
+
# We need 3 responses, one for each retries
|
|
68
|
+
for i in range(3):
|
|
69
|
+
responses.add(
|
|
70
|
+
responses.GET,
|
|
71
|
+
base_url,
|
|
72
|
+
match=[matchers.query_param_matcher({"page": 2})],
|
|
73
|
+
status=400,
|
|
74
|
+
json={"error": "some error happened"},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Page 3
|
|
78
|
+
responses.add(
|
|
79
|
+
responses.GET,
|
|
80
|
+
base_url,
|
|
81
|
+
match=[matchers.query_param_matcher({"page": 3})],
|
|
82
|
+
json={
|
|
83
|
+
"count": 9,
|
|
84
|
+
"number": 3,
|
|
85
|
+
"previous": f"{base_url}?page=3",
|
|
86
|
+
"next": None,
|
|
87
|
+
"results": [7, 8, 9],
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
92
|
+
paginator = cli.paginate("ListElements", allow_missing_data=True, retries=3)
|
|
93
|
+
assert paginator.allow_missing_data is True
|
|
94
|
+
|
|
95
|
+
# First page is retrieved
|
|
96
|
+
assert len(paginator) == 9
|
|
97
|
+
assert len(responses.calls) == 2
|
|
98
|
+
responses.assert_call_count(count=1, url=base_url)
|
|
99
|
+
# Remaining pages are retrieved with retries
|
|
100
|
+
assert list(paginator) == [1, 2, 3, 7, 8, 9]
|
|
101
|
+
assert len(responses.calls) == 6
|
|
102
|
+
responses.assert_call_count(count=3, url=f"{base_url}?page=2")
|
|
103
|
+
# Page 2 has failed
|
|
104
|
+
assert paginator.missing == {2}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_page_pagination_incomplete(mock_schema, monkeypatch):
|
|
108
|
+
"""
|
|
109
|
+
Pagination raises an exception on repeated errors by default
|
|
110
|
+
"""
|
|
111
|
+
base_url = "https://dummy.test/api/v1/elements/"
|
|
112
|
+
|
|
113
|
+
# Disable sleeps
|
|
114
|
+
monkeypatch.setattr(time, "sleep", lambda x: None)
|
|
115
|
+
|
|
116
|
+
# Page 1
|
|
117
|
+
responses.add(
|
|
118
|
+
responses.GET,
|
|
119
|
+
base_url,
|
|
120
|
+
match=[matchers.query_param_matcher({})],
|
|
121
|
+
json={
|
|
122
|
+
"count": 9,
|
|
123
|
+
"number": 1,
|
|
124
|
+
"previous": None,
|
|
125
|
+
"next": f"{base_url}?page=2",
|
|
126
|
+
"results": ["A", "B"],
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Page 2 is erroneous
|
|
131
|
+
# Page 3 is not needed as it won't try to load it
|
|
132
|
+
responses.add(
|
|
133
|
+
responses.GET,
|
|
134
|
+
base_url,
|
|
135
|
+
match=[matchers.query_param_matcher({"page": 2})],
|
|
136
|
+
status=500,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
140
|
+
paginator = cli.paginate("ListElements", retries=1)
|
|
141
|
+
assert paginator.allow_missing_data is False
|
|
142
|
+
|
|
143
|
+
with pytest.raises(
|
|
144
|
+
Exception, match="Stopping pagination as data will be incomplete"
|
|
145
|
+
):
|
|
146
|
+
list(paginator)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_cursor_pagination_length(mock_schema):
|
|
150
|
+
"""
|
|
151
|
+
The first page should be retrieved with the `with_count` parameter in case of a cursor pagination
|
|
152
|
+
Allows the client to know the total number of results
|
|
153
|
+
"""
|
|
154
|
+
base_url = "https://dummy.test/api/v1/process/process_id/elements/"
|
|
155
|
+
|
|
156
|
+
responses.add(
|
|
157
|
+
responses.GET,
|
|
158
|
+
base_url,
|
|
159
|
+
match=[matchers.query_param_matcher({"with_count": "true"})],
|
|
160
|
+
json={
|
|
161
|
+
"count": 6,
|
|
162
|
+
"previous": None,
|
|
163
|
+
"next": f"{base_url}?cursor=DEF",
|
|
164
|
+
"results": [1, 2, 3],
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
responses.add(
|
|
168
|
+
responses.GET,
|
|
169
|
+
base_url,
|
|
170
|
+
match=[matchers.query_param_matcher({"cursor": "DEF"})],
|
|
171
|
+
json={
|
|
172
|
+
"count": None,
|
|
173
|
+
"previous": f"{base_url}?cursor=ABC",
|
|
174
|
+
"next": None,
|
|
175
|
+
"results": [4, 5, 6],
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
180
|
+
paginator = cli.paginate("ListProcessElements", id="process_id")
|
|
181
|
+
|
|
182
|
+
assert len(paginator) == 6
|
|
183
|
+
assert len(responses.calls) == 2
|
|
184
|
+
# Schema is used to detect the pagination mode
|
|
185
|
+
responses.assert_call_count(
|
|
186
|
+
count=1, url="https://dummy.test/api/v1/openapi/?format=json"
|
|
187
|
+
)
|
|
188
|
+
responses.assert_call_count(count=1, url=f"{base_url}?with_count=true")
|
|
189
|
+
assert list(paginator) == list(range(1, 7))
|
|
190
|
+
assert len(responses.calls) == 3
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_cursor_pagination_zero_results(mock_schema):
|
|
194
|
+
"""
|
|
195
|
+
Pagination should handle queries returning zero results
|
|
196
|
+
"""
|
|
197
|
+
base_url = "https://dummy.test/api/v1/process/process_id/elements/"
|
|
198
|
+
responses.add(
|
|
199
|
+
responses.GET,
|
|
200
|
+
base_url,
|
|
201
|
+
match=[matchers.query_param_matcher({"with_count": "true"})],
|
|
202
|
+
json={
|
|
203
|
+
"count": 0,
|
|
204
|
+
"previous": None,
|
|
205
|
+
"next": None,
|
|
206
|
+
"results": [],
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
211
|
+
paginator = cli.paginate("ListProcessElements", id="process_id")
|
|
212
|
+
|
|
213
|
+
assert len(paginator) == 0
|
|
214
|
+
assert list(paginator) == []
|
|
215
|
+
assert len(responses.calls) == 2
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_cursor_pagination_len_errors(mock_schema, mocker):
|
|
219
|
+
"""
|
|
220
|
+
Errors must be raised when calling the __len__ method of an iterator
|
|
221
|
+
"""
|
|
222
|
+
base_url = "https://dummy.test/api/v1/process/process_id/elements/"
|
|
223
|
+
responses.add(
|
|
224
|
+
responses.GET,
|
|
225
|
+
base_url,
|
|
226
|
+
match=[matchers.query_param_matcher({"with_count": "true"})],
|
|
227
|
+
status=500,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Prevent waiting for a random amount of time between retries
|
|
231
|
+
mocker.patch("arkindex.pagination.random.random", return_value=0.0001)
|
|
232
|
+
|
|
233
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
234
|
+
paginator = cli.paginate("ListProcessElements", id="process_id", retries=5)
|
|
235
|
+
|
|
236
|
+
with pytest.raises(
|
|
237
|
+
Exception, match="Stopping pagination as data will be incomplete"
|
|
238
|
+
):
|
|
239
|
+
len(paginator)
|
|
240
|
+
assert len(responses.calls) == 6
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_paginate_x_paginated(mock_schema):
|
|
244
|
+
responses.add(
|
|
245
|
+
responses.GET,
|
|
246
|
+
"https://dummy.test/api/v1/element/element_id/metadata/",
|
|
247
|
+
match=[matchers.query_param_matcher({})],
|
|
248
|
+
json=["a", "b"],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
252
|
+
assert cli.paginate("ListElementMetaData", id="element_id") == ["a", "b"]
|
|
253
|
+
assert len(responses.calls) == 2
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_paginate_from_initial_page(mock_schema, caplog):
|
|
257
|
+
caplog.set_level(logging.INFO)
|
|
258
|
+
base_url = "https://dummy.test/api/v1/elements/"
|
|
259
|
+
|
|
260
|
+
responses.add(
|
|
261
|
+
responses.GET,
|
|
262
|
+
base_url,
|
|
263
|
+
match=[matchers.query_param_matcher({"page": "2"})],
|
|
264
|
+
json={
|
|
265
|
+
"count": 9,
|
|
266
|
+
"number": 1,
|
|
267
|
+
"previous": f"{base_url}?page=1",
|
|
268
|
+
"next": f"{base_url}?page=3",
|
|
269
|
+
"results": [4, 5, 6],
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
responses.add(
|
|
273
|
+
responses.GET,
|
|
274
|
+
base_url,
|
|
275
|
+
match=[matchers.query_param_matcher({"page": "3"})],
|
|
276
|
+
json={
|
|
277
|
+
"count": 9,
|
|
278
|
+
"number": 1,
|
|
279
|
+
"previous": f"{base_url}?page=2",
|
|
280
|
+
"next": None,
|
|
281
|
+
"results": [7, 8, 9],
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
286
|
+
assert list(cli.paginate("ListElements", page=2)) == [4, 5, 6, 7, 8, 9]
|
|
287
|
+
assert len(responses.calls) == 3
|
|
288
|
+
assert [(level, message) for _module, level, message in caplog.record_tuples] == [
|
|
289
|
+
(logging.INFO, "Loading page 2 on try 1/5"),
|
|
290
|
+
(logging.INFO, "Pagination will load a total of 2 pages."),
|
|
291
|
+
(logging.INFO, "Loading page 3 on try 1/5 - remains 1 page to load."),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_paginate_from_initial_cursor(mock_schema, caplog):
|
|
296
|
+
caplog.set_level(logging.INFO)
|
|
297
|
+
base_url = "https://dummy.test/api/v1/process/process_id/elements/"
|
|
298
|
+
|
|
299
|
+
responses.add(
|
|
300
|
+
responses.GET,
|
|
301
|
+
base_url,
|
|
302
|
+
match=[matchers.query_param_matcher({"cursor": "DEF", "with_count": "true"})],
|
|
303
|
+
json={
|
|
304
|
+
"count": 7,
|
|
305
|
+
"previous": f"{base_url}?cursor=ABC",
|
|
306
|
+
"next": f"{base_url}?cursor=GHI",
|
|
307
|
+
"results": [4, 5, 6],
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
responses.add(
|
|
311
|
+
responses.GET,
|
|
312
|
+
base_url,
|
|
313
|
+
match=[matchers.query_param_matcher({"cursor": "GHI"})],
|
|
314
|
+
json={
|
|
315
|
+
"count": 7,
|
|
316
|
+
"previous": f"{base_url}?cursor=ABC",
|
|
317
|
+
"next": None,
|
|
318
|
+
"results": [7],
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
|
|
322
|
+
assert list(cli.paginate("ListProcessElements", id="process_id", cursor="DEF")) == [
|
|
323
|
+
4,
|
|
324
|
+
5,
|
|
325
|
+
6,
|
|
326
|
+
7,
|
|
327
|
+
]
|
|
328
|
+
assert len(responses.calls) == 3
|
|
329
|
+
assert [(level, message) for _module, level, message in caplog.record_tuples] == [
|
|
330
|
+
(logging.INFO, "Loading page DEF on try 1/5"),
|
|
331
|
+
(logging.INFO, "Pagination will load a maximum of 3 pages"),
|
|
332
|
+
(
|
|
333
|
+
logging.INFO,
|
|
334
|
+
"Loading cursor GHI on try 1/5 - remains a maximum of 2 pages to load.",
|
|
335
|
+
),
|
|
336
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from arkindex.exceptions import ErrorResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_request_no_more_retries(responses, dummy_client):
|
|
8
|
+
# First call is an error
|
|
9
|
+
responses.add(
|
|
10
|
+
responses.POST,
|
|
11
|
+
"https://dummy.test/api/v1/user/login/",
|
|
12
|
+
status=502,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
dummy_client.request.retry.wait = 0
|
|
16
|
+
|
|
17
|
+
# There should be 5 retries
|
|
18
|
+
max_retries = 5
|
|
19
|
+
|
|
20
|
+
with pytest.raises(ErrorResponse):
|
|
21
|
+
dummy_client.login("user@user.user", "Pa$$w0rd")
|
|
22
|
+
|
|
23
|
+
assert len(responses.calls) == max_retries
|
|
24
|
+
assert [call.request.url for call in responses.calls] == [
|
|
25
|
+
"https://dummy.test/api/v1/user/login/",
|
|
26
|
+
] * max_retries
|
arkindex-client-1.1.5/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.1.5
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import warnings
|
|
3
|
-
|
|
4
|
-
from arkindex.exceptions import ErrorResponse
|
|
5
|
-
|
|
6
|
-
__all__ = ["ErrorResponse"]
|
|
7
|
-
|
|
8
|
-
warnings.warn(
|
|
9
|
-
"The Arkindex API client no longer depends on APIStar. "
|
|
10
|
-
"Please update your `apistar.exceptions` imports to use the `arkindex.exceptions` module.",
|
|
11
|
-
FutureWarning,
|
|
12
|
-
stacklevel=2,
|
|
13
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arkindex-client-1.1.5 → arkindex_client-1.2.1}/arkindex_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|