parcelwing 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.
@@ -0,0 +1 @@
1
+ PARCEL_WING_API_KEY=pw_live_xxxxxxxxxxxxxxxxxxxx
@@ -0,0 +1,28 @@
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-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install package
20
+ run: |
21
+ python -m pip install --upgrade pip
22
+ python -m pip install -e ".[dev]"
23
+ - name: Lint
24
+ run: ruff check .
25
+ - name: Type check
26
+ run: mypy src/parcelwing
27
+ - name: Test
28
+ run: pytest
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .coverage
8
+ coverage.xml
9
+ htmlcov/
10
+ dist/
11
+ build/
12
+ .venv/
13
+ .env
14
+ .env.*
15
+ !.env.example
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial Python SDK release.
6
+ - Add email sending, contacts, segments, topics, and automation event resources.
7
+ - Add consistent `ParcelWingError` exception mapping.
8
+ - Add examples and tests.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Parcel Wing
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.
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: parcelwing
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Parcel Wing API.
5
+ Project-URL: Homepage, https://parcelwing.com
6
+ Project-URL: Repository, https://github.com/parcelwing/parcelwing-python
7
+ Project-URL: Issues, https://github.com/parcelwing/parcelwing-python/issues
8
+ Author: Parcel Wing
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: email,marketing-email,parcel-wing,python,sdk,transactional-email
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
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
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Communications :: Email
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.10; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.8; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Parcel Wing Python SDK
32
+
33
+ The official Python SDK for the Parcel Wing API.
34
+
35
+ It is designed for a fast, predictable developer experience:
36
+
37
+ - resource clients for emails, contacts, segments, topics, and automations
38
+ - consistent `ParcelWingError` exceptions
39
+ - typed package metadata and exported type hints
40
+ - small dependency surface built on `httpx`
41
+ - works with the same public API contract used by Parcel Wing itself
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install parcelwing
47
+ ```
48
+
49
+ ## Quick start
50
+
51
+ First you'll need an API key. If you don't have one, sign up and create one at https://parcelwing.com/signup. It's free, with no credit card required.
52
+
53
+ ```python
54
+ import os
55
+
56
+ from parcelwing import ParcelWing
57
+
58
+ parcel_wing = ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"])
59
+
60
+ emails = parcel_wing.emails.send(
61
+ from_="Acme <hello@yourdomain.com>",
62
+ to="person@example.com",
63
+ subject="Hello from Parcel Wing",
64
+ text="It works.",
65
+ )
66
+
67
+ print(emails[0]["id"])
68
+ ```
69
+
70
+ `from` is a Python keyword, so the keyword-argument API uses `from_`. You can also pass a raw API dictionary if you prefer exact API field names:
71
+
72
+ ```python
73
+ emails = parcel_wing.emails.send({
74
+ "from": "Acme <hello@yourdomain.com>",
75
+ "to": "person@example.com",
76
+ "subject": "Hello from Parcel Wing",
77
+ "text": "It works.",
78
+ })
79
+ ```
80
+
81
+ ## Using templates
82
+
83
+ ```python
84
+ emails = parcel_wing.emails.send(
85
+ from_="Acme <hello@yourdomain.com>",
86
+ to="person@example.com",
87
+ template_alias="welcome_email",
88
+ template_params={
89
+ "first_name": "John",
90
+ },
91
+ )
92
+ ```
93
+
94
+ ## Contacts
95
+
96
+ ```python
97
+ contact = parcel_wing.contacts.create({
98
+ "email": "person@example.com",
99
+ "first_name": "John",
100
+ "attributes": {
101
+ "plan": "pro",
102
+ },
103
+ })
104
+
105
+ page = parcel_wing.contacts.list(page=1, limit=20)
106
+
107
+ print(len(page["data"]), page.get("pagination", {}).get("total"))
108
+ ```
109
+
110
+ Batch create contacts:
111
+
112
+ ```python
113
+ result = parcel_wing.contacts.create([
114
+ {"email": "one@example.com", "first_name": "One"},
115
+ {"email": "two@example.com", "first_name": "Two"},
116
+ ])
117
+
118
+ print(result["created"])
119
+ print(result["failed"])
120
+ ```
121
+
122
+ ## Segments
123
+
124
+ ```python
125
+ segment = parcel_wing.segments.create({
126
+ "name": "Pro plan users",
127
+ "filter_criteria": {
128
+ "version": 1,
129
+ "match": "all",
130
+ "conditions": [
131
+ {
132
+ "field": "attribute",
133
+ "attribute_key": "plan",
134
+ "operator": "equals",
135
+ "value": "pro",
136
+ },
137
+ ],
138
+ },
139
+ })
140
+ ```
141
+
142
+ ## Topics
143
+
144
+ ```python
145
+ topic = parcel_wing.topics.create({
146
+ "name": "Product Updates",
147
+ "description": "Feature launches and release notes.",
148
+ "default_subscription": "opt_in",
149
+ "visibility": "public",
150
+ })
151
+ ```
152
+
153
+ ## Automation events
154
+
155
+ ```python
156
+ parcel_wing.automations.track({
157
+ "event_name": "user.completed_onboarding",
158
+ "contact_id": "6d9dc8f7-c44e-4f2d-8a4e-d04f32f1744f",
159
+ "payload": {
160
+ "plan": "flight",
161
+ },
162
+ })
163
+ ```
164
+
165
+ ## Error handling
166
+
167
+ ```python
168
+ from parcelwing import ParcelWingError
169
+
170
+ try:
171
+ parcel_wing.emails.send(
172
+ from_="Acme <hello@yourdomain.com>",
173
+ to="person@example.com",
174
+ subject="Hello",
175
+ text="Hi there",
176
+ )
177
+ except ParcelWingError as error:
178
+ print(error.status, error.type, error.code, error.request_id)
179
+ print(error.details)
180
+ ```
181
+
182
+ ## Configuration
183
+
184
+ ```python
185
+ parcel_wing = ParcelWing(
186
+ api_key=os.environ["PARCEL_WING_API_KEY"],
187
+ base_url="https://parcelwing.com",
188
+ timeout=30.0,
189
+ )
190
+ ```
191
+
192
+ Use the client as a context manager to close the underlying HTTP connection pool automatically:
193
+
194
+ ```python
195
+ with ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"]) as parcel_wing:
196
+ emails = parcel_wing.emails.send(
197
+ from_="Acme <hello@yourdomain.com>",
198
+ to="person@example.com",
199
+ subject="Hello",
200
+ text="It works.",
201
+ )
202
+ ```
203
+
204
+ ## Local development
205
+
206
+ ```bash
207
+ python -m venv .venv
208
+ source .venv/bin/activate
209
+ pip install -e ".[dev]"
210
+ pytest
211
+ ruff check .
212
+ mypy src/parcelwing
213
+ ```
214
+
215
+ ## Publishing
216
+
217
+ ```bash
218
+ python -m pip install build twine
219
+ python -m build
220
+ twine upload dist/*
221
+ ```
@@ -0,0 +1,191 @@
1
+ # Parcel Wing Python SDK
2
+
3
+ The official Python SDK for the Parcel Wing API.
4
+
5
+ It is designed for a fast, predictable developer experience:
6
+
7
+ - resource clients for emails, contacts, segments, topics, and automations
8
+ - consistent `ParcelWingError` exceptions
9
+ - typed package metadata and exported type hints
10
+ - small dependency surface built on `httpx`
11
+ - works with the same public API contract used by Parcel Wing itself
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install parcelwing
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ First you'll need an API key. If you don't have one, sign up and create one at https://parcelwing.com/signup. It's free, with no credit card required.
22
+
23
+ ```python
24
+ import os
25
+
26
+ from parcelwing import ParcelWing
27
+
28
+ parcel_wing = ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"])
29
+
30
+ emails = parcel_wing.emails.send(
31
+ from_="Acme <hello@yourdomain.com>",
32
+ to="person@example.com",
33
+ subject="Hello from Parcel Wing",
34
+ text="It works.",
35
+ )
36
+
37
+ print(emails[0]["id"])
38
+ ```
39
+
40
+ `from` is a Python keyword, so the keyword-argument API uses `from_`. You can also pass a raw API dictionary if you prefer exact API field names:
41
+
42
+ ```python
43
+ emails = parcel_wing.emails.send({
44
+ "from": "Acme <hello@yourdomain.com>",
45
+ "to": "person@example.com",
46
+ "subject": "Hello from Parcel Wing",
47
+ "text": "It works.",
48
+ })
49
+ ```
50
+
51
+ ## Using templates
52
+
53
+ ```python
54
+ emails = parcel_wing.emails.send(
55
+ from_="Acme <hello@yourdomain.com>",
56
+ to="person@example.com",
57
+ template_alias="welcome_email",
58
+ template_params={
59
+ "first_name": "John",
60
+ },
61
+ )
62
+ ```
63
+
64
+ ## Contacts
65
+
66
+ ```python
67
+ contact = parcel_wing.contacts.create({
68
+ "email": "person@example.com",
69
+ "first_name": "John",
70
+ "attributes": {
71
+ "plan": "pro",
72
+ },
73
+ })
74
+
75
+ page = parcel_wing.contacts.list(page=1, limit=20)
76
+
77
+ print(len(page["data"]), page.get("pagination", {}).get("total"))
78
+ ```
79
+
80
+ Batch create contacts:
81
+
82
+ ```python
83
+ result = parcel_wing.contacts.create([
84
+ {"email": "one@example.com", "first_name": "One"},
85
+ {"email": "two@example.com", "first_name": "Two"},
86
+ ])
87
+
88
+ print(result["created"])
89
+ print(result["failed"])
90
+ ```
91
+
92
+ ## Segments
93
+
94
+ ```python
95
+ segment = parcel_wing.segments.create({
96
+ "name": "Pro plan users",
97
+ "filter_criteria": {
98
+ "version": 1,
99
+ "match": "all",
100
+ "conditions": [
101
+ {
102
+ "field": "attribute",
103
+ "attribute_key": "plan",
104
+ "operator": "equals",
105
+ "value": "pro",
106
+ },
107
+ ],
108
+ },
109
+ })
110
+ ```
111
+
112
+ ## Topics
113
+
114
+ ```python
115
+ topic = parcel_wing.topics.create({
116
+ "name": "Product Updates",
117
+ "description": "Feature launches and release notes.",
118
+ "default_subscription": "opt_in",
119
+ "visibility": "public",
120
+ })
121
+ ```
122
+
123
+ ## Automation events
124
+
125
+ ```python
126
+ parcel_wing.automations.track({
127
+ "event_name": "user.completed_onboarding",
128
+ "contact_id": "6d9dc8f7-c44e-4f2d-8a4e-d04f32f1744f",
129
+ "payload": {
130
+ "plan": "flight",
131
+ },
132
+ })
133
+ ```
134
+
135
+ ## Error handling
136
+
137
+ ```python
138
+ from parcelwing import ParcelWingError
139
+
140
+ try:
141
+ parcel_wing.emails.send(
142
+ from_="Acme <hello@yourdomain.com>",
143
+ to="person@example.com",
144
+ subject="Hello",
145
+ text="Hi there",
146
+ )
147
+ except ParcelWingError as error:
148
+ print(error.status, error.type, error.code, error.request_id)
149
+ print(error.details)
150
+ ```
151
+
152
+ ## Configuration
153
+
154
+ ```python
155
+ parcel_wing = ParcelWing(
156
+ api_key=os.environ["PARCEL_WING_API_KEY"],
157
+ base_url="https://parcelwing.com",
158
+ timeout=30.0,
159
+ )
160
+ ```
161
+
162
+ Use the client as a context manager to close the underlying HTTP connection pool automatically:
163
+
164
+ ```python
165
+ with ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"]) as parcel_wing:
166
+ emails = parcel_wing.emails.send(
167
+ from_="Acme <hello@yourdomain.com>",
168
+ to="person@example.com",
169
+ subject="Hello",
170
+ text="It works.",
171
+ )
172
+ ```
173
+
174
+ ## Local development
175
+
176
+ ```bash
177
+ python -m venv .venv
178
+ source .venv/bin/activate
179
+ pip install -e ".[dev]"
180
+ pytest
181
+ ruff check .
182
+ mypy src/parcelwing
183
+ ```
184
+
185
+ ## Publishing
186
+
187
+ ```bash
188
+ python -m pip install build twine
189
+ python -m build
190
+ twine upload dist/*
191
+ ```
@@ -0,0 +1,14 @@
1
+ import os
2
+
3
+ from parcelwing import ParcelWing
4
+
5
+ parcel_wing = ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"])
6
+
7
+ emails = parcel_wing.emails.send(
8
+ from_="Acme <hello@yourdomain.com>",
9
+ to="person@example.com",
10
+ subject="Hello from Parcel Wing",
11
+ text="It works.",
12
+ )
13
+
14
+ print(emails[0]["id"])
@@ -0,0 +1,14 @@
1
+ import os
2
+
3
+ from parcelwing import ParcelWing
4
+
5
+ parcel_wing = ParcelWing(api_key=os.environ["PARCEL_WING_API_KEY"])
6
+
7
+ emails = parcel_wing.emails.send(
8
+ from_="Acme <hello@yourdomain.com>",
9
+ to="person@example.com",
10
+ template_alias="welcome_email",
11
+ template_params={"first_name": "John"},
12
+ )
13
+
14
+ print(emails[0]["id"])
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "parcelwing"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Parcel Wing API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Parcel Wing" }]
13
+ keywords = ["parcel-wing", "email", "transactional-email", "marketing-email", "sdk", "python"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Communications :: Email",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = ["httpx>=0.27,<1"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://parcelwing.com"
31
+ Repository = "https://github.com/parcelwing/parcelwing-python"
32
+ Issues = "https://github.com/parcelwing/parcelwing-python/issues"
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.0",
37
+ "ruff>=0.8",
38
+ "mypy>=1.10",
39
+ ]
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/parcelwing"]
43
+
44
+ [tool.ruff]
45
+ line-length = 88
46
+ target-version = "py39"
47
+
48
+ [tool.ruff.lint]
49
+ select = ["E", "F", "I", "UP", "B", "SIM"]
50
+
51
+ [tool.mypy]
52
+ python_version = "3.9"
53
+ strict = true
54
+ packages = ["parcelwing"]
@@ -0,0 +1,11 @@
1
+ """Official Python SDK for Parcel Wing."""
2
+
3
+ from .client import ParcelWing
4
+ from .errors import ParcelWingError, ParcelWingErrorType, is_parcelwing_error
5
+
6
+ __all__ = [
7
+ "ParcelWing",
8
+ "ParcelWingError",
9
+ "ParcelWingErrorType",
10
+ "is_parcelwing_error",
11
+ ]