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.
@@ -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.5.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(sub_org_id="...")
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
- `your_reference`, `quantity` and `checked_at` are your org's own, never
105
- shown to the other side.
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, your_reference="PO-4471-A", quantity="12.5", checked=True)
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(sub_org_id="...")
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
- `your_reference`, `quantity` and `checked_at` are your org's own, never
82
- shown to the other side.
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, your_reference="PO-4471-A", quantity="12.5", checked=True)
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "workwire"
7
- version = "0.5.0"
7
+ version = "0.7.0"
8
8
  description = "Official Python SDK for the Workwire API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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; ``your_reference``,
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. ``your_reference``, ``quantity`` and
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
- ``your_reference``, ``quantity`` (in the org's ``quantity_unit``) and
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
- - ``your_reference`` — your own reference (≤200 characters).
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 your_reference, quantity, checked "
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 (``sub_org_id=``) or cold-invite a
4
- sub by email (``cold_invite=ColdInvite(...)`` — the partnership, the
5
- invitation, and the email are created atomically). Exactly one of the two;
6
- the invitation email greets the recipient **as your organization**.
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 ``sub_org_id`` or ``cold_invite``.
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 (sub_org_id is None) == (cold_invite is None):
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 sub_org_id is not None:
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, with both org
69
- names. Requires ``partnerships:read``."""
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