python-documentcloud 4.1.2__tar.gz → 4.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 (32) hide show
  1. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/PKG-INFO +20 -5
  2. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/base.py +1 -1
  3. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/constants.py +42 -1
  4. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/python_documentcloud.egg-info/PKG-INFO +20 -5
  5. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/python_documentcloud.egg-info/SOURCES.txt +10 -1
  6. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/setup.py +1 -1
  7. python-documentcloud-4.2.0/tests/test_annotations.py +50 -0
  8. python-documentcloud-4.2.0/tests/test_base.py +114 -0
  9. python-documentcloud-4.2.0/tests/test_client.py +113 -0
  10. python-documentcloud-4.2.0/tests/test_documents.py +238 -0
  11. python-documentcloud-4.2.0/tests/test_organizarions.py +11 -0
  12. python-documentcloud-4.2.0/tests/test_projects.py +111 -0
  13. python-documentcloud-4.2.0/tests/test_sections.py +32 -0
  14. python-documentcloud-4.2.0/tests/test_toolbox.py +25 -0
  15. python-documentcloud-4.2.0/tests/test_users.py +10 -0
  16. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/LICENSE +0 -0
  17. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/README.md +0 -0
  18. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/__init__.py +0 -0
  19. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/addon.py +0 -0
  20. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/annotations.py +0 -0
  21. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/client.py +0 -0
  22. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/documents.py +0 -0
  23. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/exceptions.py +0 -0
  24. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/organizations.py +0 -0
  25. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/projects.py +0 -0
  26. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/sections.py +0 -0
  27. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/toolbox.py +0 -0
  28. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/documentcloud/users.py +0 -0
  29. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/python_documentcloud.egg-info/dependency_links.txt +0 -0
  30. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/python_documentcloud.egg-info/requires.txt +2 -2
  31. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/python_documentcloud.egg-info/top_level.txt +0 -0
  32. {python-documentcloud-4.1.2 → python-documentcloud-4.2.0}/setup.cfg +0 -0
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-documentcloud
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: A simple Python wrapper for the DocumentCloud API
5
5
  Home-page: https://github.com/muckrock/python-documentcloud
6
6
  Author: Mitchell Kotler
7
7
  Author-email: mitch@muckrock.com
8
8
  License: MIT
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
@@ -19,9 +18,27 @@ Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Topic :: Internet :: WWW/HTTP
21
20
  Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: future
23
+ Requires-Dist: listcrunch>=1.0.1
24
+ Requires-Dist: python-dateutil
25
+ Requires-Dist: ratelimit
26
+ Requires-Dist: requests
27
+ Requires-Dist: urllib3
28
+ Requires-Dist: pyyaml
29
+ Requires-Dist: fastjsonschema
22
30
  Provides-Extra: dev
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: coverage; extra == "dev"
33
+ Requires-Dist: isort; extra == "dev"
34
+ Requires-Dist: pylint; extra == "dev"
35
+ Requires-Dist: sphinx; extra == "dev"
36
+ Requires-Dist: twine; extra == "dev"
23
37
  Provides-Extra: test
24
- License-File: LICENSE
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: pytest-mock; extra == "test"
40
+ Requires-Dist: pytest-recording; extra == "test"
41
+ Requires-Dist: vcrpy; extra == "test"
25
42
 
26
43
  <pre><code> ____ _ ____ _ _
27
44
  | _ \ ___ ___ _ _ _ __ ___ ___ _ __ | |_ / ___| | ___ _ _ __| |
@@ -48,5 +65,3 @@ Installation is as easy as...
48
65
  ```bash
49
66
  $ pip install python-documentcloud
50
67
  ```
51
-
52
-
@@ -162,7 +162,7 @@ class BaseAPIObject(object):
162
162
  self._client.put(f"{self.api_path}/{self.id}/", json=data)
163
163
 
164
164
  def delete(self):
165
- self._client.delete(f"{self.api_path}/{self.id}")
165
+ self._client.delete(f"{self.api_path}/{self.id}/")
166
166
 
167
167
 
168
168
  class APISet(list):
@@ -8,7 +8,7 @@ RATE_PERIOD = 1
8
8
  SUPPORTED_EXTENSIONS = [
9
9
  ".abw",
10
10
  ".zabw",
11
- ".md",
11
+ ".pmd",
12
12
  ".pm3",
13
13
  ".pm4",
14
14
  ".pm5",
@@ -51,11 +51,16 @@ SUPPORTED_EXTENSIONS = [
51
51
  ".wk4",
52
52
  ".pct",
53
53
  ".mml",
54
+ ".xml",
55
+ ".xls",
56
+ ".xlw",
57
+ ".xlt",
54
58
  ".xls",
55
59
  ".xlw",
56
60
  ".xlt",
57
61
  ".xlsx",
58
62
  ".docx",
63
+ ".xlsx",
59
64
  ".pptx",
60
65
  ".ppt",
61
66
  ".pps",
@@ -97,5 +102,41 @@ SUPPORTED_EXTENSIONS = [
97
102
  ".pcx",
98
103
  ".pcd",
99
104
  ".psd",
105
+ ".txt",
100
106
  ".pdf",
107
+ ".png",
108
+ ".qxp",
109
+ ".wb2",
110
+ ".wq1",
111
+ ".wq2",
112
+ ".svg",
113
+ ".sgv",
114
+ ".602",
115
+ ".txt",
116
+ ".sdc",
117
+ ".vor",
118
+ ".sda",
119
+ ".sdd",
120
+ ".sdp",
121
+ ".vor",
122
+ ".sdw",
123
+ ".sgl",
124
+ ".vor",
125
+ ".sgf",
126
+ ".rlf",
127
+ ".ras",
128
+ ".svm",
129
+ ".slk",
130
+ ".tif",
131
+ ".tiff",
132
+ ".tga",
133
+ ".uof",
134
+ ".uot",
135
+ ".uos",
136
+ ".uop",
137
+ ".wpd",
138
+ ".wps",
139
+ ".xbm",
140
+ ".xpm",
141
+ ".zmf",
101
142
  ]
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-documentcloud
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: A simple Python wrapper for the DocumentCloud API
5
5
  Home-page: https://github.com/muckrock/python-documentcloud
6
6
  Author: Mitchell Kotler
7
7
  Author-email: mitch@muckrock.com
8
8
  License: MIT
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
@@ -19,9 +18,27 @@ Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Topic :: Internet :: WWW/HTTP
21
20
  Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: future
23
+ Requires-Dist: listcrunch>=1.0.1
24
+ Requires-Dist: python-dateutil
25
+ Requires-Dist: ratelimit
26
+ Requires-Dist: requests
27
+ Requires-Dist: urllib3
28
+ Requires-Dist: pyyaml
29
+ Requires-Dist: fastjsonschema
22
30
  Provides-Extra: dev
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: coverage; extra == "dev"
33
+ Requires-Dist: isort; extra == "dev"
34
+ Requires-Dist: pylint; extra == "dev"
35
+ Requires-Dist: sphinx; extra == "dev"
36
+ Requires-Dist: twine; extra == "dev"
23
37
  Provides-Extra: test
24
- License-File: LICENSE
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: pytest-mock; extra == "test"
40
+ Requires-Dist: pytest-recording; extra == "test"
41
+ Requires-Dist: vcrpy; extra == "test"
25
42
 
26
43
  <pre><code> ____ _ ____ _ _
27
44
  | _ \ ___ ___ _ _ _ __ ___ ___ _ __ | |_ / ___| | ___ _ _ __| |
@@ -48,5 +65,3 @@ Installation is as easy as...
48
65
  ```bash
49
66
  $ pip install python-documentcloud
50
67
  ```
51
-
52
-
@@ -19,4 +19,13 @@ python_documentcloud.egg-info/PKG-INFO
19
19
  python_documentcloud.egg-info/SOURCES.txt
20
20
  python_documentcloud.egg-info/dependency_links.txt
21
21
  python_documentcloud.egg-info/requires.txt
22
- python_documentcloud.egg-info/top_level.txt
22
+ python_documentcloud.egg-info/top_level.txt
23
+ tests/test_annotations.py
24
+ tests/test_base.py
25
+ tests/test_client.py
26
+ tests/test_documents.py
27
+ tests/test_organizarions.py
28
+ tests/test_projects.py
29
+ tests/test_sections.py
30
+ tests/test_toolbox.py
31
+ tests/test_users.py
@@ -7,7 +7,7 @@ with open("README.md", "r") as fh:
7
7
 
8
8
  setup(
9
9
  name="python-documentcloud",
10
- version="4.1.2",
10
+ version="4.2.0",
11
11
  description="A simple Python wrapper for the DocumentCloud API",
12
12
  author="Mitchell Kotler",
13
13
  author_email="mitch@muckrock.com",
@@ -0,0 +1,50 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+ # Third Party
8
+ import pytest
9
+
10
+ # DocumentCloud
11
+ from documentcloud.annotations import Annotation
12
+
13
+
14
+ class TestAnnotation:
15
+ def test_create_delete(self, document):
16
+ assert len(document.notes.list().results) == 1
17
+ note = document.notes.create(
18
+ "Test Note", 0, "<p>Note content!</p>", x1=0.1, y1=0.1, x2=0.2, y2=0.2
19
+ )
20
+ assert len(document.notes.list().results) == 2
21
+ for note in document.notes:
22
+ assert isinstance(note, Annotation)
23
+ note.delete()
24
+ assert len(document.notes.list().results) == 1
25
+
26
+ def test_create_page_note(self, document):
27
+ note = document.notes.create("Test Note", 0, "<p>Page note!</p>")
28
+ assert note
29
+ note.delete()
30
+
31
+ def test_create_partial_coords(self, document):
32
+ with pytest.raises(ValueError):
33
+ document.notes.create("Test Note", 0, "<p>Page note!</p>", x1=0.5)
34
+
35
+ def test_create_invalid_coords(self, document):
36
+ with pytest.raises(ValueError):
37
+ document.notes.create(
38
+ "Test Note", 0, "<p>Page note!</p>", x1=0.5, y1=1.5, x2=2, y2=3
39
+ )
40
+
41
+ def test_str(self, document):
42
+ assert str(document.notes[0])
43
+
44
+ def test_alias(self, document):
45
+ assert document.notes is document.annotations
46
+
47
+ def test_location(self, document):
48
+ note = document.notes[0]
49
+ assert note.location.top < note.location.bottom
50
+ assert note.location.left < note.location.right
@@ -0,0 +1,114 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+ # Third Party
8
+ import pytest
9
+
10
+ # DocumentCloud
11
+ from documentcloud.documents import Document
12
+ from documentcloud.exceptions import DuplicateObjectError
13
+
14
+
15
+ class TestAPIResults:
16
+ def test_str(self, client):
17
+ results = client.documents.list()
18
+ assert str(results)
19
+
20
+ def test_getitem(self, client):
21
+ results = client.documents.list()
22
+ assert isinstance(results[0], Document)
23
+
24
+ def test_getitem_paginate(self, client):
25
+ results = client.documents.list(per_page=2)
26
+ assert isinstance(results[3], Document)
27
+
28
+ def test_getitem_index_error(self, client):
29
+ # pylint: disable=pointless-statement
30
+ results = client.documents.list()
31
+ index = len(list(results)) + 1
32
+ with pytest.raises(IndexError):
33
+ results[index]
34
+
35
+ def test_len(self, client):
36
+ results = client.documents.list()
37
+ assert len(results.results) > 0
38
+
39
+ def test_iter(self, client):
40
+ results = client.documents.list()
41
+ for doc in results:
42
+ assert isinstance(doc, Document)
43
+
44
+ def test_next(self, client):
45
+ results = client.documents.list(user=client.user_id, per_page=1)
46
+ assert len(results.results) > 0
47
+ while results.next is not None:
48
+ results = results.next
49
+
50
+ def test_previous(self, client):
51
+ results = client.documents.list(user=client.user_id, per_page=1).next
52
+ assert results.previous
53
+ assert results.previous.previous is None
54
+
55
+
56
+ class TestAPISet:
57
+ def test_init(self, project_factory, document):
58
+ project = project_factory()
59
+ document_list = project.document_list
60
+ project.document_list = [document]
61
+ project.document_list = document_list
62
+
63
+ def test_init_bad_types(self, project):
64
+ with pytest.raises(TypeError):
65
+ project.document_list = [1, 2, 3]
66
+
67
+ def test_init_dupes(self, project, document):
68
+ with pytest.raises(DuplicateObjectError):
69
+ project.document_list = [document, document]
70
+
71
+ def test_append(self, project, document_factory):
72
+ document = document_factory()
73
+ project.document_list.append(document)
74
+ assert project.document_list[-1] == document
75
+ project.document_list.remove(document)
76
+
77
+ def test_append_bad_type(self, project):
78
+ with pytest.raises(TypeError):
79
+ project.document_list.append(1)
80
+
81
+ def test_append_dupes(self, project, document):
82
+ with pytest.raises(DuplicateObjectError):
83
+ project.document_list.append(document)
84
+
85
+ def test_add(self, project, document_factory):
86
+ document = document_factory()
87
+ project.document_list.add(document)
88
+ assert document in project.document_list
89
+ project.document_list.remove(document)
90
+
91
+ def test_add_bad_type(self, project):
92
+ with pytest.raises(TypeError):
93
+ project.document_list.add(1)
94
+
95
+ def test_add_dupe(self, project, document):
96
+ assert document in project.document_list
97
+ length = len(project.document_list)
98
+ project.document_list.add(document)
99
+ assert document in project.document_list
100
+ assert len(project.document_list) == length
101
+
102
+ def test_extend(self, project, document_factory):
103
+ document = document_factory()
104
+ project.document_list.extend([document])
105
+ assert document == project.document_list[-1]
106
+ project.document_list.remove(document)
107
+
108
+ def test_extend_bad_type(self, project):
109
+ with pytest.raises(TypeError):
110
+ project.document_list.extend([1])
111
+
112
+ def test_extend_dupe(self, project, document):
113
+ with pytest.raises(DuplicateObjectError):
114
+ project.document_list.extend([document])
@@ -0,0 +1,113 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ import time
6
+
7
+ # Third Party
8
+ import pytest
9
+ import ratelimit
10
+
11
+ # DocumentCloud
12
+ from documentcloud.constants import RATE_LIMIT
13
+ from documentcloud.exceptions import APIError, CredentialsFailedError
14
+
15
+ # pylint: disable=protected-access
16
+
17
+
18
+ def test_set_tokens_credentials(client):
19
+ """Test setting the tokens using credentials"""
20
+ client.refresh_token = None
21
+ del client.session.headers["Authorization"]
22
+ client._set_tokens()
23
+ assert client.refresh_token
24
+ assert "Authorization" in client.session.headers
25
+
26
+
27
+ def test_set_tokens_refresh(client):
28
+ """Test setting the tokens using refresh token"""
29
+ # first set tokens sets, refresh token, second one uses it
30
+ client.refresh_token = None
31
+ del client.session.headers["Authorization"]
32
+ client._set_tokens()
33
+ client._set_tokens()
34
+ assert client.refresh_token
35
+ assert "Authorization" in client.session.headers
36
+
37
+
38
+ def test_set_tokens_none(public_client):
39
+ """Test setting the tokens with no credentials"""
40
+ public_client._set_tokens()
41
+ assert public_client.refresh_token is None
42
+ assert "Authorization" not in public_client.session.headers
43
+
44
+
45
+ def test_get_tokens(client):
46
+ """Test getting access and refresh tokens using valid credentials"""
47
+ access, refresh = client._get_tokens(client.username, client.password)
48
+ assert access
49
+ assert refresh
50
+
51
+
52
+ def test_get_tokens_bad_credentials(client):
53
+ """Test getting access and refresh tokens using invalid credentials"""
54
+ with pytest.raises(CredentialsFailedError):
55
+ client._get_tokens(client.username, "foo")
56
+
57
+
58
+ def test_refresh_tokens(client):
59
+ """Test refreshing the tokens"""
60
+ access, refresh = client._refresh_tokens(client.refresh_token)
61
+ assert access
62
+ assert refresh
63
+
64
+
65
+ def test_user_id(client):
66
+ assert client.user_id
67
+
68
+
69
+ def test_user_id_public(public_client):
70
+ # pylint: disable=pointless-statement
71
+ with pytest.raises(APIError, match=r"404"):
72
+ public_client.user_id
73
+
74
+
75
+ def test_bad_attr(client):
76
+ with pytest.raises(AttributeError):
77
+ assert client.foo
78
+
79
+
80
+ def test_rate_limit(rate_client):
81
+ with pytest.raises(ratelimit.RateLimitException):
82
+ for _ in range(RATE_LIMIT * 2):
83
+ rate_client.users.get("me")
84
+
85
+
86
+ @pytest.mark.short
87
+ @pytest.mark.vcr(cassette_library_dir="tests/cassettes/short_fixtures")
88
+ def test_expired_access_token(short_client, record_mode):
89
+ # get fresh tokens
90
+ short_client._set_tokens()
91
+ old_refresh_token = short_client.refresh_token
92
+ # wait for the access token to expire
93
+ if record_mode == "all":
94
+ time.sleep(3)
95
+ # make a request
96
+ assert short_client.users.get("me")
97
+ # check the refresh token was updated
98
+ assert old_refresh_token != short_client.refresh_token
99
+
100
+
101
+ @pytest.mark.short
102
+ @pytest.mark.vcr(cassette_library_dir="tests/cassettes/short_fixtures")
103
+ def test_expired_refresh_token(short_client, record_mode):
104
+ # get fresh tokens
105
+ short_client._set_tokens()
106
+ old_refresh_token = short_client.refresh_token
107
+ # wait for the access and refresh tokens to expire
108
+ if record_mode == "all":
109
+ time.sleep(6)
110
+ # make a request
111
+ assert short_client.users.get("me")
112
+ # check the refresh token was updated
113
+ assert old_refresh_token != short_client.refresh_token
@@ -0,0 +1,238 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+ from datetime import datetime
7
+
8
+ # Third Party
9
+ import pytest
10
+
11
+ # DocumentCloud
12
+ from documentcloud.documents import Mention
13
+ from documentcloud.exceptions import APIError, DoesNotExistError
14
+ from documentcloud.organizations import Organization
15
+ from documentcloud.users import User
16
+
17
+ # pylint: disable=protected-access
18
+
19
+
20
+ class TestDocument:
21
+ def test_str(self, document):
22
+ assert str(document) == document.title
23
+
24
+ def test_dates(self, document):
25
+ for date_field in document.date_fields:
26
+ assert isinstance(getattr(document, date_field), datetime)
27
+
28
+ @pytest.mark.parametrize(
29
+ "attr",
30
+ [
31
+ "full_text_url",
32
+ "full_text",
33
+ "thumbnail_image_url",
34
+ "small_image",
35
+ "normal_image_url_list",
36
+ "large_image_url",
37
+ "page_text",
38
+ "json_text_url",
39
+ "pdf",
40
+ ],
41
+ )
42
+ def test_getattr(self, document, attr):
43
+ assert getattr(document, attr)
44
+
45
+ @pytest.mark.parametrize(
46
+ "attr",
47
+ [
48
+ "get_full_text_url",
49
+ "get_full_text",
50
+ "get_thumbnail_image_url",
51
+ "get_small_image",
52
+ "get_normal_image_url_list",
53
+ "get_large_image_url",
54
+ "get_page_text",
55
+ "get_json_text_url",
56
+ "get_pdf",
57
+ ],
58
+ )
59
+ def test_getattr_method(self, document, attr):
60
+ assert getattr(document, attr)()
61
+
62
+ @pytest.mark.parametrize(
63
+ "attr",
64
+ [
65
+ "full_text_url",
66
+ "get_full_text",
67
+ "thumbnail_image_url",
68
+ "get_small_image",
69
+ "normal_image_url_list",
70
+ "get_large_image_url",
71
+ ],
72
+ )
73
+ def test_dir(self, document, attr):
74
+ assert attr in dir(document)
75
+
76
+ def test_mentions(self, client, document):
77
+ document = client.documents.search(
78
+ f"document:{document.id} text", mentions="true"
79
+ )[0]
80
+ assert document.mentions
81
+ mention = document.mentions[0]
82
+ assert mention.page
83
+ assert "<em>text</em>" in mention.text
84
+
85
+ def test_mentions_nosearch(self, document):
86
+ assert not document.mentions
87
+
88
+ def test_user(self, document):
89
+ assert document._user is None
90
+ assert isinstance(document.user, User)
91
+ assert document.user == document._user
92
+
93
+ def test_user_expanded(self, client, document):
94
+ document = client.documents.get(document.id, expand=["user"])
95
+ assert document._user is not None
96
+ assert document._user == document.user
97
+
98
+ def test_organization(self, document):
99
+ assert document._organization is None
100
+ assert isinstance(document.organization, Organization)
101
+ assert document.organization == document._organization
102
+
103
+ @pytest.mark.parametrize(
104
+ "attr",
105
+ [
106
+ "id",
107
+ "access",
108
+ "asset_url",
109
+ "canonical_url",
110
+ "created_at",
111
+ "data",
112
+ "description",
113
+ "edit_access",
114
+ "language",
115
+ "organization_id",
116
+ "page_count",
117
+ "page_spec",
118
+ "projects",
119
+ "related_article",
120
+ "published_url",
121
+ "slug",
122
+ "source",
123
+ "status",
124
+ "title",
125
+ "updated_at",
126
+ "user_id",
127
+ "pages",
128
+ "contributor",
129
+ "contributor_organization",
130
+ "contributor_organization_slug",
131
+ ],
132
+ )
133
+ def test_attrs(self, document, attr):
134
+ assert getattr(document, attr)
135
+
136
+ def test_save(self, client, document):
137
+ assert document.source == "DocumentCloud"
138
+ document.source = "MuckRock"
139
+ document.save()
140
+ document = client.documents.get(document.id)
141
+ assert document.source == "MuckRock"
142
+
143
+ def test_delete(self, client, document_factory):
144
+ document = document_factory()
145
+ document.delete()
146
+
147
+ with pytest.raises(DoesNotExistError):
148
+ client.documents.get(document.id)
149
+
150
+ def test_section(self, document_factory):
151
+ document = document_factory()
152
+ assert len(document.sections.list().results) == 0
153
+ section = document.sections.create("Test Section", 0)
154
+ assert str(section) == "Test Section - p0"
155
+ assert section.page == 0
156
+ assert section == document.sections.list()[0]
157
+
158
+
159
+ class TestDocumentClient:
160
+ def test_search(self, client, document):
161
+ documents = client.documents.search(
162
+ f"document:{document.id} simple"
163
+ )
164
+ assert documents
165
+
166
+ def test_list(self, client):
167
+ # list and all are aliases
168
+ all_documents = client.documents.all()
169
+ my_documents = client.documents.list(user=client.user_id)
170
+ assert len(list(all_documents)) > len(list(my_documents.results))
171
+
172
+ def test_upload_url(self, document_factory):
173
+ document = document_factory()
174
+ assert document.status == "success"
175
+
176
+ def test_public_upload(self, public_client):
177
+ with pytest.raises(APIError, match=r"403"):
178
+ public_client.documents.upload("tests/test.pdf")
179
+
180
+ def test_upload_file(self, document_factory):
181
+ with open("tests/test.pdf", "rb") as pdf:
182
+ document = document_factory(pdf)
183
+ assert document.status == "success"
184
+
185
+
186
+ def test_upload_file_path(self, document_factory):
187
+ document = document_factory("tests/test.pdf")
188
+ assert document.status == "success"
189
+
190
+ def test_upload_big_file(self, client, mocker):
191
+ mocker.patch("os.path.getsize", return_value=502 * 1024 * 1024)
192
+ with pytest.raises(ValueError):
193
+ client.documents.upload("tests/test.pdf")
194
+
195
+ def test_upload_dir(self, client):
196
+ documents = client.documents.upload_directory("tests/pdfs/")
197
+ assert len(documents) == 2
198
+
199
+ def test_format_upload_parameters(self, client):
200
+ with pytest.warns(UserWarning):
201
+ params = client.documents._format_upload_parameters(
202
+ "tests/test.pdf", access="private", secure=True, project=2, foo="bar"
203
+ )
204
+ assert params == {"title": "test", "access": "private", "projects": [2]}
205
+
206
+ def test_delete(self, document_factory, client):
207
+ document = document_factory()
208
+ client.documents.delete(document.id)
209
+
210
+ with pytest.raises(DoesNotExistError):
211
+ client.documents.get(document.id)
212
+
213
+
214
+ class TestMention:
215
+ def test_mention(self):
216
+ mention = Mention("page_no_42", "text")
217
+ assert str(mention) == '42 - "text"'
218
+
219
+
220
+ class TestSection:
221
+ def test_create_delete(self, document_factory):
222
+ document = document_factory()
223
+ assert len(document.sections.list().results) == 0
224
+ section = document.sections.create("Test Section", 0)
225
+ assert len(document.sections.list().results) == 1
226
+
227
+ # may not have two sections on the same page
228
+ with pytest.raises(APIError):
229
+ document.sections.create("Test Section 2", 0)
230
+
231
+ section.delete()
232
+ assert len(document.sections.list().results) == 0
233
+
234
+ def test_str(self, document):
235
+ assert str(document.sections[0])
236
+
237
+ def test_page(self, document):
238
+ assert document.sections[0].page == 0
@@ -0,0 +1,11 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+
8
+ def test_organization(client):
9
+ user = client.users.get(client.user_id)
10
+ organization = client.organizations.get(user.organization)
11
+ assert str(organization) == organization.name
@@ -0,0 +1,111 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+ # Third Party
8
+ import pytest
9
+
10
+ # DocumentCloud
11
+ from documentcloud.documents import Document
12
+ from documentcloud.exceptions import DoesNotExistError, MultipleObjectsReturnedError
13
+
14
+
15
+ class TestProject:
16
+ def test_str(self, project):
17
+ assert str(project) == project.title
18
+
19
+ def test_save(self, client, project, document_factory):
20
+ document = document_factory()
21
+ assert document not in project.documents
22
+ project.documents.append(document)
23
+ # put is an alias for save
24
+ project.put()
25
+ project = client.projects.get(project.id)
26
+ assert document in project.documents
27
+
28
+ def test_document_list(self, project):
29
+ assert len(project.document_list) > 0
30
+ assert all(isinstance(d, Document) for d in project.document_list)
31
+
32
+ def test_document_list_paginate(self, project):
33
+ # pylint: disable=protected-access
34
+ length = len(project.document_list)
35
+ assert length > 1
36
+ # clear cache
37
+ project._document_list = None
38
+ # set per page to 1 to force pagination
39
+ project._per_page = 1
40
+ assert len(project.document_list) == length
41
+
42
+ def test_document_list_setter(self, project, document):
43
+ assert document in project.document_list
44
+ # setting to none clears it and sets to an empty list
45
+ project.document_list = None
46
+ assert document not in project.document_list
47
+ # documents is an alias for document_list
48
+ project.documents = [document]
49
+ assert document in project.document_list
50
+ with pytest.raises(TypeError):
51
+ project.document_list = document
52
+
53
+ def test_document_ids(self, project, document):
54
+ assert document.id in project.document_ids
55
+
56
+ def test_get_document(self, project, document):
57
+ assert project.get_document(document.id)
58
+
59
+ def test_get_document_missing(self, project, document_factory):
60
+ document = document_factory()
61
+ with pytest.raises(DoesNotExistError):
62
+ project.get_document(document.id)
63
+
64
+
65
+ class TestProjectClient:
66
+ def test_list(self, client):
67
+ all_projects = client.projects.list()
68
+ my_projects = client.projects.all()
69
+ assert len(all_projects.results) > len(my_projects.results)
70
+ assert len(client.projects.list(user=client.user_id).results) == len(
71
+ my_projects.results
72
+ )
73
+
74
+ def test_get_id(self, client, project):
75
+ assert client.projects.get(id=project.id)
76
+
77
+ def test_get_title(self, client, project):
78
+ assert client.projects.get(title=project.title)
79
+
80
+ def test_get_nothing(self, client):
81
+ with pytest.raises(ValueError):
82
+ client.projects.get()
83
+
84
+ def test_get_both(self, client, project):
85
+ with pytest.raises(ValueError):
86
+ client.projects.get(id=project.id, title=project.title)
87
+
88
+ def test_get_by_id(self, client, project):
89
+ assert client.projects.get_by_id(project.id)
90
+
91
+ def test_get_by_title(self, client, project):
92
+ assert client.projects.get_by_title(project.title)
93
+
94
+ def test_get_by_title_multiple(self, client, project_factory):
95
+ for _ in range(2):
96
+ project_factory(title="Dupe")
97
+ with pytest.raises(MultipleObjectsReturnedError):
98
+ client.projects.get_by_title("Dupe")
99
+
100
+ def test_get_or_create_by_title_get(self, client, project):
101
+ title = project.title
102
+ project, created = client.projects.get_or_create_by_title(title)
103
+ assert project.title == title
104
+ assert not created
105
+
106
+ def test_get_or_create_by_title_create(self, client):
107
+ title = "Created Title"
108
+ project, created = client.projects.get_or_create_by_title(title)
109
+ assert project.title == title
110
+ project.delete()
111
+ assert created
@@ -0,0 +1,32 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+ # Third Party
8
+ import pytest
9
+
10
+ # DocumentCloud
11
+ from documentcloud.exceptions import APIError
12
+
13
+
14
+ class TestSection:
15
+ def test_create_delete(self, document_factory):
16
+ document = document_factory()
17
+ assert len(document.sections.list().results) == 0
18
+ section = document.sections.create("Test Section", 0)
19
+ assert len(document.sections.list().results) == 1
20
+
21
+ # may not have two sections on the same page
22
+ with pytest.raises(APIError):
23
+ document.sections.create("Test Section 2", 0)
24
+
25
+ section.delete()
26
+ assert len(document.sections.list().results) == 0
27
+
28
+ def test_str(self, document):
29
+ assert str(document.sections[0])
30
+
31
+ def test_page(self, document):
32
+ assert document.sections[0].page == 0
@@ -0,0 +1,25 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # DocumentCloud
5
+ from documentcloud.toolbox import get_id
6
+
7
+
8
+ def test_get_id_number():
9
+ assert get_id(42) == 42
10
+
11
+
12
+ def test_get_id_str():
13
+ assert get_id("42") == "42"
14
+
15
+
16
+ def test_get_id_prefix():
17
+ assert get_id("42-foo-bar") == "42"
18
+
19
+
20
+ def test_get_id_postfix():
21
+ assert get_id("foo-bar-42") == "42"
22
+
23
+
24
+ def test_get_id_both():
25
+ assert get_id("42-foo-bar-123") == "42"
@@ -0,0 +1,10 @@
1
+ # Future
2
+ from __future__ import division, print_function, unicode_literals
3
+
4
+ # Standard Library
5
+ from builtins import str
6
+
7
+
8
+ def test_user(client):
9
+ user = client.users.get(client.user_id)
10
+ assert str(user) == user.username
@@ -1,11 +1,11 @@
1
- fastjsonschema
2
1
  future
3
2
  listcrunch>=1.0.1
4
3
  python-dateutil
5
- pyyaml
6
4
  ratelimit
7
5
  requests
8
6
  urllib3
7
+ pyyaml
8
+ fastjsonschema
9
9
 
10
10
  [dev]
11
11
  black