smtpfast 0.1.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.
- smtpfast-0.1.0/.github/workflows/ci.yml +40 -0
- smtpfast-0.1.0/.github/workflows/regenerate.yml +50 -0
- smtpfast-0.1.0/.github/workflows/release.yml +28 -0
- smtpfast-0.1.0/.gitignore +8 -0
- smtpfast-0.1.0/LICENSE +21 -0
- smtpfast-0.1.0/PKG-INFO +131 -0
- smtpfast-0.1.0/README.md +108 -0
- smtpfast-0.1.0/pyproject.toml +43 -0
- smtpfast-0.1.0/ruff.toml +5 -0
- smtpfast-0.1.0/scripts/generate.py +350 -0
- smtpfast-0.1.0/scripts/pull_spec.py +14 -0
- smtpfast-0.1.0/spec/openapi.json +22274 -0
- smtpfast-0.1.0/src/smtpfast/__init__.py +40 -0
- smtpfast-0.1.0/src/smtpfast/_client.py +63 -0
- smtpfast-0.1.0/src/smtpfast/_core.py +246 -0
- smtpfast-0.1.0/src/smtpfast/_errors.py +94 -0
- smtpfast-0.1.0/src/smtpfast/_resources.py +1654 -0
- smtpfast-0.1.0/src/smtpfast/_version.py +1 -0
- smtpfast-0.1.0/src/smtpfast/py.typed +0 -0
- smtpfast-0.1.0/src/smtpfast/types.py +3109 -0
- smtpfast-0.1.0/src/smtpfast/webhooks.py +42 -0
- smtpfast-0.1.0/tests/conftest.py +50 -0
- smtpfast-0.1.0/tests/test_client.py +218 -0
- smtpfast-0.1.0/tests/test_coverage.py +40 -0
- smtpfast-0.1.0/tests/test_live.py +70 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- run: pip install -e '.[dev]'
|
|
20
|
+
# The generated code must be exactly what the spec in the repo produces.
|
|
21
|
+
- name: Generated code is up to date
|
|
22
|
+
run: python scripts/generate.py && git diff --exit-code
|
|
23
|
+
- run: ruff check src tests scripts
|
|
24
|
+
- run: mypy
|
|
25
|
+
- run: pytest -q
|
|
26
|
+
|
|
27
|
+
# Read-only calls to the real API, checked against the spec. Needs a key
|
|
28
|
+
# with email:read and domain:read in the SMTPFAST_API_KEY secret.
|
|
29
|
+
live:
|
|
30
|
+
if: github.event_name == 'push'
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v7
|
|
34
|
+
- uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.13"
|
|
37
|
+
- run: pip install -e '.[dev]'
|
|
38
|
+
- run: pytest -q tests/test_live.py
|
|
39
|
+
env:
|
|
40
|
+
SMTPFAST_API_KEY: ${{ secrets.SMTPFAST_API_KEY }}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Regenerate from the API spec
|
|
2
|
+
|
|
3
|
+
# Pulls the published OpenAPI document, regenerates the SDK and opens a pull
|
|
4
|
+
# request when anything changed. Runs daily, on demand, and when the API repo
|
|
5
|
+
# sends a spec-changed dispatch.
|
|
6
|
+
on:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "23 5 * * *"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
repository_dispatch:
|
|
11
|
+
types: [spec-changed]
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pull-requests: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
regenerate:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-python@v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
- run: pip install -e '.[dev]'
|
|
26
|
+
- run: python scripts/pull_spec.py
|
|
27
|
+
# Until the API publishes x-sdk-group/x-sdk-method, there is nothing to regenerate from.
|
|
28
|
+
- name: Spec has SDK names
|
|
29
|
+
id: names
|
|
30
|
+
run: |
|
|
31
|
+
if grep -q '"x-sdk-method"' spec/openapi.json; then echo "ok=true" >> "$GITHUB_OUTPUT"; else echo "::notice::The published spec has no x-sdk names yet; skipping."; echo "ok=false" >> "$GITHUB_OUTPUT"; fi
|
|
32
|
+
- run: python scripts/generate.py
|
|
33
|
+
if: steps.names.outputs.ok == 'true'
|
|
34
|
+
- run: mypy
|
|
35
|
+
if: steps.names.outputs.ok == 'true'
|
|
36
|
+
- run: pytest -q
|
|
37
|
+
if: steps.names.outputs.ok == 'true'
|
|
38
|
+
# A pull request opened with the default GITHUB_TOKEN does not start the
|
|
39
|
+
# CI workflow. Set SDK_BOT_TOKEN (a GitHub App or fine-grained token with
|
|
40
|
+
# contents and pull-requests write) so it does.
|
|
41
|
+
- uses: peter-evans/create-pull-request@v7
|
|
42
|
+
if: steps.names.outputs.ok == 'true'
|
|
43
|
+
with:
|
|
44
|
+
token: ${{ secrets.SDK_BOT_TOKEN || github.token }}
|
|
45
|
+
branch: spec-sync
|
|
46
|
+
title: "Sync with the API spec"
|
|
47
|
+
commit-message: "Sync with the API spec"
|
|
48
|
+
body: |
|
|
49
|
+
The published OpenAPI document changed. This regenerates `src/smtpfast/types.py` and `src/smtpfast/_resources.py` from it.
|
|
50
|
+
Review the diff of `spec/openapi.json` for what changed in the API.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI when a GitHub release is published, with PyPI trusted
|
|
4
|
+
# publishing (no token). The tag is the version, for example v0.2.0; it must
|
|
5
|
+
# match pyproject.toml.
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment: pypi
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.13"
|
|
23
|
+
- name: Tag matches pyproject.toml
|
|
24
|
+
run: test "v$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')" = "${GITHUB_REF_NAME}"
|
|
25
|
+
- run: pip install -e '.[dev]' build
|
|
26
|
+
- run: pytest -q
|
|
27
|
+
- run: python -m build
|
|
28
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
smtpfast-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SMTPfast
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
smtpfast-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: smtpfast
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python SDK for the SMTPfast API.
|
|
5
|
+
Project-URL: Homepage, https://smtpfa.st
|
|
6
|
+
Project-URL: Documentation, https://smtpfa.st/docs
|
|
7
|
+
Project-URL: Source, https://github.com/smtpfast/smtpfast-python
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,email,sdk,smtpfast,transactional-email
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Communications :: Email
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: httpx<1,>=0.27
|
|
16
|
+
Requires-Dist: typing-extensions>=4.7
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: jsonschema>=4.22; extra == 'dev'
|
|
19
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# SMTPfast Python SDK
|
|
25
|
+
|
|
26
|
+
The official Python SDK for the [SMTPfast](https://smtpfa.st) API. It covers every endpoint in the API, sync and async, with types for every request and response. It needs Python 3.10+ and depends only on `httpx`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install smtpfast
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Send an email
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from smtpfast import SMTPfast
|
|
36
|
+
|
|
37
|
+
client = SMTPfast() # reads SMTPFAST_API_KEY
|
|
38
|
+
|
|
39
|
+
email = client.emails.send({
|
|
40
|
+
"from": "Acme <hello@yourdomain.com>",
|
|
41
|
+
"to": ["customer@example.org"],
|
|
42
|
+
"subject": "Your receipt",
|
|
43
|
+
"html": "<p>Thanks for your order.</p>",
|
|
44
|
+
"category": "transactional",
|
|
45
|
+
})
|
|
46
|
+
print(email["id"])
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Email is marketing unless you set `category` to `transactional`. Marketing email gets a one-click unsubscribe header and is not sent to people who unsubscribed. Set `transactional` on receipts, password resets and account alerts so a marketing opt-out does not block them.
|
|
50
|
+
|
|
51
|
+
Requests and responses are plain dicts, typed with `TypedDict`s from `smtpfast.types`, so your editor and type checker know every field. A new field or value from the API never breaks your code, because the SDK does not validate responses at runtime.
|
|
52
|
+
|
|
53
|
+
Every API operation is a method, named after its path: `client.domains.verify(id)`, `client.contacts.segments.add(contact_id, segment_id)`, `client.inboxes.threads.emails.reply(inbox_id, thread_id, email_id, params)`. Path parameters come first, then the request body, and query parameters are keyword arguments: `client.emails.list(limit=20, status="bounced")`.
|
|
54
|
+
|
|
55
|
+
## Async
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from smtpfast import AsyncSMTPfast
|
|
59
|
+
|
|
60
|
+
async with AsyncSMTPfast() as client:
|
|
61
|
+
await client.emails.send({...})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
client = SMTPfast(
|
|
68
|
+
api_key="sf_...", # default: SMTPFAST_API_KEY
|
|
69
|
+
base_url="https://smtpfa.st/api",
|
|
70
|
+
max_retries=2, # after 429 (with Retry-After), 408, 5xx and dropped connections
|
|
71
|
+
timeout=60.0, # per attempt, in seconds
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
client.emails.send(params, idempotency_key="order-1234", options={"timeout": 10})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Retries and idempotency
|
|
78
|
+
|
|
79
|
+
Reads, `PUT` and `DELETE` are retried after a 5xx answer or a dropped connection. A send, reply or forward is retried with the same `Idempotency-Key`, so the API returns the first answer instead of sending twice. When you do not give a key, the SDK makes one per call. Other `POST` and `PATCH` calls are retried only after a per-second rate limit, which the API rejects before doing any work.
|
|
80
|
+
|
|
81
|
+
## Errors
|
|
82
|
+
|
|
83
|
+
Every error is an `SMTPfastError`. An answer outside 2xx raises an `APIError` with `status`, `body` and `headers`, as one of `BadRequestError`, `AuthenticationError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `RateLimitError` or `InternalServerError`. No answer at all raises `APIConnectionError` or `APITimeoutError`.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import smtpfast
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
client.emails.get("em_missing")
|
|
90
|
+
except smtpfast.NotFoundError as err:
|
|
91
|
+
print(err.status, err.body)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Pagination
|
|
95
|
+
|
|
96
|
+
Lists that page with a cursor also have a `..._all` method that fetches the next pages as you iterate:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
for email in client.emails.list_all(limit=100):
|
|
100
|
+
print(email["id"], email["status"])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Webhooks
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from smtpfast import webhooks
|
|
107
|
+
|
|
108
|
+
# The raw body, before any JSON parsing.
|
|
109
|
+
event = webhooks.parse(request.body, request.headers, secret=os.environ["SMTPFAST_WEBHOOK_SECRET"])
|
|
110
|
+
if event["type"] == "email.bounced":
|
|
111
|
+
...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`webhooks.verify_signature(payload, signature, secret)` returns a bool if you only need the check.
|
|
115
|
+
|
|
116
|
+
## How this SDK is made
|
|
117
|
+
|
|
118
|
+
The SDK is generated from the [OpenAPI document](https://smtpfa.st/api/v1/openapi.json), so it follows the API without hand edits. `scripts/generate.py` (standard library only) writes `src/smtpfast/types.py` and `src/smtpfast/_resources.py`, naming every method from the `x-sdk-group` and `x-sdk-method` the spec gives each operation; the SDKs for other languages use the same names. `_core.py`, `_errors.py` and `webhooks.py` are written by hand.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
python scripts/pull_spec.py # download the published spec into spec/openapi.json
|
|
122
|
+
python scripts/generate.py # regenerate types.py and _resources.py
|
|
123
|
+
pytest -q # unit tests, including one call per operation
|
|
124
|
+
SMTPFAST_API_KEY=sf_... pytest tests/test_live.py # read-only calls, checked against the spec
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A daily workflow pulls the spec and opens a pull request when it changed.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
smtpfast-0.1.0/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# SMTPfast Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [SMTPfast](https://smtpfa.st) API. It covers every endpoint in the API, sync and async, with types for every request and response. It needs Python 3.10+ and depends only on `httpx`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install smtpfast
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Send an email
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from smtpfast import SMTPfast
|
|
13
|
+
|
|
14
|
+
client = SMTPfast() # reads SMTPFAST_API_KEY
|
|
15
|
+
|
|
16
|
+
email = client.emails.send({
|
|
17
|
+
"from": "Acme <hello@yourdomain.com>",
|
|
18
|
+
"to": ["customer@example.org"],
|
|
19
|
+
"subject": "Your receipt",
|
|
20
|
+
"html": "<p>Thanks for your order.</p>",
|
|
21
|
+
"category": "transactional",
|
|
22
|
+
})
|
|
23
|
+
print(email["id"])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Email is marketing unless you set `category` to `transactional`. Marketing email gets a one-click unsubscribe header and is not sent to people who unsubscribed. Set `transactional` on receipts, password resets and account alerts so a marketing opt-out does not block them.
|
|
27
|
+
|
|
28
|
+
Requests and responses are plain dicts, typed with `TypedDict`s from `smtpfast.types`, so your editor and type checker know every field. A new field or value from the API never breaks your code, because the SDK does not validate responses at runtime.
|
|
29
|
+
|
|
30
|
+
Every API operation is a method, named after its path: `client.domains.verify(id)`, `client.contacts.segments.add(contact_id, segment_id)`, `client.inboxes.threads.emails.reply(inbox_id, thread_id, email_id, params)`. Path parameters come first, then the request body, and query parameters are keyword arguments: `client.emails.list(limit=20, status="bounced")`.
|
|
31
|
+
|
|
32
|
+
## Async
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from smtpfast import AsyncSMTPfast
|
|
36
|
+
|
|
37
|
+
async with AsyncSMTPfast() as client:
|
|
38
|
+
await client.emails.send({...})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
client = SMTPfast(
|
|
45
|
+
api_key="sf_...", # default: SMTPFAST_API_KEY
|
|
46
|
+
base_url="https://smtpfa.st/api",
|
|
47
|
+
max_retries=2, # after 429 (with Retry-After), 408, 5xx and dropped connections
|
|
48
|
+
timeout=60.0, # per attempt, in seconds
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
client.emails.send(params, idempotency_key="order-1234", options={"timeout": 10})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Retries and idempotency
|
|
55
|
+
|
|
56
|
+
Reads, `PUT` and `DELETE` are retried after a 5xx answer or a dropped connection. A send, reply or forward is retried with the same `Idempotency-Key`, so the API returns the first answer instead of sending twice. When you do not give a key, the SDK makes one per call. Other `POST` and `PATCH` calls are retried only after a per-second rate limit, which the API rejects before doing any work.
|
|
57
|
+
|
|
58
|
+
## Errors
|
|
59
|
+
|
|
60
|
+
Every error is an `SMTPfastError`. An answer outside 2xx raises an `APIError` with `status`, `body` and `headers`, as one of `BadRequestError`, `AuthenticationError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `RateLimitError` or `InternalServerError`. No answer at all raises `APIConnectionError` or `APITimeoutError`.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import smtpfast
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
client.emails.get("em_missing")
|
|
67
|
+
except smtpfast.NotFoundError as err:
|
|
68
|
+
print(err.status, err.body)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Pagination
|
|
72
|
+
|
|
73
|
+
Lists that page with a cursor also have a `..._all` method that fetches the next pages as you iterate:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
for email in client.emails.list_all(limit=100):
|
|
77
|
+
print(email["id"], email["status"])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Webhooks
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from smtpfast import webhooks
|
|
84
|
+
|
|
85
|
+
# The raw body, before any JSON parsing.
|
|
86
|
+
event = webhooks.parse(request.body, request.headers, secret=os.environ["SMTPFAST_WEBHOOK_SECRET"])
|
|
87
|
+
if event["type"] == "email.bounced":
|
|
88
|
+
...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`webhooks.verify_signature(payload, signature, secret)` returns a bool if you only need the check.
|
|
92
|
+
|
|
93
|
+
## How this SDK is made
|
|
94
|
+
|
|
95
|
+
The SDK is generated from the [OpenAPI document](https://smtpfa.st/api/v1/openapi.json), so it follows the API without hand edits. `scripts/generate.py` (standard library only) writes `src/smtpfast/types.py` and `src/smtpfast/_resources.py`, naming every method from the `x-sdk-group` and `x-sdk-method` the spec gives each operation; the SDKs for other languages use the same names. `_core.py`, `_errors.py` and `webhooks.py` are written by hand.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python scripts/pull_spec.py # download the published spec into spec/openapi.json
|
|
99
|
+
python scripts/generate.py # regenerate types.py and _resources.py
|
|
100
|
+
pytest -q # unit tests, including one call per operation
|
|
101
|
+
SMTPFAST_API_KEY=sf_... pytest tests/test_live.py # read-only calls, checked against the spec
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A daily workflow pulls the spec and opens a pull request when it changed.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smtpfast"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The official Python SDK for the SMTPfast API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["httpx>=0.27,<1", "typing_extensions>=4.7"]
|
|
14
|
+
keywords = ["smtpfast", "email", "transactional-email", "api", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
"Topic :: Communications :: Email",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://smtpfa.st"
|
|
23
|
+
Documentation = "https://smtpfa.st/docs"
|
|
24
|
+
Source = "https://github.com/smtpfast/smtpfast-python"
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = ["pytest>=8", "mypy>=1.10", "jsonschema>=4.22", "ruff>=0.5"]
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/smtpfast"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
|
|
35
|
+
[tool.mypy]
|
|
36
|
+
strict = true
|
|
37
|
+
files = ["src/smtpfast"]
|
|
38
|
+
|
|
39
|
+
[[tool.mypy.overrides]]
|
|
40
|
+
module = ["smtpfast._resources", "smtpfast._core"]
|
|
41
|
+
disallow_untyped_defs = false
|
|
42
|
+
disallow_untyped_calls = false
|
|
43
|
+
disallow_incomplete_defs = false
|