pypaperless 2.2.0__tar.gz → 2.3.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 (48) hide show
  1. {pypaperless-2.2.0/pypaperless.egg-info → pypaperless-2.3.0}/PKG-INFO +9 -5
  2. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/__init__.py +132 -34
  3. pypaperless-2.3.0/pypaperless/const.py +65 -0
  4. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/controllers/__init__.py +6 -0
  5. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/controllers/default.py +36 -0
  6. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/controllers/documents.py +6 -22
  7. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/controllers/tasks.py +5 -3
  8. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/errors.py +4 -0
  9. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/__init__.py +15 -7
  10. pypaperless-2.3.0/pypaperless/models/base.py +9 -0
  11. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/custom_fields.py +20 -1
  12. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/documents.py +2 -1
  13. pypaperless-2.2.0/pypaperless/models/mail_rules.py → pypaperless-2.3.0/pypaperless/models/mails.py +18 -1
  14. pypaperless-2.3.0/pypaperless/models/matching.py +137 -0
  15. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/share_links.py +16 -3
  16. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/tasks.py +15 -1
  17. pypaperless-2.3.0/pypaperless/models/workflows.py +130 -0
  18. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/util.py +21 -0
  19. {pypaperless-2.2.0 → pypaperless-2.3.0/pypaperless.egg-info}/PKG-INFO +9 -5
  20. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless.egg-info/SOURCES.txt +11 -9
  21. pypaperless-2.3.0/pypaperless.egg-info/requires.txt +17 -0
  22. {pypaperless-2.2.0 → pypaperless-2.3.0}/pyproject.toml +9 -5
  23. pypaperless-2.3.0/tests/test_paperless_0_0_0.py +754 -0
  24. pypaperless-2.3.0/tests/test_paperless_1_17_0.py +88 -0
  25. pypaperless-2.3.0/tests/test_paperless_1_8_0.py +124 -0
  26. pypaperless-2.3.0/tests/test_paperless_2_0_0.py +268 -0
  27. pypaperless-2.3.0/tests/test_paperless_2_3_0.py +206 -0
  28. pypaperless-2.3.0/tests/test_paperless_common.py +209 -0
  29. pypaperless-2.2.0/pypaperless/models/base.py +0 -34
  30. pypaperless-2.2.0/pypaperless/models/consumption_templates.py +0 -30
  31. pypaperless-2.2.0/pypaperless/models/correspondents.py +0 -27
  32. pypaperless-2.2.0/pypaperless/models/document_types.py +0 -25
  33. pypaperless-2.2.0/pypaperless/models/mail_accounts.py +0 -22
  34. pypaperless-2.2.0/pypaperless/models/shared.py +0 -106
  35. pypaperless-2.2.0/pypaperless/models/storage_paths.py +0 -27
  36. pypaperless-2.2.0/pypaperless/models/tags.py +0 -33
  37. pypaperless-2.2.0/pypaperless.egg-info/requires.txt +0 -13
  38. {pypaperless-2.2.0 → pypaperless-2.3.0}/LICENSE +0 -0
  39. {pypaperless-2.2.0 → pypaperless-2.3.0}/MANIFEST.in +0 -0
  40. {pypaperless-2.2.0 → pypaperless-2.3.0}/README.md +0 -0
  41. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/controllers/base.py +0 -0
  42. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/groups.py +0 -0
  43. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/saved_views.py +0 -0
  44. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless/models/users.py +0 -0
  45. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless.egg-info/dependency_links.txt +0 -0
  46. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless.egg-info/not-zip-safe +0 -0
  47. {pypaperless-2.2.0 → pypaperless-2.3.0}/pypaperless.egg-info/top_level.txt +0 -0
  48. {pypaperless-2.2.0 → pypaperless-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pypaperless
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Little api wrapper for the paperless(-ngx) dms.
5
5
  Author-email: Tobias Schulz <public.dev@tbsch.de>
6
6
  License: MIT License
@@ -19,18 +19,22 @@ Classifier: Programming Language :: Python :: 3
19
19
  Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: aiohttp
22
+ Requires-Dist: aiohttp==3.9.1
23
+ Requires-Dist: awesomeversion==23.11.0
24
+ Requires-Dist: yarl==1.9.4
23
25
  Provides-Extra: test
24
26
  Requires-Dist: black==23.12.1; extra == "test"
25
27
  Requires-Dist: codespell==2.2.6; extra == "test"
26
28
  Requires-Dist: mypy==1.8.0; extra == "test"
27
- Requires-Dist: ruff==0.1.9; extra == "test"
28
- Requires-Dist: pytest==7.4.3; extra == "test"
29
- Requires-Dist: pytest-asyncio==0.23.2; extra == "test"
29
+ Requires-Dist: ruff==0.1.11; extra == "test"
30
+ Requires-Dist: pytest==7.4.4; extra == "test"
31
+ Requires-Dist: pytest-asyncio==0.23.3; extra == "test"
30
32
  Requires-Dist: pytest-aiohttp==1.0.5; extra == "test"
31
33
  Requires-Dist: pytest-cov==4.1.0; extra == "test"
32
34
  Requires-Dist: pre-commit==3.6.0; extra == "test"
33
35
  Requires-Dist: pylint==3.0.3; extra == "test"
36
+ Requires-Dist: fastapi==0.108.0; extra == "test"
37
+ Requires-Dist: httpx==0.26.0; extra == "test"
34
38
 
35
39
  # PyPaperless
36
40
 
@@ -6,7 +6,17 @@ from contextlib import asynccontextmanager
6
6
  from typing import Any
7
7
 
8
8
  import aiohttp
9
-
9
+ from awesomeversion import AwesomeVersion
10
+ from yarl import URL
11
+
12
+ from .const import (
13
+ PAPERLESS_V1_8_0,
14
+ PAPERLESS_V1_17_0,
15
+ PAPERLESS_V2_0_0,
16
+ PAPERLESS_V2_3_0,
17
+ ControllerPath,
18
+ PaperlessFeature,
19
+ )
10
20
  from .controllers import (
11
21
  ConsumptionTemplatesController,
12
22
  CorrespondentsController,
@@ -22,17 +32,20 @@ from .controllers import (
22
32
  TagsController,
23
33
  TasksController,
24
34
  UsersController,
35
+ WorkflowActionsController,
36
+ WorkflowsController,
37
+ WorkflowTriggersController,
25
38
  )
26
- from .errors import BadRequestException, DataNotExpectedException
27
- from .models.shared import ResourceType
39
+ from .errors import BadRequestException, ControllerConfusion, DataNotExpectedException
40
+ from .util import create_url_from_input
28
41
 
29
42
 
30
- class Paperless: # pylint: disable=too-many-instance-attributes
43
+ class Paperless: # pylint: disable=too-many-instance-attributes,too-many-public-methods
31
44
  """Retrieves and manipulates data from and to paperless via REST."""
32
45
 
33
46
  def __init__(
34
47
  self,
35
- host: str,
48
+ url: str | URL,
36
49
  token: str,
37
50
  request_opts: dict[str, Any] | None = None,
38
51
  session: aiohttp.ClientSession | None = None,
@@ -41,18 +54,19 @@ class Paperless: # pylint: disable=too-many-instance-attributes
41
54
  Initialize the Paperless api instance.
42
55
 
43
56
  Parameters:
44
- * host: the hostname or IP-address of Paperless as string.
57
+ * host: the hostname or IP-address of Paperless as string, or yarl.URL object.
45
58
  * token: provide an api token created in Paperless Django settings.
46
59
  * session: provide an existing aiohttp ClientSession.
47
60
  """
48
- self._host = host
61
+ self._url = create_url_from_input(url)
49
62
  self._token = token
50
63
  self._request_opts = request_opts
51
64
  self._session = session
52
65
  self._initialized = False
53
- self.logger = logging.getLogger(f"{__package__}[{host}]")
66
+ self.logger = logging.getLogger(f"{__package__}[{self._url.host}]")
54
67
 
55
- # endpoints
68
+ self.features: PaperlessFeature = PaperlessFeature(0)
69
+ # api controllers
56
70
  self._consumption_templates: ConsumptionTemplatesController | None = None
57
71
  self._correspondents: CorrespondentsController | None = None
58
72
  self._custom_fields: CustomFieldsController | None = None
@@ -67,11 +81,14 @@ class Paperless: # pylint: disable=too-many-instance-attributes
67
81
  self._tags: TagsController | None = None
68
82
  self._tasks: TasksController | None = None
69
83
  self._users: UsersController | None = None
84
+ self._workflows: WorkflowsController | None = None
85
+ self._workflow_actions: WorkflowActionsController | None = None
86
+ self._workflow_triggers: WorkflowTriggersController | None = None
70
87
 
71
88
  @property
72
- def host(self) -> str | None:
73
- """Return the hostname of Paperless."""
74
- return self._host
89
+ def url(self) -> URL:
90
+ """Return the url of Paperless."""
91
+ return self._url
75
92
 
76
93
  @property
77
94
  def is_initialized(self) -> bool:
@@ -148,33 +165,95 @@ class Paperless: # pylint: disable=too-many-instance-attributes
148
165
  """Gateway to users."""
149
166
  return self._users
150
167
 
168
+ @property
169
+ def workflows(self) -> WorkflowsController | None:
170
+ """Gateway to workflows."""
171
+ return self._workflows
172
+
173
+ @property
174
+ def workflow_actions(self) -> WorkflowActionsController | None:
175
+ """Gateway to workflow actions."""
176
+ return self._workflow_actions
177
+
178
+ @property
179
+ def workflow_triggers(self) -> WorkflowTriggersController | None:
180
+ """Gateway to workflow triggers."""
181
+ return self._workflow_triggers
182
+
151
183
  async def initialize(self) -> None:
152
184
  """Initialize the connection to the api and fetch the endpoints."""
153
185
  self.logger.info("Fetching api endpoints.")
154
186
 
155
- res = await self.request_json("get", "")
156
-
157
- self._consumption_templates = ConsumptionTemplatesController(
158
- self, res.pop(ResourceType.CONSUMPTION_TEMPLATES)
187
+ async with self.generate_request("get", f"{self._url}") as res:
188
+ version = AwesomeVersion(
189
+ res.headers.get("x-version") if "x-version" in res.headers else "0.0.0"
190
+ )
191
+
192
+ if version >= PAPERLESS_V1_8_0:
193
+ self.features |= PaperlessFeature.CONTROLLER_STORAGE_PATHS
194
+ if version >= PAPERLESS_V1_17_0:
195
+ self.features |= PaperlessFeature.FEATURE_DOCUMENT_NOTES
196
+ if version >= PAPERLESS_V2_0_0:
197
+ self.features |= (
198
+ PaperlessFeature.CONTROLLER_SHARE_LINKS
199
+ | PaperlessFeature.CONTROLLER_CONSUMPTION_TEMPLATES
200
+ | PaperlessFeature.CONTROLLER_CUSTOM_FIELDS
201
+ )
202
+ if version >= PAPERLESS_V2_3_0:
203
+ self.features |= (
204
+ PaperlessFeature.CONTROLLER_CONFIGS | PaperlessFeature.CONTROLLER_WORKFLOWS
205
+ )
206
+ self.features ^= PaperlessFeature.CONTROLLER_CONSUMPTION_TEMPLATES
207
+
208
+ paths = await res.json()
209
+
210
+ self._correspondents = CorrespondentsController(
211
+ self, paths.pop(ControllerPath.CORRESPONDENTS)
212
+ )
213
+ self._documents = DocumentsController(self, paths.pop(ControllerPath.DOCUMENTS))
214
+ self._document_types = DocumentTypesController(
215
+ self, paths.pop(ControllerPath.DOCUMENT_TYPES)
159
216
  )
160
- self._correspondents = CorrespondentsController(self, res.pop(ResourceType.CORRESPONDENTS))
161
- self._custom_fields = CustomFieldsController(self, res.pop(ResourceType.CUSTOM_FIELDS))
162
- self._documents = DocumentsController(self, res.pop(ResourceType.DOCUMENTS))
163
- self._document_types = DocumentTypesController(self, res.pop(ResourceType.DOCUMENT_TYPES))
164
- self._groups = GroupsController(self, res.pop(ResourceType.GROUPS))
165
- self._mail_accounts = MailAccountsController(self, res.pop(ResourceType.MAIL_ACCOUNTS))
166
- self._mail_rules = MailRulesController(self, res.pop(ResourceType.MAIL_RULES))
167
- self._saved_views = SavedViewsController(self, res.pop(ResourceType.SAVED_VIEWS))
168
- self._share_links = ShareLinksController(self, res.pop(ResourceType.SHARE_LINKS))
169
- self._storage_paths = StoragePathsController(self, res.pop(ResourceType.STORAGE_PATHS))
170
- self._tags = TagsController(self, res.pop(ResourceType.TAGS))
171
- self._tasks = TasksController(self, res.pop(ResourceType.TASKS))
172
- self._users = UsersController(self, res.pop(ResourceType.USERS))
217
+ self._groups = GroupsController(self, paths.pop(ControllerPath.GROUPS))
218
+ self._mail_accounts = MailAccountsController(self, paths.pop(ControllerPath.MAIL_ACCOUNTS))
219
+ self._mail_rules = MailRulesController(self, paths.pop(ControllerPath.MAIL_RULES))
220
+ self._saved_views = SavedViewsController(self, paths.pop(ControllerPath.SAVED_VIEWS))
221
+ self._tags = TagsController(self, paths.pop(ControllerPath.TAGS))
222
+ self._tasks = TasksController(self, paths.pop(ControllerPath.TASKS))
223
+ self._users = UsersController(self, paths.pop(ControllerPath.USERS))
224
+
225
+ try:
226
+ if PaperlessFeature.CONTROLLER_STORAGE_PATHS in self.features:
227
+ self._storage_paths = StoragePathsController(
228
+ self, paths.pop(ControllerPath.STORAGE_PATHS)
229
+ )
230
+ if PaperlessFeature.CONTROLLER_CONSUMPTION_TEMPLATES in self.features:
231
+ self._consumption_templates = ConsumptionTemplatesController(
232
+ self, paths.pop(ControllerPath.CONSUMPTION_TEMPLATES)
233
+ )
234
+ if PaperlessFeature.CONTROLLER_CUSTOM_FIELDS in self.features:
235
+ self._custom_fields = CustomFieldsController(
236
+ self, paths.pop(ControllerPath.CUSTOM_FIELDS)
237
+ )
238
+ if PaperlessFeature.CONTROLLER_SHARE_LINKS in self.features:
239
+ self._share_links = ShareLinksController(
240
+ self, paths.pop(ControllerPath.SHARE_LINKS)
241
+ )
242
+ if PaperlessFeature.CONTROLLER_WORKFLOWS in self.features:
243
+ self._workflows = WorkflowsController(self, paths.pop(ControllerPath.WORKFLOWS))
244
+ self._workflow_actions = WorkflowActionsController(
245
+ self, paths.pop(ControllerPath.WORKFLOW_ACTIONS)
246
+ )
247
+ self._workflow_triggers = WorkflowTriggersController(
248
+ self, paths.pop(ControllerPath.WORKFLOW_TRIGGERS)
249
+ )
250
+ except KeyError as exc:
251
+ raise ControllerConfusion(exc) from exc
173
252
 
174
253
  self._initialized = True
175
254
 
176
- if len(res) > 0:
177
- self.logger.debug("Unused endpoints: %s", ", ".join(res))
255
+ if len(paths) > 0:
256
+ self.logger.debug("Unused paths: %s", ", ".join(paths))
178
257
  self.logger.info("Initialized.")
179
258
 
180
259
  async def close(self) -> None:
@@ -194,8 +273,7 @@ class Paperless: # pylint: disable=too-many-instance-attributes
194
273
  if not isinstance(self._session, aiohttp.ClientSession):
195
274
  self._session = aiohttp.ClientSession()
196
275
 
197
- url = path if path.startswith("http") else f"http://{self.host}/api/{path}"
198
- url = url.rstrip("/") + "/" # check and add trailing slash
276
+ path = path.rstrip("/") + "/" # check and add trailing slash
199
277
 
200
278
  if isinstance(self._request_opts, dict):
201
279
  kwargs.update(self._request_opts)
@@ -208,7 +286,27 @@ class Paperless: # pylint: disable=too-many-instance-attributes
208
286
  }
209
287
  )
210
288
 
211
- async with self._session.request(method, url, **kwargs) as res:
289
+ # convert form to FormData, if dict
290
+ if "form" in kwargs:
291
+ payload = kwargs.pop("form")
292
+ if not isinstance(payload, dict):
293
+ raise TypeError()
294
+ form = aiohttp.FormData()
295
+
296
+ # we just convert data, no nesting dicts
297
+ for key, value in payload.items():
298
+ if isinstance(value, str | bytes):
299
+ form.add_field(key, value)
300
+ elif isinstance(value, list):
301
+ for list_value in value:
302
+ form.add_field(key, f"{list_value}")
303
+ else:
304
+ form.add_field(key, f"{value}")
305
+
306
+ kwargs["data"] = form
307
+
308
+ # request data
309
+ async with self._session.request(method, path, **kwargs) as res:
212
310
  yield res
213
311
 
214
312
  async def request_json(
@@ -0,0 +1,65 @@
1
+ """PyPaperless constants."""
2
+
3
+ from enum import IntFlag, StrEnum
4
+
5
+ from awesomeversion import AwesomeVersion
6
+
7
+ PAPERLESS_V1_8_0 = AwesomeVersion("1.8.0")
8
+ PAPERLESS_V1_17_0 = AwesomeVersion("1.17.0")
9
+ PAPERLESS_V2_0_0 = AwesomeVersion("2.0.0")
10
+ PAPERLESS_V2_3_0 = AwesomeVersion("2.3.0")
11
+
12
+
13
+ CTRL_CONSUMPTION_TEMPLATES = "consumption_templates"
14
+ CTRL_CORRESPONDENTS = "correspondents"
15
+ CTRL_CUSTOM_FIELDS = "custom_fields"
16
+ CTRL_DOCUMENTS = "documents"
17
+ CTRL_DOCUMENT_TYPES = "document_types"
18
+ CTRL_GROUPS = "groups"
19
+ CTRL_MAIL_ACCOUNTS = "mail_accounts"
20
+ CTRL_MAIL_RULES = "mail_rules"
21
+ CTRL_SAVED_VIEWS = "saved_views"
22
+ CTRL_SHARE_LINKS = "share_links"
23
+ CTRL_STORAGE_PATHS = "storage_paths"
24
+ CTRL_TAGS = "tags"
25
+ CTRL_TASKS = "tasks"
26
+ CTRL_USERS = "users"
27
+ CTRL_WORKFLOW_ACTIONS = "workflow_actions"
28
+ CTRL_WORKFLOWS = "workflows"
29
+ CTRL_WORKFLOW_TRIGGERS = "workflow_triggers"
30
+ CTRL_UNKNOWN = "unknown"
31
+
32
+
33
+ class PaperlessFeature(IntFlag):
34
+ """Supported features."""
35
+
36
+ CONTROLLER_STORAGE_PATHS = 1
37
+ FEATURE_DOCUMENT_NOTES = 2
38
+ CONTROLLER_SHARE_LINKS = 4
39
+ CONTROLLER_CUSTOM_FIELDS = 8
40
+ CONTROLLER_CONSUMPTION_TEMPLATES = 16
41
+ CONTROLLER_WORKFLOWS = 32
42
+ CONTROLLER_CONFIGS = 64
43
+
44
+
45
+ class ControllerPath(StrEnum):
46
+ """Represent paths of api endpoints."""
47
+
48
+ CONSUMPTION_TEMPLATES = CTRL_CONSUMPTION_TEMPLATES
49
+ CORRESPONDENTS = CTRL_CORRESPONDENTS
50
+ CUSTOM_FIELDS = CTRL_CUSTOM_FIELDS
51
+ DOCUMENTS = CTRL_DOCUMENTS
52
+ DOCUMENT_TYPES = CTRL_DOCUMENT_TYPES
53
+ GROUPS = CTRL_GROUPS
54
+ MAIL_ACCOUNTS = CTRL_MAIL_ACCOUNTS
55
+ MAIL_RULES = CTRL_MAIL_RULES
56
+ SAVED_VIEWS = CTRL_SAVED_VIEWS
57
+ SHARE_LINKS = CTRL_SHARE_LINKS
58
+ STORAGE_PATHS = CTRL_STORAGE_PATHS
59
+ TAGS = CTRL_TAGS
60
+ TASKS = CTRL_TASKS
61
+ USERS = CTRL_USERS
62
+ WORKFLOWS = CTRL_WORKFLOWS
63
+ WORKFLOW_ACTIONS = CTRL_WORKFLOW_ACTIONS
64
+ WORKFLOW_TRIGGERS = CTRL_WORKFLOW_TRIGGERS
65
+ UNKNOWN = CTRL_UNKNOWN
@@ -13,6 +13,9 @@ from .default import (
13
13
  StoragePathsController,
14
14
  TagsController,
15
15
  UsersController,
16
+ WorkflowActionsController,
17
+ WorkflowsController,
18
+ WorkflowTriggersController,
16
19
  )
17
20
  from .documents import DocumentsController
18
21
  from .tasks import TasksController
@@ -32,4 +35,7 @@ __all__ = [
32
35
  "TagsController",
33
36
  "TasksController",
34
37
  "UsersController",
38
+ "WorkflowActionsController",
39
+ "WorkflowsController",
40
+ "WorkflowTriggersController",
35
41
  ]
@@ -13,6 +13,9 @@ from pypaperless.models import (
13
13
  StoragePath,
14
14
  Tag,
15
15
  User,
16
+ Workflow,
17
+ WorkflowAction,
18
+ WorkflowTrigger,
16
19
  )
17
20
 
18
21
  from .base import (
@@ -174,3 +177,36 @@ class UsersController(
174
177
  """Represent Paperless users resource."""
175
178
 
176
179
  _resource = User
180
+
181
+
182
+ class WorkflowsController(
183
+ BaseController[Workflow],
184
+ PaginationMixin[Workflow],
185
+ ListMixin[Workflow],
186
+ OneMixin[Workflow],
187
+ ):
188
+ """Represent Paperless workflows resource."""
189
+
190
+ _resource = Workflow
191
+
192
+
193
+ class WorkflowActionsController(
194
+ BaseController[WorkflowAction],
195
+ PaginationMixin[WorkflowAction],
196
+ ListMixin[WorkflowAction],
197
+ OneMixin[WorkflowAction],
198
+ ):
199
+ """Represent Paperless workflow actions resource."""
200
+
201
+ _resource = WorkflowAction
202
+
203
+
204
+ class WorkflowTriggersController(
205
+ BaseController[WorkflowTrigger],
206
+ PaginationMixin[WorkflowTrigger],
207
+ ListMixin[WorkflowTrigger],
208
+ OneMixin[WorkflowTrigger],
209
+ ):
210
+ """Represent Paperless workflow triggers resource."""
211
+
212
+ _resource = WorkflowTrigger
@@ -2,8 +2,7 @@
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- import aiohttp
6
-
5
+ from pypaperless.const import PaperlessFeature
7
6
  from pypaperless.models import (
8
7
  Document,
9
8
  DocumentMetaInformation,
@@ -114,32 +113,17 @@ class DocumentsController( # pylint: disable=too-many-ancestors
114
113
  """Override initialize controller. Also initialize service controllers."""
115
114
  super().__init__(paperless, path)
116
115
 
117
- self.notes = DocumentNotesService(self)
118
116
  self.files = DocumentFilesService(self)
117
+ self.notes: DocumentNotesService | None = None
118
+
119
+ if PaperlessFeature.FEATURE_DOCUMENT_NOTES in self._paperless.features:
120
+ self.notes = DocumentNotesService(self)
119
121
 
120
122
  async def create(self, obj: DocumentPost) -> str:
121
123
  """Create a new document. Raise on failure."""
122
- form = aiohttp.FormData()
123
- form.add_field("document", obj.document)
124
-
125
- for field in (
126
- "title",
127
- "created",
128
- "correspondent",
129
- "document_type",
130
- "archive_serial_number",
131
- ):
132
- if getattr(obj, field):
133
- form.add_field(field, getattr(obj, field))
134
-
135
- if isinstance(obj.tags, list):
136
- for tag in obj.tags:
137
- form.add_field("tags", f"{tag}")
138
-
139
124
  url = f"{self.path}/post_document/"
140
-
141
125
  # result is a string in this case
142
- res = str(await self._paperless.request_json("post", url, data=form))
126
+ res: str = await self._paperless.request_json("post", url, form=dataclass_to_dict(obj))
143
127
  return res
144
128
 
145
129
  async def meta(self, obj: _DocumentOrIdType) -> DocumentMetaInformation:
@@ -29,11 +29,13 @@ class TasksController(BaseController[Task]):
29
29
  for item in res:
30
30
  yield item
31
31
 
32
- async def one(self, idx: str) -> Task:
32
+ async def one(self, idx: str) -> Task | None:
33
33
  """Request exactly one task item by pk."""
34
34
  params = {
35
35
  "task_id": idx,
36
36
  }
37
37
  res = await self._paperless.request_json("get", self.path, params=params)
38
- data: Task = dataclass_from_dict(self.resource, res.pop())
39
- return data
38
+ if len(res) > 0:
39
+ data: Task = dataclass_from_dict(self.resource, res.pop())
40
+ return data
41
+ return None
@@ -11,3 +11,7 @@ class BadRequestException(PaperlessException):
11
11
 
12
12
  class DataNotExpectedException(PaperlessException):
13
13
  """Raise when expecting a type and receiving something else."""
14
+
15
+
16
+ class ControllerConfusion(PaperlessException):
17
+ """Raise when Paperless version does not match to controllers."""
@@ -1,9 +1,6 @@
1
1
  """Models for Paperless resources."""
2
2
 
3
- from .consumption_templates import ConsumptionTemplate
4
- from .correspondents import Correspondent, CorrespondentPost
5
3
  from .custom_fields import CustomField, CustomFieldPost
6
- from .document_types import DocumentType, DocumentTypePost
7
4
  from .documents import (
8
5
  Document,
9
6
  DocumentMetadata,
@@ -13,14 +10,22 @@ from .documents import (
13
10
  DocumentPost,
14
11
  )
15
12
  from .groups import Group
16
- from .mail_accounts import MailAccount
17
- from .mail_rules import MailRule
13
+ from .mails import MailAccount, MailRule
14
+ from .matching import (
15
+ Correspondent,
16
+ CorrespondentPost,
17
+ DocumentType,
18
+ DocumentTypePost,
19
+ StoragePath,
20
+ StoragePathPost,
21
+ Tag,
22
+ TagPost,
23
+ )
18
24
  from .saved_views import SavedView, SavedViewFilterRule
19
25
  from .share_links import ShareLink, ShareLinkPost
20
- from .storage_paths import StoragePath, StoragePathPost
21
- from .tags import Tag, TagPost
22
26
  from .tasks import Task
23
27
  from .users import User
28
+ from .workflows import ConsumptionTemplate, Workflow, WorkflowAction, WorkflowTrigger
24
29
 
25
30
  __all__ = [
26
31
  "ConsumptionTemplate",
@@ -49,4 +54,7 @@ __all__ = [
49
54
  "TagPost",
50
55
  "Task",
51
56
  "User",
57
+ "Workflow",
58
+ "WorkflowAction",
59
+ "WorkflowTrigger",
52
60
  ]
@@ -0,0 +1,9 @@
1
+ """Base models."""
2
+
3
+
4
+ class PaperlessModel: # pylint: disable=too-few-public-methods
5
+ """Base class that represents a Paperless model."""
6
+
7
+
8
+ class PaperlessPost(PaperlessModel): # pylint: disable=too-few-public-methods
9
+ """Base class that represents a Paperless POST model."""
@@ -1,10 +1,29 @@
1
1
  """Model for custom field resource."""
2
2
 
3
3
  from dataclasses import dataclass
4
+ from enum import Enum
4
5
  from typing import Any
5
6
 
6
7
  from .base import PaperlessModel, PaperlessPost
7
- from .shared import CustomFieldType
8
+
9
+
10
+ class CustomFieldType(Enum):
11
+ """Enum with custom field types."""
12
+
13
+ STRING = "string"
14
+ BOOLEAN = "boolean"
15
+ INTEGER = "integer"
16
+ FLOAT = "float"
17
+ MONETARY = "monetary"
18
+ DATE = "date"
19
+ URL = "url"
20
+ DOCUMENT_LINK = "documentlink"
21
+ UNKNOWN = "unknown"
22
+
23
+ @classmethod
24
+ def _missing_(cls: type, value: object) -> "CustomFieldType": # noqa ARG003
25
+ """Set default member on unknown value."""
26
+ return CustomFieldType.UNKNOWN
8
27
 
9
28
 
10
29
  @dataclass(kw_only=True)
@@ -80,7 +80,7 @@ class Document(PaperlessModel): # pylint: disable=too-many-instance-attributes
80
80
 
81
81
 
82
82
  @dataclass(kw_only=True)
83
- class DocumentPost(PaperlessPost):
83
+ class DocumentPost(PaperlessPost): # pylint: disable=too-many-instance-attributes
84
84
  """Attributes to send when creating a document on the Paperless api."""
85
85
 
86
86
  document: bytes
@@ -88,5 +88,6 @@ class DocumentPost(PaperlessPost):
88
88
  created: datetime | None = None
89
89
  correspondent: int | None = None
90
90
  document_type: int | None = None
91
+ storage_path: int | None = None
91
92
  tags: list[int] | None = None
92
93
  archive_serial_number: int | None = None
@@ -1,10 +1,27 @@
1
- """Model for mail rule resource."""
1
+ """Model for mail resources."""
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
5
  from .base import PaperlessModel
6
6
 
7
7
 
8
+ @dataclass(kw_only=True)
9
+ class MailAccount(PaperlessModel): # pylint: disable=too-many-instance-attributes
10
+ """Represent a mail account resource on the Paperless api."""
11
+
12
+ id: int | None = None
13
+ name: str | None = None
14
+ imap_server: str | None = None
15
+ imap_port: int | None = None
16
+ imap_security: int | None = None
17
+ username: str | None = None
18
+ password: str | None = None
19
+ character_set: str | None = None
20
+ is_token: bool | None = None
21
+ owner: int | None = None
22
+ user_can_change: bool | None = None
23
+
24
+
8
25
  @dataclass(kw_only=True)
9
26
  class MailRule(PaperlessModel): # pylint: disable=too-many-instance-attributes
10
27
  """Represent a mail rule resource on the Paperless api."""