arkindex-client 1.1.4__tar.gz → 1.2.0__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.
Files changed (36) hide show
  1. {arkindex-client-1.1.4/arkindex_client.egg-info → arkindex_client-1.2.0}/PKG-INFO +15 -2
  2. arkindex_client-1.2.0/VERSION +1 -0
  3. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/mock.py +4 -1
  4. {arkindex-client-1.1.4 → arkindex_client-1.2.0/arkindex_client.egg-info}/PKG-INFO +15 -2
  5. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex_client.egg-info/SOURCES.txt +7 -4
  6. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex_client.egg-info/requires.txt +1 -1
  7. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex_client.egg-info/top_level.txt +0 -1
  8. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/requirements.txt +1 -1
  9. arkindex_client-1.2.0/tests/test_auth.py +54 -0
  10. arkindex_client-1.2.0/tests/test_env.py +74 -0
  11. arkindex_client-1.2.0/tests/test_init.py +85 -0
  12. arkindex_client-1.2.0/tests/test_mock.py +118 -0
  13. arkindex_client-1.2.0/tests/test_pagination.py +336 -0
  14. arkindex_client-1.2.0/tests/test_request.py +26 -0
  15. arkindex-client-1.1.4/VERSION +0 -1
  16. arkindex-client-1.1.4/apistar/__init__.py +0 -9
  17. arkindex-client-1.1.4/apistar/exceptions.py +0 -13
  18. arkindex-client-1.1.4/requirements-docs.txt +0 -3
  19. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/LICENSE +0 -0
  20. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/MANIFEST.in +0 -0
  21. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/README.md +0 -0
  22. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/__init__.py +0 -0
  23. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/auth.py +0 -0
  24. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/client/__init__.py +0 -0
  25. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/client/client.py +0 -0
  26. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/client/decoders.py +0 -0
  27. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/compat.py +0 -0
  28. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/document.py +0 -0
  29. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/exceptions.py +0 -0
  30. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/pagination.py +0 -0
  31. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/schema/__init__.py +0 -0
  32. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/schema/openapi.py +0 -0
  33. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex/schema/validator.py +0 -0
  34. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/arkindex_client.egg-info/dependency_links.txt +0 -0
  35. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/setup.cfg +0 -0
  36. {arkindex-client-1.1.4 → arkindex_client-1.2.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: arkindex-client
3
- Version: 1.1.4
3
+ Version: 1.2.0
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.0
@@ -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(object):
13
+ class MockApiClient(ArkindexClient):
13
14
  """A mockup of the Arkindex API Client to build unit tests"""
14
15
 
15
16
  def __init__(self):
17
+ super().__init__()
18
+
16
19
  self.history = []
17
20
  self.responses = []
18
21
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: arkindex-client
3
- Version: 1.1.4
3
+ Version: 1.2.0
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
@@ -1,3 +1,3 @@
1
1
  requests>=2.30
2
- tenacity==8.2.3
2
+ tenacity>=9.1.0
3
3
  typesystem==0.4.1
@@ -1,3 +1,3 @@
1
1
  requests>=2.30
2
- tenacity==8.2.3
2
+ tenacity>=9.1.0
3
3
  typesystem==0.4.1
@@ -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
@@ -1 +0,0 @@
1
- 1.1.4
@@ -1,9 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import warnings
3
-
4
- warnings.warn(
5
- "The Arkindex API client no longer depends on APIStar. "
6
- "Please update your `apistar` imports to use the `arkindex` package.",
7
- FutureWarning,
8
- stacklevel=2,
9
- )
@@ -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
- )
@@ -1,3 +0,0 @@
1
- black==24.2.0
2
- mkdocs-autorefs==1.0.1
3
- mkdocs-material==9.5.10
File without changes