workwire 0.3.0__tar.gz → 0.5.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.3.0 → workwire-0.5.0}/CHANGELOG.md +14 -0
- {workwire-0.3.0 → workwire-0.5.0}/PKG-INFO +37 -8
- {workwire-0.3.0 → workwire-0.5.0}/README.md +36 -7
- {workwire-0.3.0 → workwire-0.5.0}/pyproject.toml +1 -1
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/_client.py +1 -5
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/_coverage.py +1 -1
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/_models.py +43 -19
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/jobs.py +2 -2
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/locations.py +20 -6
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/projects.py +7 -4
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/wires.py +2 -5
- {workwire-0.3.0 → workwire-0.5.0}/.gitignore +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/LICENSE +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/SECURITY.md +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/__init__.py +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/_version.py +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/errors.py +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/py.typed +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/__init__.py +0 -0
- {workwire-0.3.0 → workwire-0.5.0}/src/workwire/resources/partnerships.py +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0](https://github.com/workwire/workwire-python/compare/v0.4.0...v0.5.0) (2026-10-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **WOR-511:** backend v1.62.0's spec — unit_type on locations and jobs, the kind_rank reason ([#24](https://github.com/workwire/workwire-python/issues/24)) ([c97d486](https://github.com/workwire/workwire-python/commit/c97d4860acfe7a73598c6c3407ccc9dc5d666be2))
|
|
9
|
+
|
|
10
|
+
## [0.4.0](https://github.com/workwire/workwire-python/compare/v0.3.0...v0.4.0) (2026-10-18)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **WOR-471:** party jobs carry location_name, location_kind and location_path (backend v1.54.0) ([#20](https://github.com/workwire/workwire-python/issues/20)) ([14f1c08](https://github.com/workwire/workwire-python/commit/14f1c083a600747521087a3b4b572d44c69934ef))
|
|
16
|
+
|
|
3
17
|
## [0.3.0](https://github.com/workwire/workwire-python/compare/v0.2.0...v0.3.0) (2026-10-16)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: workwire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -107,19 +107,48 @@ shown to the other side.
|
|
|
107
107
|
```python
|
|
108
108
|
from workwire import LocationNode, Party
|
|
109
109
|
|
|
110
|
-
project = client.projects.create(
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
project = client.projects.create(
|
|
111
|
+
name="Elm St. Townhomes",
|
|
112
|
+
template="development",
|
|
113
|
+
locations=[
|
|
114
|
+
LocationNode("building", "Bldg A", children=[LocationNode("floor", "Level 1")]),
|
|
115
|
+
],
|
|
116
|
+
)
|
|
113
117
|
tree = client.projects.locations.list(project.id).data # the site root first
|
|
114
118
|
level_1 = next(loc for loc in tree if loc.name == "Level 1")
|
|
115
|
-
client.wires.create(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
client.wires.create(
|
|
120
|
+
project_id=project.id,
|
|
121
|
+
title="Drywall — Level 1",
|
|
122
|
+
parties=[
|
|
123
|
+
Party.partnered(
|
|
124
|
+
partnership_id=partnership.id,
|
|
125
|
+
trade="Drywall",
|
|
126
|
+
scope_text="Level 1.",
|
|
127
|
+
contract_value="40000.00",
|
|
128
|
+
scope_location_ids=[level_1.id], # one Job per scope location
|
|
129
|
+
)
|
|
130
|
+
],
|
|
131
|
+
)
|
|
119
132
|
page = client.jobs.list(project=project.id) # page.quantity_unit is your org's unit
|
|
120
133
|
client.jobs.update(page.data[0].id, your_reference="PO-4471-A", quantity="12.5", checked=True)
|
|
121
134
|
```
|
|
122
135
|
|
|
136
|
+
A party Job — each of `client.wires.retrieve(...).parties[i].jobs`, and the
|
|
137
|
+
one `client.jobs.update` returns — carries `location_name`, `location_kind` and
|
|
138
|
+
`location_path` (the ancestors, root first), so a sub reads its scope without
|
|
139
|
+
the GC's tree; `client.jobs.list` rows had them already.
|
|
140
|
+
|
|
141
|
+
A tree only descends: `site` › `building`/`house` › `floor` ›
|
|
142
|
+
`unit`/`common_area` › `room`, levels skippable but never repeated or
|
|
143
|
+
reversed. A node that breaks the rank raises `InvalidRequestError` with code
|
|
144
|
+
`invalid_parent` and `details` `{"reason": "kind_rank", "child_kind": ...,
|
|
145
|
+
"parent_kind": ...}`.
|
|
146
|
+
|
|
147
|
+
`LocationNode(..., unit_type="C1.1 (2 Bedroom)")` (or
|
|
148
|
+
`locations.create(..., unit_type=...)`) labels a unit's plan; every
|
|
149
|
+
`Location` reads it back as `unit_type`, and a Job as `location_unit_type` —
|
|
150
|
+
`None` when unset.
|
|
151
|
+
|
|
123
152
|
Leave `scope_location_ids` out to scope a party to the whole project.
|
|
124
153
|
`client.jobs.update` sends only the fields you pass: leave one out to keep
|
|
125
154
|
it, pass `None` to clear it. Add to a tree later with
|
|
@@ -84,19 +84,48 @@ shown to the other side.
|
|
|
84
84
|
```python
|
|
85
85
|
from workwire import LocationNode, Party
|
|
86
86
|
|
|
87
|
-
project = client.projects.create(
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
project = client.projects.create(
|
|
88
|
+
name="Elm St. Townhomes",
|
|
89
|
+
template="development",
|
|
90
|
+
locations=[
|
|
91
|
+
LocationNode("building", "Bldg A", children=[LocationNode("floor", "Level 1")]),
|
|
92
|
+
],
|
|
93
|
+
)
|
|
90
94
|
tree = client.projects.locations.list(project.id).data # the site root first
|
|
91
95
|
level_1 = next(loc for loc in tree if loc.name == "Level 1")
|
|
92
|
-
client.wires.create(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
client.wires.create(
|
|
97
|
+
project_id=project.id,
|
|
98
|
+
title="Drywall — Level 1",
|
|
99
|
+
parties=[
|
|
100
|
+
Party.partnered(
|
|
101
|
+
partnership_id=partnership.id,
|
|
102
|
+
trade="Drywall",
|
|
103
|
+
scope_text="Level 1.",
|
|
104
|
+
contract_value="40000.00",
|
|
105
|
+
scope_location_ids=[level_1.id], # one Job per scope location
|
|
106
|
+
)
|
|
107
|
+
],
|
|
108
|
+
)
|
|
96
109
|
page = client.jobs.list(project=project.id) # page.quantity_unit is your org's unit
|
|
97
110
|
client.jobs.update(page.data[0].id, your_reference="PO-4471-A", quantity="12.5", checked=True)
|
|
98
111
|
```
|
|
99
112
|
|
|
113
|
+
A party Job — each of `client.wires.retrieve(...).parties[i].jobs`, and the
|
|
114
|
+
one `client.jobs.update` returns — carries `location_name`, `location_kind` and
|
|
115
|
+
`location_path` (the ancestors, root first), so a sub reads its scope without
|
|
116
|
+
the GC's tree; `client.jobs.list` rows had them already.
|
|
117
|
+
|
|
118
|
+
A tree only descends: `site` › `building`/`house` › `floor` ›
|
|
119
|
+
`unit`/`common_area` › `room`, levels skippable but never repeated or
|
|
120
|
+
reversed. A node that breaks the rank raises `InvalidRequestError` with code
|
|
121
|
+
`invalid_parent` and `details` `{"reason": "kind_rank", "child_kind": ...,
|
|
122
|
+
"parent_kind": ...}`.
|
|
123
|
+
|
|
124
|
+
`LocationNode(..., unit_type="C1.1 (2 Bedroom)")` (or
|
|
125
|
+
`locations.create(..., unit_type=...)`) labels a unit's plan; every
|
|
126
|
+
`Location` reads it back as `unit_type`, and a Job as `location_unit_type` —
|
|
127
|
+
`None` when unset.
|
|
128
|
+
|
|
100
129
|
Leave `scope_location_ids` out to scope a party to the whole project.
|
|
101
130
|
`client.jobs.update` sends only the fields you pass: leave one out to keep
|
|
102
131
|
it, pass `None` to clear it. Add to a tree later with
|
|
@@ -96,9 +96,7 @@ class BaseClient:
|
|
|
96
96
|
return {
|
|
97
97
|
"Authorization": f"Bearer {self._api_key}",
|
|
98
98
|
"Accept": "application/json",
|
|
99
|
-
"User-Agent": (
|
|
100
|
-
f"workwire-python/{__version__} python/{platform.python_version()}"
|
|
101
|
-
),
|
|
99
|
+
"User-Agent": (f"workwire-python/{__version__} python/{platform.python_version()}"),
|
|
102
100
|
"X-Workwire-Client-User-Agent": telemetry,
|
|
103
101
|
}
|
|
104
102
|
|
|
@@ -248,5 +246,3 @@ class BaseClient:
|
|
|
248
246
|
headers={k.lower(): v for k, v in response.headers.items()},
|
|
249
247
|
body=body,
|
|
250
248
|
)
|
|
251
|
-
|
|
252
|
-
|
|
@@ -66,7 +66,7 @@ OPERATIONS: dict[str, dict[str, object]] = {
|
|
|
66
66
|
"createProjectLocation": {
|
|
67
67
|
"method": "POST",
|
|
68
68
|
"path": "/v1/projects/{id}/locations",
|
|
69
|
-
"request_fields": {"parent_id", "kind", "name"},
|
|
69
|
+
"request_fields": {"parent_id", "kind", "name", "unit_type"},
|
|
70
70
|
"query_params": set(),
|
|
71
71
|
"response_model": _models.Location,
|
|
72
72
|
},
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
from collections.abc import Iterator, Mapping, Sequence
|
|
21
21
|
from collections.abc import Set as AbstractSet
|
|
22
|
-
from dataclasses import dataclass
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
23
|
from typing import Any, Literal
|
|
24
24
|
|
|
25
25
|
__all__ = [
|
|
@@ -171,14 +171,33 @@ class PO(WorkwireObject):
|
|
|
171
171
|
created_at: str
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
class JobPathLocation(WorkwireObject):
|
|
175
|
+
"""One ancestor on a Job's ``location_path`` (a :class:`PartyJob`'s or a
|
|
176
|
+
:class:`JobRow`'s)."""
|
|
177
|
+
|
|
178
|
+
id: str
|
|
179
|
+
name: str
|
|
180
|
+
kind: str
|
|
181
|
+
|
|
182
|
+
|
|
174
183
|
class PartyJob(WorkwireObject):
|
|
175
184
|
"""One Job on a wire party (``JOB-…``) — the party's work at one location
|
|
176
|
-
of its scope. ``
|
|
177
|
-
|
|
185
|
+
of its scope. ``location_name``, ``location_kind`` and ``location_path``
|
|
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``,
|
|
188
|
+
``quantity`` and ``checked_at`` are **your org's own** fields, never the
|
|
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."""
|
|
191
|
+
|
|
192
|
+
_nested = {"location_path": JobPathLocation}
|
|
178
193
|
|
|
179
194
|
id: str
|
|
180
195
|
display_id: str
|
|
181
196
|
location_id: str
|
|
197
|
+
location_name: str
|
|
198
|
+
location_kind: str
|
|
199
|
+
location_unit_type: str | None
|
|
200
|
+
location_path: list[JobPathLocation]
|
|
182
201
|
your_reference: str | None
|
|
183
202
|
quantity: str | None
|
|
184
203
|
checked_at: str | None
|
|
@@ -296,13 +315,16 @@ class PartnershipListItem(WorkwireObject):
|
|
|
296
315
|
|
|
297
316
|
class Location(WorkwireObject):
|
|
298
317
|
"""One location of a project's tree. ``path`` is the ancestor ids, root
|
|
299
|
-
first (empty on the root, whose ``parent_id`` is ``None``).
|
|
318
|
+
first (empty on the root, whose ``parent_id`` is ``None``). ``unit_type``
|
|
319
|
+
is the label a builder gives a unit's plan — ``None`` when unset; always
|
|
320
|
+
``None`` on a split."""
|
|
300
321
|
|
|
301
322
|
id: str
|
|
302
323
|
project_id: str
|
|
303
324
|
parent_id: str | None
|
|
304
325
|
kind: str
|
|
305
326
|
name: str
|
|
327
|
+
unit_type: str | None
|
|
306
328
|
path: list[str]
|
|
307
329
|
archived_at: str | None
|
|
308
330
|
created_at: str
|
|
@@ -334,14 +356,6 @@ class LocationBatch(list[Location]):
|
|
|
334
356
|
return copy.deepcopy(self._data)
|
|
335
357
|
|
|
336
358
|
|
|
337
|
-
class JobPathLocation(WorkwireObject):
|
|
338
|
-
"""One ancestor on a Job row's ``location_path``."""
|
|
339
|
-
|
|
340
|
-
id: str
|
|
341
|
-
name: str
|
|
342
|
-
kind: str
|
|
343
|
-
|
|
344
|
-
|
|
345
359
|
class JobMilestones(WorkwireObject):
|
|
346
360
|
"""A Job row's party milestone counts (repeated across the party's Jobs)."""
|
|
347
361
|
|
|
@@ -352,7 +366,9 @@ class JobMilestones(WorkwireObject):
|
|
|
352
366
|
class JobRow(WorkwireObject):
|
|
353
367
|
"""One row of :meth:`Jobs.list`: the Job with its party, wire, project,
|
|
354
368
|
location and counterparty context. ``your_reference``, ``quantity`` and
|
|
355
|
-
``checked_at`` are your org's own fields, never the other side's.
|
|
369
|
+
``checked_at`` are your org's own fields, never the other side's.
|
|
370
|
+
``location_unit_type`` is the location's ``unit_type`` — the label a
|
|
371
|
+
builder gives a unit's plan, ``None`` when unset."""
|
|
356
372
|
|
|
357
373
|
_nested = {"location_path": JobPathLocation, "milestones": JobMilestones}
|
|
358
374
|
|
|
@@ -371,6 +387,7 @@ class JobRow(WorkwireObject):
|
|
|
371
387
|
location_id: str
|
|
372
388
|
location_name: str
|
|
373
389
|
location_kind: str
|
|
390
|
+
location_unit_type: str | None
|
|
374
391
|
location_path: list[JobPathLocation]
|
|
375
392
|
counterparty_org_id: str | None
|
|
376
393
|
counterparty_name: str | None
|
|
@@ -444,13 +461,20 @@ class ColdInvite:
|
|
|
444
461
|
|
|
445
462
|
@dataclass(frozen=True)
|
|
446
463
|
class LocationNode:
|
|
447
|
-
"""One location of a tree being created — its ``kind``, its ``name``,
|
|
448
|
-
|
|
449
|
-
|
|
464
|
+
"""One location of a tree being created — its ``kind``, its ``name``, its
|
|
465
|
+
optional ``unit_type`` (the label a builder gives a unit's plan, "C1.1
|
|
466
|
+
(2 Bedroom)"), and the ``children`` nested beneath it. A tree only
|
|
467
|
+
descends — each kind ranks strictly below its parent's, ``site`` ›
|
|
468
|
+
``building``/``house`` › ``floor`` › ``unit``/``common_area`` › ``room``,
|
|
469
|
+
levels skippable but never repeated or reversed, else
|
|
470
|
+
``InvalidRequestError`` code ``invalid_parent`` with
|
|
471
|
+
``details["reason"] == "kind_rank"``. At most 500 locations per request,
|
|
450
472
|
counted over every level."""
|
|
451
473
|
|
|
452
474
|
kind: LocationKind
|
|
453
475
|
name: str
|
|
476
|
+
# Keyword-only, so a positional third argument is still ``children``.
|
|
477
|
+
unit_type: str | None = field(default=None, kw_only=True)
|
|
454
478
|
children: Sequence[LocationNode] | None = None
|
|
455
479
|
|
|
456
480
|
def __post_init__(self) -> None:
|
|
@@ -466,6 +490,8 @@ class LocationNode:
|
|
|
466
490
|
|
|
467
491
|
def to_payload(self) -> dict[str, Any]:
|
|
468
492
|
payload: dict[str, Any] = {"kind": self.kind, "name": self.name}
|
|
493
|
+
if self.unit_type is not None: # absent, never null: the bytes stay as before
|
|
494
|
+
payload["unit_type"] = self.unit_type
|
|
469
495
|
if self.children is not None:
|
|
470
496
|
payload["children"] = [child.to_payload() for child in self.children]
|
|
471
497
|
return payload
|
|
@@ -515,9 +541,7 @@ class Party:
|
|
|
515
541
|
|
|
516
542
|
def __init__(self, payload: dict[str, Any], *, _guard: object = None) -> None:
|
|
517
543
|
if _guard is not _PARTY_GUARD:
|
|
518
|
-
raise TypeError(
|
|
519
|
-
"Construct with Party.partnered(...) or Party.cold_invite(...)"
|
|
520
|
-
)
|
|
544
|
+
raise TypeError("Construct with Party.partnered(...) or Party.cold_invite(...)")
|
|
521
545
|
self._payload = payload
|
|
522
546
|
|
|
523
547
|
@classmethod
|
|
@@ -43,11 +43,11 @@ def _quantity(value: str | Decimal | None) -> str | None:
|
|
|
43
43
|
return format(value, "f")
|
|
44
44
|
if isinstance(value, float):
|
|
45
45
|
raise TypeError(
|
|
46
|
-
|
|
46
|
+
'quantity must be a decimal string ("12.5") or a decimal.Decimal, '
|
|
47
47
|
"never a float: a binary float is not an exact quantity"
|
|
48
48
|
)
|
|
49
49
|
raise TypeError(
|
|
50
|
-
|
|
50
|
+
'quantity must be a decimal string ("12.5") or a decimal.Decimal, '
|
|
51
51
|
f"not {type(value).__name__} — pass str(n) or Decimal(n)"
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
Every project has one ``site`` root, created with the project
|
|
5
5
|
(``Project.root_location_id``), and beneath it houses, buildings, floors,
|
|
6
|
-
units, rooms and common areas,
|
|
6
|
+
units, rooms and common areas, each kind ranked below its parent's (see
|
|
7
|
+
:class:`~workwire.LocationNode`). Locations have stable ids —
|
|
7
8
|
nothing references one by name or path. Seed a tree when you create the
|
|
8
9
|
project (``projects.create(locations=...)``) or add to it here; renames,
|
|
9
10
|
moves and archives happen in the Workwire app.
|
|
@@ -76,6 +77,7 @@ class ProjectLocations:
|
|
|
76
77
|
*,
|
|
77
78
|
kind: LocationKind,
|
|
78
79
|
name: str,
|
|
80
|
+
unit_type: str | None = None,
|
|
79
81
|
parent_id: str | None = None,
|
|
80
82
|
idempotency_key: str | None = None,
|
|
81
83
|
timeout: float | None = None,
|
|
@@ -83,13 +85,22 @@ class ProjectLocations:
|
|
|
83
85
|
"""Add one location beneath ``parent_id`` — absent, the project's
|
|
84
86
|
root. Requires ``projects:write``.
|
|
85
87
|
|
|
88
|
+
``unit_type`` is the label a builder gives a unit's plan ("C1.1
|
|
89
|
+
(2 Bedroom)"); left ``None``, it is not sent.
|
|
90
|
+
|
|
86
91
|
A parent that is not an unarchived location of this project's tree
|
|
87
|
-
raises ``InvalidRequestError`` (code ``invalid_parent``)
|
|
88
|
-
``kind``
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
raises ``InvalidRequestError`` (code ``invalid_parent``), and so does
|
|
93
|
+
a ``kind`` that does not rank below the parent's (``site`` ›
|
|
94
|
+
``building``/``house`` › ``floor`` › ``unit``/``common_area`` ›
|
|
95
|
+
``room``) — that refusal's ``details`` are ``{"reason": "kind_rank",
|
|
96
|
+
"child_kind": ..., "parent_kind": ...}``. An unknown ``kind`` is
|
|
97
|
+
checked with the body here — ``InvalidRequestError`` code
|
|
98
|
+
``validation_error``, ``fields["kind"]``. An archived project's tree
|
|
99
|
+
is read-only (``ConflictError``, code ``project_archived``).
|
|
91
100
|
"""
|
|
92
101
|
body: dict[str, Any] = {"kind": kind, "name": name}
|
|
102
|
+
if unit_type is not None:
|
|
103
|
+
body["unit_type"] = unit_type
|
|
93
104
|
if parent_id is not None:
|
|
94
105
|
body["parent_id"] = parent_id
|
|
95
106
|
data, request_id = self._client._request(
|
|
@@ -117,7 +128,10 @@ class ProjectLocations:
|
|
|
117
128
|
At most 500 locations, counted over every level: more raises
|
|
118
129
|
``InvalidRequestError`` (code ``too_many_locations``, with
|
|
119
130
|
``details["limit"]``), as does an unknown ``kind``
|
|
120
|
-
(``invalid_location_kind``)
|
|
131
|
+
(``invalid_location_kind``) and a node whose kind does not rank below
|
|
132
|
+
its own parent's (``invalid_parent``, with ``details`` ``{"reason":
|
|
133
|
+
"kind_rank", "child_kind": ..., "parent_kind": ...}``); a refused
|
|
134
|
+
node creates nothing. Returns
|
|
121
135
|
a :class:`LocationBatch` — a plain list of the created locations,
|
|
122
136
|
flat, every parent before its children, so you have every id; each
|
|
123
137
|
carries the response's ``request_id``.
|
|
@@ -45,10 +45,13 @@ class Projects:
|
|
|
45
45
|
(``root_location_id``). ``locations`` seeds a tree beneath the root
|
|
46
46
|
in the same transaction — at most 500 locations counted over every
|
|
47
47
|
level (``too_many_locations``), each of a known kind
|
|
48
|
-
(``invalid_location_kind``)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
(``invalid_location_kind``) ranked below its parent's
|
|
49
|
+
(``invalid_parent``, with ``details`` ``{"reason": "kind_rank",
|
|
50
|
+
"child_kind": ..., "parent_kind": ...}`` — see :class:`LocationNode`);
|
|
51
|
+
a refused node creates nothing, the project included. ``template``
|
|
52
|
+
(``development``, ``single_home``, ``renovation``) records the Project
|
|
53
|
+
Type your tree follows in the project's history only — the server
|
|
54
|
+
seeds nothing from it.
|
|
52
55
|
"""
|
|
53
56
|
body: dict[str, Any] = {"name": name}
|
|
54
57
|
if address is not None:
|
|
@@ -46,8 +46,7 @@ class Wires:
|
|
|
46
46
|
for party in parties:
|
|
47
47
|
if not isinstance(party, Party):
|
|
48
48
|
raise TypeError(
|
|
49
|
-
"parties must be built with Party.partnered(...) or "
|
|
50
|
-
"Party.cold_invite(...)"
|
|
49
|
+
"parties must be built with Party.partnered(...) or Party.cold_invite(...)"
|
|
51
50
|
)
|
|
52
51
|
body: dict[str, Any] = {
|
|
53
52
|
"project_id": project_id,
|
|
@@ -70,9 +69,7 @@ class Wires:
|
|
|
70
69
|
def retrieve(self, wire_id: str, *, timeout: float | None = None) -> Wire:
|
|
71
70
|
"""Fetch one wire with embedded parties, milestones, and POs.
|
|
72
71
|
Requires ``wires:read``."""
|
|
73
|
-
data, request_id = self._client._request(
|
|
74
|
-
"GET", f"/v1/wires/{wire_id}", timeout=timeout
|
|
75
|
-
)
|
|
72
|
+
data, request_id = self._client._request("GET", f"/v1/wires/{wire_id}", timeout=timeout)
|
|
76
73
|
return Wire(data, request_id=request_id)
|
|
77
74
|
|
|
78
75
|
def go_live(
|
|
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
|