workwire 0.5.0__tar.gz → 0.7.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.
- {workwire-0.5.0 → workwire-0.7.0}/CHANGELOG.md +14 -0
- {workwire-0.5.0 → workwire-0.7.0}/PKG-INFO +7 -5
- {workwire-0.5.0 → workwire-0.7.0}/README.md +6 -4
- {workwire-0.5.0 → workwire-0.7.0}/pyproject.toml +1 -1
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/_coverage.py +2 -2
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/_models.py +45 -4
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/jobs.py +16 -4
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/partnerships.py +28 -10
- {workwire-0.5.0 → workwire-0.7.0}/.gitignore +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/LICENSE +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/SECURITY.md +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/__init__.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/_client.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/_version.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/errors.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/py.typed +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/__init__.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/locations.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/projects.py +0 -0
- {workwire-0.5.0 → workwire-0.7.0}/src/workwire/resources/wires.py +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0](https://github.com/workwire/workwire-python/compare/v0.6.0...v0.7.0) (2026-11-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **WOR-569:** backend v1.81.0's spec — jobs.update(reference=), PartyJob and JobRow gain reference ([#28](https://github.com/workwire/workwire-python/issues/28)) ([b94e055](https://github.com/workwire/workwire-python/commit/b94e0556429fbebacbda2f1d444d86f7b755838f))
|
|
9
|
+
|
|
10
|
+
## [0.6.0](https://github.com/workwire/workwire-python/compare/v0.5.0...v0.6.0) (2026-10-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **WOR-547:** backend v1.71.1's spec — partnerships name the counterparty, the wire list carries viewer_party_id ([#26](https://github.com/workwire/workwire-python/issues/26)) ([e5199ba](https://github.com/workwire/workwire-python/commit/e5199ba1e6c2bf95136815e475698fc0a93da02c))
|
|
16
|
+
|
|
3
17
|
## [0.5.0](https://github.com/workwire/workwire-python/compare/v0.4.0...v0.5.0) (2026-10-24)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workwire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Official Python SDK for the Workwire API
|
|
5
5
|
Project-URL: Homepage, https://github.com/workwire/workwire-python
|
|
6
6
|
Project-URL: Changelog, https://github.com/workwire/workwire-python/blob/main/CHANGELOG.md
|
|
@@ -54,7 +54,7 @@ project = client.projects.create(name="Elm St. Townhomes")
|
|
|
54
54
|
|
|
55
55
|
# The partnership with your sub — create one (see below) or pick an
|
|
56
56
|
# established one from client.partnerships.list().
|
|
57
|
-
partnership = client.partnerships.create(
|
|
57
|
+
partnership = client.partnerships.create(org_id="...")
|
|
58
58
|
|
|
59
59
|
wire = client.wires.create(
|
|
60
60
|
project_id=project.id,
|
|
@@ -101,8 +101,10 @@ with `--go-live`.
|
|
|
101
101
|
Every project has a location tree under one `site` root
|
|
102
102
|
(`project.root_location_id`). A party's scope is the locations its work
|
|
103
103
|
covers, and it gets one Job (`JOB-…`) per scope location. A Job's
|
|
104
|
-
`
|
|
105
|
-
|
|
104
|
+
`reference`, `quantity` and `checked_at` are your org's own, never shown to
|
|
105
|
+
the other side. `your_reference` is the deprecated spelling of `reference`
|
|
106
|
+
— still served beside it with the same value, and still accepted by
|
|
107
|
+
`client.jobs.update` (one or the other, never both).
|
|
106
108
|
|
|
107
109
|
```python
|
|
108
110
|
from workwire import LocationNode, Party
|
|
@@ -130,7 +132,7 @@ client.wires.create(
|
|
|
130
132
|
],
|
|
131
133
|
)
|
|
132
134
|
page = client.jobs.list(project=project.id) # page.quantity_unit is your org's unit
|
|
133
|
-
client.jobs.update(page.data[0].id,
|
|
135
|
+
client.jobs.update(page.data[0].id, reference="PO-4471-A", quantity="12.5", checked=True)
|
|
134
136
|
```
|
|
135
137
|
|
|
136
138
|
A party Job — each of `client.wires.retrieve(...).parties[i].jobs`, and the
|
|
@@ -31,7 +31,7 @@ project = client.projects.create(name="Elm St. Townhomes")
|
|
|
31
31
|
|
|
32
32
|
# The partnership with your sub — create one (see below) or pick an
|
|
33
33
|
# established one from client.partnerships.list().
|
|
34
|
-
partnership = client.partnerships.create(
|
|
34
|
+
partnership = client.partnerships.create(org_id="...")
|
|
35
35
|
|
|
36
36
|
wire = client.wires.create(
|
|
37
37
|
project_id=project.id,
|
|
@@ -78,8 +78,10 @@ with `--go-live`.
|
|
|
78
78
|
Every project has a location tree under one `site` root
|
|
79
79
|
(`project.root_location_id`). A party's scope is the locations its work
|
|
80
80
|
covers, and it gets one Job (`JOB-…`) per scope location. A Job's
|
|
81
|
-
`
|
|
82
|
-
|
|
81
|
+
`reference`, `quantity` and `checked_at` are your org's own, never shown to
|
|
82
|
+
the other side. `your_reference` is the deprecated spelling of `reference`
|
|
83
|
+
— still served beside it with the same value, and still accepted by
|
|
84
|
+
`client.jobs.update` (one or the other, never both).
|
|
83
85
|
|
|
84
86
|
```python
|
|
85
87
|
from workwire import LocationNode, Party
|
|
@@ -107,7 +109,7 @@ client.wires.create(
|
|
|
107
109
|
],
|
|
108
110
|
)
|
|
109
111
|
page = client.jobs.list(project=project.id) # page.quantity_unit is your org's unit
|
|
110
|
-
client.jobs.update(page.data[0].id,
|
|
112
|
+
client.jobs.update(page.data[0].id, reference="PO-4471-A", quantity="12.5", checked=True)
|
|
111
113
|
```
|
|
112
114
|
|
|
113
115
|
A party Job — each of `client.wires.retrieve(...).parties[i].jobs`, and the
|
|
@@ -116,7 +116,7 @@ OPERATIONS: dict[str, dict[str, object]] = {
|
|
|
116
116
|
"createPartnership": {
|
|
117
117
|
"method": "POST",
|
|
118
118
|
"path": "/v1/partnerships",
|
|
119
|
-
"request_fields": {"sub_org_id", "cold_invite"},
|
|
119
|
+
"request_fields": {"org_id", "sub_org_id", "cold_invite"},
|
|
120
120
|
"query_params": set(),
|
|
121
121
|
"response_model": _models.Partnership,
|
|
122
122
|
},
|
|
@@ -147,7 +147,7 @@ OPERATIONS: dict[str, dict[str, object]] = {
|
|
|
147
147
|
"updateJob": {
|
|
148
148
|
"method": "PATCH",
|
|
149
149
|
"path": "/v1/jobs/{id}",
|
|
150
|
-
"request_fields": {"your_reference", "quantity", "checked"},
|
|
150
|
+
"request_fields": {"reference", "your_reference", "quantity", "checked"},
|
|
151
151
|
"query_params": set(),
|
|
152
152
|
"response_model": _models.PartyJob,
|
|
153
153
|
},
|
|
@@ -184,10 +184,12 @@ class PartyJob(WorkwireObject):
|
|
|
184
184
|
"""One Job on a wire party (``JOB-…``) — the party's work at one location
|
|
185
185
|
of its scope. ``location_name``, ``location_kind`` and ``location_path``
|
|
186
186
|
(the ancestors, root first; empty at the site root) are the server's
|
|
187
|
-
description of where the Job sits in the GC's tree; ``
|
|
187
|
+
description of where the Job sits in the GC's tree; ``reference``,
|
|
188
188
|
``quantity`` and ``checked_at`` are **your org's own** fields, never the
|
|
189
189
|
other side's. ``location_unit_type`` is the location's ``unit_type`` — the
|
|
190
|
-
label a builder gives a unit's plan, ``None`` when unset.
|
|
190
|
+
label a builder gives a unit's plan, ``None`` when unset.
|
|
191
|
+
``your_reference`` is deprecated — still served, the same value as
|
|
192
|
+
``reference``, which replaces it."""
|
|
191
193
|
|
|
192
194
|
_nested = {"location_path": JobPathLocation}
|
|
193
195
|
|
|
@@ -198,6 +200,7 @@ class PartyJob(WorkwireObject):
|
|
|
198
200
|
location_kind: str
|
|
199
201
|
location_unit_type: str | None
|
|
200
202
|
location_path: list[JobPathLocation]
|
|
203
|
+
reference: str | None
|
|
201
204
|
your_reference: str | None
|
|
202
205
|
quantity: str | None
|
|
203
206
|
checked_at: str | None
|
|
@@ -266,21 +269,36 @@ class Wire(WorkwireObject):
|
|
|
266
269
|
|
|
267
270
|
|
|
268
271
|
class WireListItem(WorkwireObject):
|
|
272
|
+
"""One row of :meth:`Wires.list`. ``viewer_party_id`` is your org's one
|
|
273
|
+
party on the wire when your org is its sub — ``None`` when your org is
|
|
274
|
+
the wire's origin or holds more than one party on it."""
|
|
275
|
+
|
|
269
276
|
id: str
|
|
270
277
|
display_id: str
|
|
271
278
|
project_id: str
|
|
272
279
|
title: str
|
|
273
280
|
state: str
|
|
274
281
|
party_count: int
|
|
282
|
+
viewer_party_id: str | None
|
|
275
283
|
created_at: str
|
|
276
284
|
sent_at: str | None
|
|
277
285
|
closed_at: str | None
|
|
278
286
|
|
|
279
287
|
|
|
280
288
|
class Partnership(WorkwireObject):
|
|
289
|
+
"""A partnership as created, read from your org's side:
|
|
290
|
+
``counterparty_org_id`` is the other org and ``initiated_by_org_id`` is
|
|
291
|
+
the org that asked for the partnership — equal to ``counterparty_org_id``
|
|
292
|
+
when the other org asked, your org's id otherwise. ``gc_org_id`` and
|
|
293
|
+
``sub_org_id`` are deprecated — still served, replaced by
|
|
294
|
+
``counterparty_org_id`` and ``initiated_by_org_id``. ``invitation_id`` is
|
|
295
|
+
set on the cold-invite path only."""
|
|
296
|
+
|
|
281
297
|
id: str
|
|
282
298
|
gc_org_id: str
|
|
283
299
|
sub_org_id: str
|
|
300
|
+
counterparty_org_id: str
|
|
301
|
+
initiated_by_org_id: str
|
|
284
302
|
status: str
|
|
285
303
|
initiation_path: str
|
|
286
304
|
created_at: str
|
|
@@ -290,9 +308,21 @@ class Partnership(WorkwireObject):
|
|
|
290
308
|
|
|
291
309
|
|
|
292
310
|
class PartnershipDetail(WorkwireObject):
|
|
311
|
+
"""One partnership read from your org's side: ``counterparty_org_id`` and
|
|
312
|
+
``counterparty_name`` (``None`` while the other org has no name yet) are
|
|
313
|
+
the other org; ``initiated_by_org_id`` is the org that asked for the
|
|
314
|
+
partnership — equal to ``counterparty_org_id`` when the other org asked,
|
|
315
|
+
your org's id otherwise. Deprecated — still served: ``gc_org_id`` and
|
|
316
|
+
``sub_org_id`` (replaced by ``counterparty_org_id`` and
|
|
317
|
+
``initiated_by_org_id``), ``gc_org_name`` and ``sub_org_name`` (replaced
|
|
318
|
+
by ``counterparty_name``)."""
|
|
319
|
+
|
|
293
320
|
id: str
|
|
294
321
|
gc_org_id: str
|
|
295
322
|
sub_org_id: str
|
|
323
|
+
counterparty_org_id: str
|
|
324
|
+
counterparty_name: str | None
|
|
325
|
+
initiated_by_org_id: str
|
|
296
326
|
status: str
|
|
297
327
|
initiation_path: str
|
|
298
328
|
created_at: str
|
|
@@ -303,10 +333,18 @@ class PartnershipDetail(WorkwireObject):
|
|
|
303
333
|
|
|
304
334
|
|
|
305
335
|
class PartnershipListItem(WorkwireObject):
|
|
336
|
+
"""One row of :meth:`Partnerships.list`, read from your org's side:
|
|
337
|
+
``counterparty_org_id`` and ``counterparty_name`` are the other org;
|
|
338
|
+
``initiated_by_org_id`` is the org that asked for the partnership — equal
|
|
339
|
+
to ``counterparty_org_id`` when the other org asked, your org's id
|
|
340
|
+
otherwise. ``role`` is deprecated — still served, replaced by
|
|
341
|
+
``initiated_by_org_id``."""
|
|
342
|
+
|
|
306
343
|
id: str
|
|
307
344
|
role: str
|
|
308
345
|
counterparty_org_id: str
|
|
309
346
|
counterparty_name: str | None
|
|
347
|
+
initiated_by_org_id: str
|
|
310
348
|
status: str
|
|
311
349
|
initiation_path: str
|
|
312
350
|
created_at: str
|
|
@@ -365,15 +403,18 @@ class JobMilestones(WorkwireObject):
|
|
|
365
403
|
|
|
366
404
|
class JobRow(WorkwireObject):
|
|
367
405
|
"""One row of :meth:`Jobs.list`: the Job with its party, wire, project,
|
|
368
|
-
location and counterparty context. ``
|
|
406
|
+
location and counterparty context. ``reference``, ``quantity`` and
|
|
369
407
|
``checked_at`` are your org's own fields, never the other side's.
|
|
370
408
|
``location_unit_type`` is the location's ``unit_type`` — the label a
|
|
371
|
-
builder gives a unit's plan, ``None`` when unset.
|
|
409
|
+
builder gives a unit's plan, ``None`` when unset. ``your_reference`` is
|
|
410
|
+
deprecated — still served, the same value as ``reference``, which
|
|
411
|
+
replaces it."""
|
|
372
412
|
|
|
373
413
|
_nested = {"location_path": JobPathLocation, "milestones": JobMilestones}
|
|
374
414
|
|
|
375
415
|
id: str
|
|
376
416
|
display_id: str
|
|
417
|
+
reference: str | None
|
|
377
418
|
your_reference: str | None
|
|
378
419
|
quantity: str | None
|
|
379
420
|
checked_at: str | None
|
|
@@ -5,8 +5,10 @@ Jobs are derived: a party gets one per scope location
|
|
|
5
5
|
(``scope_location_ids`` on ``Party.partnered(...)`` /
|
|
6
6
|
``Party.cold_invite(...)``; absent, the whole project), and nothing creates,
|
|
7
7
|
names or closes one. Each org keeps its own private fields on a Job —
|
|
8
|
-
``
|
|
9
|
-
``checked_at`` — never shown to the other side.
|
|
8
|
+
``reference``, ``quantity`` (in the org's ``quantity_unit``) and
|
|
9
|
+
``checked_at`` — never shown to the other side. ``your_reference`` is the
|
|
10
|
+
deprecated spelling of ``reference``: still served beside it, the same
|
|
11
|
+
value, and still accepted by :meth:`Jobs.update`.
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
from __future__ import annotations
|
|
@@ -121,6 +123,7 @@ class Jobs:
|
|
|
121
123
|
self,
|
|
122
124
|
job_id: str,
|
|
123
125
|
*,
|
|
126
|
+
reference: str | None | NotGiven = NOT_GIVEN,
|
|
124
127
|
your_reference: str | None | NotGiven = NOT_GIVEN,
|
|
125
128
|
quantity: str | Decimal | None | NotGiven = NOT_GIVEN,
|
|
126
129
|
checked: bool | None | NotGiven = NOT_GIVEN,
|
|
@@ -134,7 +137,10 @@ class Jobs:
|
|
|
134
137
|
and the server keeps that field; an explicit ``None`` is sent as
|
|
135
138
|
``null`` and clears it. At least one is required.
|
|
136
139
|
|
|
137
|
-
- ``
|
|
140
|
+
- ``reference`` — your own reference (≤200 characters).
|
|
141
|
+
- ``your_reference`` — deprecated: still accepted in place of
|
|
142
|
+
``reference`` and sent as ``your_reference``. Pass one or the
|
|
143
|
+
other, never both (``None`` counts as passed).
|
|
138
144
|
- ``quantity`` — a decimal string (``"12.5"``) or a
|
|
139
145
|
``decimal.Decimal``, in your org's ``quantity_unit``; at most 2
|
|
140
146
|
decimal places. Never a float.
|
|
@@ -145,7 +151,13 @@ class Jobs:
|
|
|
145
151
|
your org is not a side of — a draft's Job, for the sub — is a
|
|
146
152
|
``NotFoundError``. Returns the Job as your org now sees it.
|
|
147
153
|
"""
|
|
154
|
+
if not isinstance(reference, NotGiven) and not isinstance(your_reference, NotGiven):
|
|
155
|
+
raise ValueError("pass reference or your_reference, not both")
|
|
148
156
|
body: dict[str, Any] = {}
|
|
157
|
+
if not isinstance(reference, NotGiven):
|
|
158
|
+
if reference is not None and not isinstance(reference, str):
|
|
159
|
+
raise TypeError("reference must be a str or None")
|
|
160
|
+
body["reference"] = reference
|
|
149
161
|
if not isinstance(your_reference, NotGiven):
|
|
150
162
|
if your_reference is not None and not isinstance(your_reference, str):
|
|
151
163
|
raise TypeError("your_reference must be a str or None")
|
|
@@ -158,7 +170,7 @@ class Jobs:
|
|
|
158
170
|
body["checked"] = checked
|
|
159
171
|
if not body:
|
|
160
172
|
raise ValueError(
|
|
161
|
-
"pass at least one of
|
|
173
|
+
"pass at least one of reference, quantity, checked "
|
|
162
174
|
"(None clears a field; leaving one out keeps it)"
|
|
163
175
|
)
|
|
164
176
|
data, request_id = self._client._request(
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"""Partnerships — the GC↔sub relationship Wires require.
|
|
2
2
|
|
|
3
|
-
Create one against a known Workwire org (``
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Create one against a known Workwire org (``org_id=``; the deprecated
|
|
4
|
+
``sub_org_id=`` is still accepted in its place) or cold-invite a sub by email
|
|
5
|
+
(``cold_invite=ColdInvite(...)`` — the partnership, the invitation, and the
|
|
6
|
+
email are created atomically). Exactly one of them; the invitation email
|
|
7
|
+
greets the recipient **as your organization**.
|
|
8
|
+
|
|
9
|
+
A partnership reads from your organization's side: ``counterparty_org_id``
|
|
10
|
+
is the other organization and ``initiated_by_org_id`` is the organization
|
|
11
|
+
that asked for the partnership — equal to ``counterparty_org_id`` when the
|
|
12
|
+
other organization asked, yours otherwise.
|
|
7
13
|
"""
|
|
8
14
|
|
|
9
15
|
from __future__ import annotations
|
|
@@ -29,13 +35,20 @@ class Partnerships:
|
|
|
29
35
|
def create(
|
|
30
36
|
self,
|
|
31
37
|
*,
|
|
38
|
+
org_id: str | None = None,
|
|
32
39
|
sub_org_id: str | None = None,
|
|
33
40
|
cold_invite: ColdInvite | None = None,
|
|
34
41
|
idempotency_key: str | None = None,
|
|
35
42
|
timeout: float | None = None,
|
|
36
43
|
) -> Partnership:
|
|
37
44
|
"""Create a partnership. Requires ``partnerships:write``. Pass
|
|
38
|
-
exactly one of ``
|
|
45
|
+
exactly one of ``org_id`` (an organization already on Workwire) or
|
|
46
|
+
``cold_invite``. ``sub_org_id`` is deprecated — still accepted in
|
|
47
|
+
place of ``org_id`` and sent as ``sub_org_id``.
|
|
48
|
+
|
|
49
|
+
The partnership comes back read from your side:
|
|
50
|
+
``initiated_by_org_id`` is the organization that asked — yours, on a
|
|
51
|
+
create.
|
|
39
52
|
|
|
40
53
|
The cold-invite path **emails a real person**, counts against the
|
|
41
54
|
100/org/rolling-24h machine invitation quota (breach raises
|
|
@@ -43,10 +56,12 @@ class Partnerships:
|
|
|
43
56
|
``partnership_invites_per_org_per_day``), and returns the
|
|
44
57
|
partnership with ``invitation_id`` set.
|
|
45
58
|
"""
|
|
46
|
-
if (
|
|
47
|
-
raise ValueError("pass exactly one of sub_org_id or cold_invite")
|
|
59
|
+
if sum(arg is not None for arg in (org_id, sub_org_id, cold_invite)) != 1:
|
|
60
|
+
raise ValueError("pass exactly one of org_id, sub_org_id or cold_invite")
|
|
48
61
|
body: dict[str, Any]
|
|
49
|
-
if
|
|
62
|
+
if org_id is not None:
|
|
63
|
+
body = {"org_id": org_id}
|
|
64
|
+
elif sub_org_id is not None:
|
|
50
65
|
body = {"sub_org_id": sub_org_id}
|
|
51
66
|
else:
|
|
52
67
|
assert cold_invite is not None
|
|
@@ -65,8 +80,11 @@ class Partnerships:
|
|
|
65
80
|
def retrieve(
|
|
66
81
|
self, partnership_id: str, *, timeout: float | None = None
|
|
67
82
|
) -> PartnershipDetail:
|
|
68
|
-
"""Fetch one partnership your org is a side of,
|
|
69
|
-
|
|
83
|
+
"""Fetch one partnership your org is a side of, read from your
|
|
84
|
+
side: ``counterparty_org_id`` and ``counterparty_name`` are the other
|
|
85
|
+
organization, and ``initiated_by_org_id`` is the organization that
|
|
86
|
+
asked. The deprecated ``gc_org_id`` / ``sub_org_id`` and both org
|
|
87
|
+
names are still served. Requires ``partnerships:read``."""
|
|
70
88
|
data, request_id = self._client._request(
|
|
71
89
|
"GET", f"/v1/partnerships/{partnership_id}", timeout=timeout
|
|
72
90
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|