python-documentcloud 3.7.1__tar.gz → 4.0.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.
- {python-documentcloud-3.7.1/python_documentcloud.egg-info → python-documentcloud-4.0.0}/PKG-INFO +13 -16
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/README.md +8 -10
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/addon.py +33 -3
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/annotations.py +3 -8
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/base.py +21 -40
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/client.py +8 -13
- python-documentcloud-4.0.0/documentcloud/constants.py +101 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/documents.py +80 -74
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/exceptions.py +2 -4
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/organizations.py +0 -7
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/projects.py +11 -22
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/sections.py +4 -11
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/toolbox.py +4 -9
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/users.py +0 -7
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0/python_documentcloud.egg-info}/PKG-INFO +13 -16
- python-documentcloud-4.0.0/setup.py +59 -0
- python-documentcloud-3.7.1/documentcloud/constants.py +0 -104
- python-documentcloud-3.7.1/setup.py +0 -60
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/LICENSE +0 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/documentcloud/__init__.py +0 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/python_documentcloud.egg-info/SOURCES.txt +0 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/python_documentcloud.egg-info/dependency_links.txt +0 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/python_documentcloud.egg-info/requires.txt +2 -2
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/python_documentcloud.egg-info/top_level.txt +0 -0
- {python-documentcloud-3.7.1 → python-documentcloud-4.0.0}/setup.cfg +0 -0
{python-documentcloud-3.7.1/python_documentcloud.egg-info → python-documentcloud-4.0.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-documentcloud
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.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
|
|
@@ -11,13 +11,12 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 2
|
|
16
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
19
14
|
Classifier: Programming Language :: Python :: 3.7
|
|
20
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
20
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
Provides-Extra: dev
|
|
@@ -32,19 +31,17 @@ License-File: LICENSE
|
|
|
32
31
|
|
|
33
32
|
A simple python wrapper for the DocumentCloud API
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
- Documentation: [http://documentcloud.readthedocs.org/](http://documentcloud.readthedocs.org/)
|
|
35
|
+
- Issues: [https://github.com/muckrock/python-documentcloud/issues](https://github.com/muckrock/python-documentcloud/issues)
|
|
36
|
+
- Packaging: [https://pypi.python.org/pypi/python-documentcloud](https://pypi.python.org/pypi/python-documentcloud)
|
|
38
37
|
|
|
39
|
-
Features
|
|
40
|
-
--------
|
|
38
|
+
## Features
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
- Retrieve and edit documents and projects, both public and private, from documentcloud.org
|
|
41
|
+
- Upload PDFs into your documentcloud.org account and organize them into projects
|
|
42
|
+
- Download text and images extracted from your PDFs by DocumentCloud
|
|
45
43
|
|
|
46
|
-
Getting started
|
|
47
|
-
---------------
|
|
44
|
+
## Getting started
|
|
48
45
|
|
|
49
46
|
Installation is as easy as...
|
|
50
47
|
|
|
@@ -6,19 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
A simple python wrapper for the DocumentCloud API
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- Documentation: [http://documentcloud.readthedocs.org/](http://documentcloud.readthedocs.org/)
|
|
10
|
+
- Issues: [https://github.com/muckrock/python-documentcloud/issues](https://github.com/muckrock/python-documentcloud/issues)
|
|
11
|
+
- Packaging: [https://pypi.python.org/pypi/python-documentcloud](https://pypi.python.org/pypi/python-documentcloud)
|
|
12
12
|
|
|
13
|
-
Features
|
|
14
|
-
--------
|
|
13
|
+
## Features
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- Retrieve and edit documents and projects, both public and private, from documentcloud.org
|
|
16
|
+
- Upload PDFs into your documentcloud.org account and organize them into projects
|
|
17
|
+
- Download text and images extracted from your PDFs by DocumentCloud
|
|
19
18
|
|
|
20
|
-
Getting started
|
|
21
|
-
---------------
|
|
19
|
+
## Getting started
|
|
22
20
|
|
|
23
21
|
Installation is as easy as...
|
|
24
22
|
|
|
@@ -24,7 +24,7 @@ class BaseAddOn:
|
|
|
24
24
|
|
|
25
25
|
def __init__(self):
|
|
26
26
|
args = self._parse_arguments()
|
|
27
|
-
|
|
27
|
+
self._create_client(args)
|
|
28
28
|
|
|
29
29
|
# a unique identifier for this run
|
|
30
30
|
self.id = args.pop("id", None)
|
|
@@ -42,6 +42,8 @@ class BaseAddOn:
|
|
|
42
42
|
self.org_id = args.pop("organization", None)
|
|
43
43
|
# add on specific data
|
|
44
44
|
self.data = args.pop("data", None)
|
|
45
|
+
# title of the addon
|
|
46
|
+
self.title = args.pop("title", None)
|
|
45
47
|
|
|
46
48
|
def _create_client(self, args):
|
|
47
49
|
client_kwargs = {
|
|
@@ -63,7 +65,7 @@ class BaseAddOn:
|
|
|
63
65
|
self.client.refresh_token = args["refresh_token"]
|
|
64
66
|
if args["token"] is not None:
|
|
65
67
|
self.client.session.headers.update(
|
|
66
|
-
{"Authorization": "Bearer {
|
|
68
|
+
{"Authorization": f"Bearer {args['token']}"}
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
# custom user agent for AddOns
|
|
@@ -117,9 +119,11 @@ class BaseAddOn:
|
|
|
117
119
|
|
|
118
120
|
# validate parameter data
|
|
119
121
|
try:
|
|
120
|
-
with open("config.yaml") as config:
|
|
122
|
+
with open("config.yaml", encoding="utf-8") as config:
|
|
121
123
|
schema = yaml.safe_load(config)
|
|
122
124
|
args["data"] = fastjsonschema.validate(schema, args["data"])
|
|
125
|
+
# add title in case the add-on wants to reference its own title
|
|
126
|
+
args["title"] = schema.get("title")
|
|
123
127
|
except FileNotFoundError:
|
|
124
128
|
pass
|
|
125
129
|
except fastjsonschema.JsonSchemaException as exc:
|
|
@@ -171,6 +175,7 @@ class AddOn(BaseAddOn):
|
|
|
171
175
|
else:
|
|
172
176
|
# text file's buffer is in binary mode
|
|
173
177
|
data = file.buffer
|
|
178
|
+
# pylint: disable=W3101
|
|
174
179
|
response = requests.put(presigned_url, data=data)
|
|
175
180
|
response.raise_for_status()
|
|
176
181
|
return self.client.patch(
|
|
@@ -203,6 +208,8 @@ class AddOn(BaseAddOn):
|
|
|
203
208
|
documents = self.client.documents.search(self.query)
|
|
204
209
|
return documents.count
|
|
205
210
|
|
|
211
|
+
return 0
|
|
212
|
+
|
|
206
213
|
def get_documents(self):
|
|
207
214
|
"""Get documents from either selected or queried documents"""
|
|
208
215
|
if self.documents:
|
|
@@ -214,6 +221,29 @@ class AddOn(BaseAddOn):
|
|
|
214
221
|
|
|
215
222
|
yield from documents
|
|
216
223
|
|
|
224
|
+
def charge_credits(self, amount):
|
|
225
|
+
"""Charge the organization a certain amount of premium credits"""
|
|
226
|
+
|
|
227
|
+
if not self.id:
|
|
228
|
+
print(f"Charge credits: {amount}")
|
|
229
|
+
return None
|
|
230
|
+
elif not self.org_id:
|
|
231
|
+
self.set_message("No organization to charge.")
|
|
232
|
+
raise ValueError
|
|
233
|
+
|
|
234
|
+
resp = self.client.post(
|
|
235
|
+
f"organizations/{self.org_id}/ai_credits/",
|
|
236
|
+
json={
|
|
237
|
+
"ai_credits": amount,
|
|
238
|
+
"addonrun_id": self.id,
|
|
239
|
+
"note": f"AddOn run: {self.title} - {self.id}",
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
if resp.status_code != 200:
|
|
243
|
+
self.set_message("Error charging AI credits.")
|
|
244
|
+
raise ValueError
|
|
245
|
+
return resp
|
|
246
|
+
|
|
217
247
|
|
|
218
248
|
class CronAddOn(BaseAddOn):
|
|
219
249
|
"""DEPREACTED"""
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
# Future
|
|
2
|
-
from __future__ import division, print_function, unicode_literals
|
|
3
|
-
|
|
4
1
|
# Third Party
|
|
5
|
-
from future.utils import python_2_unicode_compatible
|
|
6
2
|
from listcrunch.listcrunch import uncrunch
|
|
7
3
|
|
|
8
4
|
# Local
|
|
@@ -10,7 +6,6 @@ from .base import BaseAPIObject, ChildAPIClient
|
|
|
10
6
|
from .toolbox import merge_dicts
|
|
11
7
|
|
|
12
8
|
|
|
13
|
-
@python_2_unicode_compatible
|
|
14
9
|
class Annotation(BaseAPIObject):
|
|
15
10
|
"""A note on a document"""
|
|
16
11
|
|
|
@@ -30,7 +25,7 @@ class Annotation(BaseAPIObject):
|
|
|
30
25
|
|
|
31
26
|
@property
|
|
32
27
|
def api_path(self):
|
|
33
|
-
return "documents/{
|
|
28
|
+
return f"documents/{self.document.id}/notes"
|
|
34
29
|
|
|
35
30
|
@property
|
|
36
31
|
def location(self):
|
|
@@ -71,7 +66,7 @@ class AnnotationClient(ChildAPIClient):
|
|
|
71
66
|
|
|
72
67
|
@property
|
|
73
68
|
def api_path(self):
|
|
74
|
-
return "documents/{
|
|
69
|
+
return f"documents/{self.parent.id}/notes"
|
|
75
70
|
|
|
76
71
|
def create(
|
|
77
72
|
self,
|
|
@@ -102,7 +97,7 @@ class AnnotationClient(ChildAPIClient):
|
|
|
102
97
|
"x2": x2,
|
|
103
98
|
"y2": y2,
|
|
104
99
|
}
|
|
105
|
-
response = self.client.post(self.api_path
|
|
100
|
+
response = self.client.post(f"{self.api_path}/", json=data)
|
|
106
101
|
return Annotation(
|
|
107
102
|
self.client, merge_dicts(response.json(), {"document": self.parent})
|
|
108
103
|
)
|
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
# Future
|
|
2
|
-
from __future__ import division, print_function, unicode_literals
|
|
3
|
-
|
|
4
1
|
# Standard Library
|
|
5
|
-
from builtins import str
|
|
6
2
|
from copy import copy
|
|
7
3
|
|
|
8
4
|
# Third Party
|
|
9
5
|
from dateutil.parser import parse as dateparser
|
|
10
|
-
from future.utils import python_2_unicode_compatible
|
|
11
6
|
|
|
12
7
|
# Local
|
|
13
8
|
from .exceptions import DuplicateObjectError
|
|
14
9
|
from .toolbox import get_id, merge_dicts
|
|
15
10
|
|
|
16
11
|
|
|
17
|
-
@python_2_unicode_compatible
|
|
18
12
|
class APIResults(object):
|
|
19
13
|
"""Class for encapsulating paginated list results from the API"""
|
|
20
14
|
|
|
@@ -39,10 +33,10 @@ class APIResults(object):
|
|
|
39
33
|
]
|
|
40
34
|
|
|
41
35
|
def __repr__(self):
|
|
42
|
-
return "<APIResults: {!r}"
|
|
36
|
+
return f"<APIResults: {self.results!r}>" # pragma: no cover
|
|
43
37
|
|
|
44
38
|
def __str__(self):
|
|
45
|
-
return "[{
|
|
39
|
+
return f"[{', '.join(str(r) for r in self.results)}]"
|
|
46
40
|
|
|
47
41
|
def __getitem__(self, key):
|
|
48
42
|
# pylint: disable=unsubscriptable-object
|
|
@@ -104,21 +98,19 @@ class BaseAPIClient(object):
|
|
|
104
98
|
params = {"expand": ",".join(expand)}
|
|
105
99
|
else:
|
|
106
100
|
params = {}
|
|
107
|
-
response = self.client.get(
|
|
108
|
-
"{}/{}/".format(self.api_path, get_id(id_)), params=params
|
|
109
|
-
)
|
|
101
|
+
response = self.client.get(f"{self.api_path}/{get_id(id_)}/", params=params)
|
|
110
102
|
# pylint: disable=not-callable
|
|
111
103
|
return self.resource(self.client, response.json())
|
|
112
104
|
|
|
113
105
|
def delete(self, id_):
|
|
114
106
|
"""Deletes a resource"""
|
|
115
|
-
self.client.delete("{}/{
|
|
107
|
+
self.client.delete(f"{self.api_path}/{get_id(id_)}")
|
|
116
108
|
|
|
117
109
|
def all(self, **params):
|
|
118
110
|
return self.list(**params)
|
|
119
111
|
|
|
120
112
|
def list(self, **params):
|
|
121
|
-
response = self.client.get(self.api_path
|
|
113
|
+
response = self.client.get(f"{self.api_path}/", params=params)
|
|
122
114
|
return APIResults(self.resource, self.client, response)
|
|
123
115
|
|
|
124
116
|
|
|
@@ -126,11 +118,11 @@ class ChildAPIClient(BaseAPIClient):
|
|
|
126
118
|
"""Base client for sub resources"""
|
|
127
119
|
|
|
128
120
|
def __init__(self, client, parent):
|
|
129
|
-
super(
|
|
121
|
+
super().__init__(client)
|
|
130
122
|
self.parent = parent
|
|
131
123
|
|
|
132
124
|
def list(self, **params):
|
|
133
|
-
response = self.client.get(self.api_path
|
|
125
|
+
response = self.client.get(f"{self.api_path}/", params=params)
|
|
134
126
|
parent_name = self.parent.__class__.__name__.lower()
|
|
135
127
|
return APIResults(
|
|
136
128
|
self.resource, self.client, response, {parent_name: self.parent}
|
|
@@ -156,9 +148,7 @@ class BaseAPIObject(object):
|
|
|
156
148
|
setattr(self, field, dateparser(getattr(self, field)))
|
|
157
149
|
|
|
158
150
|
def __repr__(self):
|
|
159
|
-
return "<{}: {} - {}>"
|
|
160
|
-
self.__class__.__name__, self.id, self
|
|
161
|
-
) # pragma: no cover
|
|
151
|
+
return f"<{self.__class__.__name__}: {self.id} - {self}>" # pragma: no cover
|
|
162
152
|
|
|
163
153
|
def __eq__(self, obj):
|
|
164
154
|
return isinstance(obj, type(self)) and self.id == obj.id
|
|
@@ -169,65 +159,56 @@ class BaseAPIObject(object):
|
|
|
169
159
|
|
|
170
160
|
def save(self):
|
|
171
161
|
data = {f: getattr(self, f) for f in self.writable_fields if hasattr(self, f)}
|
|
172
|
-
self._client.put("{}/{}/"
|
|
162
|
+
self._client.put(f"{self.api_path}/{self.id}/", json=data)
|
|
173
163
|
|
|
174
164
|
def delete(self):
|
|
175
|
-
self._client.delete("{}/{
|
|
165
|
+
self._client.delete(f"{self.api_path}/{self.id}")
|
|
176
166
|
|
|
177
167
|
|
|
178
|
-
@python_2_unicode_compatible
|
|
179
168
|
class APISet(list):
|
|
180
169
|
def __init__(self, iterable, resource):
|
|
181
|
-
super(
|
|
170
|
+
super().__init__(iterable)
|
|
182
171
|
self.resource = resource
|
|
183
172
|
if not all(isinstance(obj, self.resource) for obj in self):
|
|
184
173
|
raise TypeError(
|
|
185
|
-
"Only {} can be added to this list"
|
|
186
|
-
self.resource.__class__.__name__
|
|
187
|
-
)
|
|
174
|
+
f"Only {self.resource.__class__.__name__} can be added to this list"
|
|
188
175
|
)
|
|
189
176
|
ids = [obj.id for obj in self]
|
|
190
177
|
for id_ in ids:
|
|
191
178
|
if ids.count(id_) > 1:
|
|
192
179
|
raise DuplicateObjectError(
|
|
193
|
-
"Object with ID {} appears in the list more than once"
|
|
180
|
+
f"Object with ID {id_} appears in the list more than once"
|
|
194
181
|
)
|
|
195
182
|
|
|
196
183
|
def append(self, obj):
|
|
197
184
|
if not isinstance(obj, self.resource):
|
|
198
185
|
raise TypeError(
|
|
199
|
-
"Only {} can be added to this list"
|
|
200
|
-
self.resource.__class__.__name__
|
|
201
|
-
)
|
|
186
|
+
f"Only {self.resource.__class__.__name__} can be added to this list"
|
|
202
187
|
)
|
|
203
188
|
if obj.id in [i.id for i in self]:
|
|
204
189
|
raise DuplicateObjectError(
|
|
205
|
-
"Object with ID {} appears in the list more than once"
|
|
190
|
+
f"Object with ID {obj.id} appears in the list more than once"
|
|
206
191
|
)
|
|
207
|
-
super(
|
|
192
|
+
super().append(copy(obj))
|
|
208
193
|
|
|
209
194
|
def add(self, obj):
|
|
210
195
|
if not isinstance(obj, self.resource):
|
|
211
196
|
raise TypeError(
|
|
212
|
-
"Only {} can be added to this list"
|
|
213
|
-
self.resource.__class__.__name__
|
|
214
|
-
)
|
|
197
|
+
f"Only {self.resource.__class__.__name__} can be added to this list"
|
|
215
198
|
)
|
|
216
199
|
# skip duplicates silently
|
|
217
200
|
if obj.id not in [i.id for i in self]:
|
|
218
|
-
super(
|
|
201
|
+
super().append(copy(obj))
|
|
219
202
|
|
|
220
203
|
def extend(self, list_):
|
|
221
204
|
if not all(isinstance(obj, self.resource) for obj in list_):
|
|
222
205
|
raise TypeError(
|
|
223
|
-
"Only {} can be added to this list"
|
|
224
|
-
self.resource.__class__.__name__
|
|
225
|
-
)
|
|
206
|
+
f"Only {self.resource.__class__.__name__} can be added to this list"
|
|
226
207
|
)
|
|
227
208
|
ids = [obj.id for obj in self + list_]
|
|
228
209
|
for id_ in ids:
|
|
229
210
|
if ids.count(id_) > 1:
|
|
230
211
|
raise DuplicateObjectError(
|
|
231
|
-
"Object with ID {} appears in the list more than once"
|
|
212
|
+
f"Object with ID {id_} appears in the list more than once"
|
|
232
213
|
)
|
|
233
|
-
super(
|
|
214
|
+
super().extend(copy(obj) for obj in list_)
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
The public interface for the DocumentCloud API
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
# Future
|
|
6
|
-
from __future__ import division, print_function, unicode_literals
|
|
7
|
-
|
|
8
5
|
# Standard Library
|
|
9
6
|
import logging
|
|
10
7
|
from functools import partial
|
|
@@ -84,20 +81,18 @@ class DocumentCloud(object):
|
|
|
84
81
|
access_token = None
|
|
85
82
|
|
|
86
83
|
if access_token:
|
|
87
|
-
self.session.headers.update(
|
|
88
|
-
{"Authorization": "Bearer {}".format(access_token)}
|
|
89
|
-
)
|
|
84
|
+
self.session.headers.update({"Authorization": f"Bearer {access_token}"})
|
|
90
85
|
|
|
91
86
|
def _get_tokens(self, username, password):
|
|
92
87
|
"""Get an access and refresh token in exchange for the username and password"""
|
|
93
88
|
response = requests_retry_session().post(
|
|
94
|
-
"{}token/"
|
|
89
|
+
f"{self.auth_uri}token/",
|
|
95
90
|
json={"username": username, "password": password},
|
|
96
91
|
timeout=self.timeout,
|
|
97
92
|
)
|
|
98
93
|
|
|
99
94
|
if response.status_code == requests.codes.UNAUTHORIZED:
|
|
100
|
-
raise CredentialsFailedError("The username and password
|
|
95
|
+
raise CredentialsFailedError("The username and password are incorrect")
|
|
101
96
|
|
|
102
97
|
self.raise_for_status(response)
|
|
103
98
|
|
|
@@ -107,7 +102,7 @@ class DocumentCloud(object):
|
|
|
107
102
|
def _refresh_tokens(self, refresh_token):
|
|
108
103
|
"""Refresh the access and refresh tokens"""
|
|
109
104
|
response = requests_retry_session().post(
|
|
110
|
-
"{}refresh/"
|
|
105
|
+
f"{self.auth_uri}refresh/",
|
|
111
106
|
json={"refresh": refresh_token},
|
|
112
107
|
timeout=self.timeout,
|
|
113
108
|
)
|
|
@@ -136,7 +131,7 @@ class DocumentCloud(object):
|
|
|
136
131
|
full_url = kwargs.pop("full_url", False)
|
|
137
132
|
|
|
138
133
|
if not full_url:
|
|
139
|
-
url = "{}{}"
|
|
134
|
+
url = f"{self.base_uri}{url}"
|
|
140
135
|
|
|
141
136
|
# set the API to version 2.0
|
|
142
137
|
parsed_url = urlparse(url)
|
|
@@ -165,7 +160,7 @@ class DocumentCloud(object):
|
|
|
165
160
|
if attr in methods:
|
|
166
161
|
return partial(self._request, attr)
|
|
167
162
|
raise AttributeError(
|
|
168
|
-
"'{}' object has no attribute '{}'"
|
|
163
|
+
f"'{self.__class__.__name__}' object has no attribute '{attr}'"
|
|
169
164
|
)
|
|
170
165
|
|
|
171
166
|
def raise_for_status(self, response):
|
|
@@ -174,6 +169,6 @@ class DocumentCloud(object):
|
|
|
174
169
|
response.raise_for_status()
|
|
175
170
|
except requests.exceptions.RequestException as exc:
|
|
176
171
|
if exc.response.status_code == 404:
|
|
177
|
-
raise DoesNotExistError(response=exc.response)
|
|
172
|
+
raise DoesNotExistError(response=exc.response) from exc
|
|
178
173
|
else:
|
|
179
|
-
raise APIError(response=exc.response)
|
|
174
|
+
raise APIError(response=exc.response) from exc
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
PER_PAGE_MAX = 100
|
|
2
|
+
BULK_LIMIT = 25
|
|
3
|
+
BASE_URI = "https://api.www.documentcloud.org/api/"
|
|
4
|
+
AUTH_URI = "https://accounts.muckrock.com/api/"
|
|
5
|
+
TIMEOUT = 20
|
|
6
|
+
RATE_LIMIT = 10
|
|
7
|
+
RATE_PERIOD = 1
|
|
8
|
+
SUPPORTED_EXTENSIONS = [
|
|
9
|
+
".abw",
|
|
10
|
+
".zabw",
|
|
11
|
+
".md",
|
|
12
|
+
".pm3",
|
|
13
|
+
".pm4",
|
|
14
|
+
".pm5",
|
|
15
|
+
".pm6",
|
|
16
|
+
".p65",
|
|
17
|
+
".cwk",
|
|
18
|
+
".agd",
|
|
19
|
+
".fhd",
|
|
20
|
+
".kth",
|
|
21
|
+
".key",
|
|
22
|
+
".numbers",
|
|
23
|
+
".pages",
|
|
24
|
+
".bmp",
|
|
25
|
+
".csv",
|
|
26
|
+
".txt",
|
|
27
|
+
".cdr",
|
|
28
|
+
".cmx",
|
|
29
|
+
".cgm",
|
|
30
|
+
".dif",
|
|
31
|
+
".dbf",
|
|
32
|
+
".xml",
|
|
33
|
+
".eps",
|
|
34
|
+
".emf",
|
|
35
|
+
".fb2",
|
|
36
|
+
".gnm",
|
|
37
|
+
".gnumeric",
|
|
38
|
+
".gif",
|
|
39
|
+
".hwp",
|
|
40
|
+
".plt",
|
|
41
|
+
".html",
|
|
42
|
+
".htm",
|
|
43
|
+
".jtd",
|
|
44
|
+
".jtt",
|
|
45
|
+
".jpg",
|
|
46
|
+
".jpeg",
|
|
47
|
+
".wk1",
|
|
48
|
+
".wks",
|
|
49
|
+
".123",
|
|
50
|
+
".wk3",
|
|
51
|
+
".wk4",
|
|
52
|
+
".pct",
|
|
53
|
+
".mml",
|
|
54
|
+
".xls",
|
|
55
|
+
".xlw",
|
|
56
|
+
".xlt",
|
|
57
|
+
".xlsx",
|
|
58
|
+
".docx",
|
|
59
|
+
".pptx",
|
|
60
|
+
".ppt",
|
|
61
|
+
".pps",
|
|
62
|
+
".pot",
|
|
63
|
+
".pptx",
|
|
64
|
+
".pub",
|
|
65
|
+
".rtf",
|
|
66
|
+
".xml",
|
|
67
|
+
".doc",
|
|
68
|
+
".dot",
|
|
69
|
+
".docx",
|
|
70
|
+
".wps",
|
|
71
|
+
".wks",
|
|
72
|
+
".wdb",
|
|
73
|
+
".wri",
|
|
74
|
+
".vsd",
|
|
75
|
+
".pgm",
|
|
76
|
+
".pbm",
|
|
77
|
+
".ppm",
|
|
78
|
+
".odt",
|
|
79
|
+
".fodt",
|
|
80
|
+
".ods",
|
|
81
|
+
".fods",
|
|
82
|
+
".odp",
|
|
83
|
+
".fodp",
|
|
84
|
+
".odg",
|
|
85
|
+
".fodg",
|
|
86
|
+
".odf",
|
|
87
|
+
".odb",
|
|
88
|
+
".sxw",
|
|
89
|
+
".stw",
|
|
90
|
+
".sxc",
|
|
91
|
+
".stc",
|
|
92
|
+
".sxi",
|
|
93
|
+
".sti",
|
|
94
|
+
".sxd",
|
|
95
|
+
".std",
|
|
96
|
+
".sxm",
|
|
97
|
+
".pcx",
|
|
98
|
+
".pcd",
|
|
99
|
+
".psd",
|
|
100
|
+
".pdf",
|
|
101
|
+
]
|