kuvert 1.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.
@@ -0,0 +1,153 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ **/__pycache__
4
+ *.py[cod]
5
+ *$py.class
6
+ *.pyc
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ pip-wheel-metadata/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
101
+ __pypackages__/
102
+
103
+ # Celery stuff
104
+ celerybeat-schedule
105
+ celerybeat.pid
106
+
107
+ # SageMath parsed files
108
+ *.sage.py
109
+
110
+ # Environments
111
+ .env
112
+ .venv
113
+ env/
114
+ venv/
115
+ ENV/
116
+ env.bak/
117
+ venv.bak/
118
+
119
+ # Spyder project settings
120
+ .spyderproject
121
+ .spyproject
122
+
123
+ # Rope project settings
124
+ .ropeproject
125
+
126
+ # mkdocs documentation
127
+ /site
128
+
129
+ # mypy
130
+ .mypy_cache/
131
+ .dmypy.json
132
+ dmypy.json
133
+
134
+ # Pyre type checker
135
+ .pyre/
136
+
137
+ # pytype static type analyzer
138
+ .pytype/
139
+
140
+ # Cython debug symbols
141
+ cython_debug/
142
+
143
+ # static files generated from Django application using `collectstatic`
144
+ media
145
+ export
146
+ static_collected
147
+ data
148
+
149
+ *.token.temp
150
+ *.fakts.yaml
151
+ fakts.yaml
152
+ # Local-source override for the integration stack (see tests/conftest.py)
153
+ tests/integration/docker-compose.local.yml
kuvert-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.5
2
+ Name: kuvert
3
+ Version: 1.0.0
4
+ Summary: The python client for kuvert, the arkitekt mail service
5
+ Author-email: jhnnsrs <jhnnsrs@gmail.com>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiohttp<4,>=3.9.5
8
+ Requires-Dist: koil>=1.0.0
9
+ Requires-Dist: rath>=4.1.1
10
+ Requires-Dist: websockets>=15.0.1
11
+ Description-Content-Type: text/markdown
12
+
13
+ # kuvert
14
+
15
+ The python client for [kuvert](https://github.com/arkitektio/kuvert-server), the
16
+ [Arkitekt](https://arkitekt.live) mail service: the mailboxes you linked, their
17
+ folders and conversations, tasks over those conversations, and sending mail.
18
+
19
+ ## Installation
20
+
21
+ ```sh
22
+ pip install kuvert
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Every kuvert operation is a method of the `Kuvert` client, in a blocking and an
28
+ `a`-prefixed async flavour.
29
+
30
+ ### In an arkitekt app
31
+
32
+ The client is injected by annotation. Add the service to your app and ask for
33
+ `kuvert: Kuvert`:
34
+
35
+ ```python
36
+ from arkitekt import App, run
37
+ from kuvert import Kuvert, kuvert_service
38
+
39
+ app = App("mail-digest", "0.1.0", services=[kuvert_service])
40
+
41
+
42
+ @app.action
43
+ async def unread_subjects(kuvert: Kuvert) -> list[str]:
44
+ """The subjects of the unread conversations in my inboxes."""
45
+ threads = await kuvert.alist_threads(filter={"unread": True, "folderRole": "INBOX"})
46
+ return [thread.subject for thread in threads]
47
+
48
+
49
+ if __name__ == "__main__":
50
+ run(app)
51
+ ```
52
+
53
+ Messages, threads, outgoing mail, mailboxes, tasks and task lists travel between
54
+ actions by id (`@kuvert/message`, `@kuvert/thread`, `@kuvert/outgoingmessage`,
55
+ `@kuvert/mailaccount`, `@kuvert/task`, `@kuvert/tasklist`), so an action can take
56
+ and return them directly, and a trigger on a kuvert signal hands them over.
57
+
58
+ ### From a script
59
+
60
+ ```python
61
+ from arkitekt import easy
62
+ from kuvert import kuvert_service
63
+
64
+ with easy("my-script", kuvert_service) as kuvert:
65
+ for account in kuvert.list_mail_accounts():
66
+ print(account.email_address, account.unread_count)
67
+ ```
68
+
69
+ ### Standalone
70
+
71
+ Without arkitekt, build the client over a rath link of your own:
72
+
73
+ ```python
74
+ from kuvert import Kuvert
75
+ from kuvert.rath import KuvertRath
76
+
77
+ kuvert = Kuvert(rath=KuvertRath(link=...))
78
+
79
+ with kuvert:
80
+ for account in kuvert.list_mail_accounts():
81
+ print(account.email_address, account.unread_count)
82
+
83
+ kuvert.send_message(
84
+ account=account.id,
85
+ to=[{"address": "someone@example.org"}],
86
+ subject="Hello",
87
+ text="From kuvert.",
88
+ )
89
+ ```
90
+
91
+ Arguments the server defaults (`cc`, `bcc`, `attachments` of `send_message`,
92
+ `notes`/`pinned` of `create_task`, …) are optional: **leave them out** rather
93
+ than passing `None`. An explicit `None` is sent as `null`, which the server
94
+ rejects for those fields.
95
+
96
+ Changes to mail (flags, moves, deletes, categories) are local-first: they apply
97
+ at once and reach the mail server when the mailbox next pushes.
98
+ `list_mail_changes` shows what is still pending and `undo_mail_changes` takes a
99
+ change back while it is.
100
+
101
+ ## Development
102
+
103
+ The generated API (`kuvert/api/schema.py`) comes from the checked-in
104
+ `schema.graphql` and the documents in `graphql/`. The config comment in
105
+ `graphql.config.yaml` has the command that refreshes the schema from a
106
+ kuvert-server checkout. Then run turms to regenerate the API.
107
+
108
+ ```sh
109
+ uv run pytest -m "not integration" # no server needed
110
+ uv run pytest -m integration # a real kuvert + mail server via dokker
111
+ ```
112
+
113
+ See [RELEASING.md](RELEASING.md) for how versions are cut.
kuvert-1.0.0/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # kuvert
2
+
3
+ The python client for [kuvert](https://github.com/arkitektio/kuvert-server), the
4
+ [Arkitekt](https://arkitekt.live) mail service: the mailboxes you linked, their
5
+ folders and conversations, tasks over those conversations, and sending mail.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pip install kuvert
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Every kuvert operation is a method of the `Kuvert` client, in a blocking and an
16
+ `a`-prefixed async flavour.
17
+
18
+ ### In an arkitekt app
19
+
20
+ The client is injected by annotation. Add the service to your app and ask for
21
+ `kuvert: Kuvert`:
22
+
23
+ ```python
24
+ from arkitekt import App, run
25
+ from kuvert import Kuvert, kuvert_service
26
+
27
+ app = App("mail-digest", "0.1.0", services=[kuvert_service])
28
+
29
+
30
+ @app.action
31
+ async def unread_subjects(kuvert: Kuvert) -> list[str]:
32
+ """The subjects of the unread conversations in my inboxes."""
33
+ threads = await kuvert.alist_threads(filter={"unread": True, "folderRole": "INBOX"})
34
+ return [thread.subject for thread in threads]
35
+
36
+
37
+ if __name__ == "__main__":
38
+ run(app)
39
+ ```
40
+
41
+ Messages, threads, outgoing mail, mailboxes, tasks and task lists travel between
42
+ actions by id (`@kuvert/message`, `@kuvert/thread`, `@kuvert/outgoingmessage`,
43
+ `@kuvert/mailaccount`, `@kuvert/task`, `@kuvert/tasklist`), so an action can take
44
+ and return them directly, and a trigger on a kuvert signal hands them over.
45
+
46
+ ### From a script
47
+
48
+ ```python
49
+ from arkitekt import easy
50
+ from kuvert import kuvert_service
51
+
52
+ with easy("my-script", kuvert_service) as kuvert:
53
+ for account in kuvert.list_mail_accounts():
54
+ print(account.email_address, account.unread_count)
55
+ ```
56
+
57
+ ### Standalone
58
+
59
+ Without arkitekt, build the client over a rath link of your own:
60
+
61
+ ```python
62
+ from kuvert import Kuvert
63
+ from kuvert.rath import KuvertRath
64
+
65
+ kuvert = Kuvert(rath=KuvertRath(link=...))
66
+
67
+ with kuvert:
68
+ for account in kuvert.list_mail_accounts():
69
+ print(account.email_address, account.unread_count)
70
+
71
+ kuvert.send_message(
72
+ account=account.id,
73
+ to=[{"address": "someone@example.org"}],
74
+ subject="Hello",
75
+ text="From kuvert.",
76
+ )
77
+ ```
78
+
79
+ Arguments the server defaults (`cc`, `bcc`, `attachments` of `send_message`,
80
+ `notes`/`pinned` of `create_task`, …) are optional: **leave them out** rather
81
+ than passing `None`. An explicit `None` is sent as `null`, which the server
82
+ rejects for those fields.
83
+
84
+ Changes to mail (flags, moves, deletes, categories) are local-first: they apply
85
+ at once and reach the mail server when the mailbox next pushes.
86
+ `list_mail_changes` shows what is still pending and `undo_mail_changes` takes a
87
+ change back while it is.
88
+
89
+ ## Development
90
+
91
+ The generated API (`kuvert/api/schema.py`) comes from the checked-in
92
+ `schema.graphql` and the documents in `graphql/`. The config comment in
93
+ `graphql.config.yaml` has the command that refreshes the schema from a
94
+ kuvert-server checkout. Then run turms to regenerate the API.
95
+
96
+ ```sh
97
+ uv run pytest -m "not integration" # no server needed
98
+ uv run pytest -m integration # a real kuvert + mail server via dokker
99
+ ```
100
+
101
+ See [RELEASING.md](RELEASING.md) for how versions are cut.
@@ -0,0 +1,20 @@
1
+ from .kuvert import Kuvert
2
+
3
+ try:
4
+ from .arkitekt import kuvert as kuvert_service
5
+ except ImportError as e:
6
+ # Only "rekuest is not installed" may pass silently. Anything else that fails
7
+ # to import here (a renamed query, a rekuest too old for what the module
8
+ # needs) is a bug, and hiding it makes this package's service vanish
9
+ # without a word. Whether it is installed is asked the plain way.
10
+ try:
11
+ import rekuest # noqa: F401 -- presence is the question
12
+ except ImportError:
13
+ pass
14
+ else:
15
+ raise e
16
+
17
+ __all__ = [
18
+ "Kuvert",
19
+ "kuvert_service",
20
+ ]
@@ -0,0 +1,120 @@
1
+ {
2
+ "schema": "schema.graphql",
3
+ "documents": "graphql/**/**.graphql",
4
+ "extensions": {
5
+ "turms": {
6
+ "domain": null,
7
+ "out_dir": "kuvert/api",
8
+ "dump_configuration": true,
9
+ "configuration_name": "project.json",
10
+ "dump_schema": true,
11
+ "schema_name": "schema.graphql",
12
+ "generated_name": "schema.py",
13
+ "documents": "graphql/**/**.graphql",
14
+ "verbose": false,
15
+ "exit_on_error": true,
16
+ "allow_introspection": true,
17
+ "object_bases": [
18
+ "pydantic.BaseModel"
19
+ ],
20
+ "interface_bases": null,
21
+ "always_resolve_interfaces": true,
22
+ "exclude_typenames": true,
23
+ "scalar_definitions": {
24
+ "uuid": "str",
25
+ "Callback": "str",
26
+ "Any": "typing.Any",
27
+ "JSON": "typing.Any",
28
+ "ID": "rath.scalars.ID",
29
+ "_Any": "typing.Any",
30
+ "ByteCount": "int"
31
+ },
32
+ "coercible_scalars": {},
33
+ "coercible_inputs": {},
34
+ "graphql_default_class": null,
35
+ "deprecated_class": null,
36
+ "document_field_metadata": true,
37
+ "unset_type_class": null,
38
+ "unset_instance": null,
39
+ "freeze": {
40
+ "enabled": true,
41
+ "types": [
42
+ "input",
43
+ "fragment",
44
+ "object"
45
+ ],
46
+ "exclude": null,
47
+ "include": null,
48
+ "convert_list_to_tuple": true
49
+ },
50
+ "create_catchall": true,
51
+ "options": {
52
+ "enabled": true,
53
+ "extra": "forbid",
54
+ "populate_by_name": true,
55
+ "from_attributes": null,
56
+ "use_enum_values": true,
57
+ "validate_assignment": null,
58
+ "alias_mode": "split",
59
+ "types": [
60
+ "input"
61
+ ],
62
+ "exclude": null,
63
+ "include": null
64
+ },
65
+ "skip_forwards": false,
66
+ "min_python_version": "3.10",
67
+ "type_annotation_style": "auto",
68
+ "external_modules": [],
69
+ "additional_bases": {},
70
+ "additional_config": {},
71
+ "force_plugin_order": true,
72
+ "omitted_document_rules": [],
73
+ "parsers": [],
74
+ "plugins": [
75
+ {
76
+ "type": "turms.plugins.enums.EnumsPlugin"
77
+ },
78
+ {
79
+ "type": "turms.plugins.inputs.InputsPlugin"
80
+ },
81
+ {
82
+ "type": "turms.plugins.fragments.FragmentsPlugin"
83
+ },
84
+ {
85
+ "type": "turms.plugins.operations.OperationsPlugin"
86
+ },
87
+ {
88
+ "type": "turms.plugins.client.ClientPlugin",
89
+ "client_class": "KuvertApi",
90
+ "global_kwargs": [
91
+ {
92
+ "key": "task",
93
+ "type": "rath.task.TaskLike",
94
+ "description": "The task this call is made for; the ambient one by default."
95
+ }
96
+ ],
97
+ "reserved_from": [
98
+ "kuvert.kuvert.Kuvert"
99
+ ],
100
+ "coercible_scalars": {
101
+ "ID": "rath.scalars.IDCoercible"
102
+ },
103
+ "expand_input_types": [
104
+ "input"
105
+ ]
106
+ }
107
+ ],
108
+ "processors": [],
109
+ "stylers": [
110
+ {
111
+ "type": "turms.stylers.default.DefaultStyler"
112
+ },
113
+ {
114
+ "type": "turms.stylers.appender.AppenderStyler",
115
+ "append_fragment": ""
116
+ }
117
+ ]
118
+ }
119
+ }
120
+ }