targetprocess-py 0.1.0__py3-none-any.whl

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.
Files changed (58) hide show
  1. targetprocess/__init__.py +231 -0
  2. targetprocess/_assignables.py +162 -0
  3. targetprocess/_base.py +318 -0
  4. targetprocess/_content.py +287 -0
  5. targetprocess/_dates.py +70 -0
  6. targetprocess/_entity_types.py +33 -0
  7. targetprocess/_generals.py +324 -0
  8. targetprocess/_joins.py +217 -0
  9. targetprocess/_lookups.py +477 -0
  10. targetprocess/_nested.py +223 -0
  11. targetprocess/_observability.py +368 -0
  12. targetprocess/client.py +430 -0
  13. targetprocess/exceptions.py +147 -0
  14. targetprocess/models.py +121 -0
  15. targetprocess/py.typed +1 -0
  16. targetprocess/request_handler.py +724 -0
  17. targetprocess/resources/__init__.py +76 -0
  18. targetprocess/resources/assignments.py +29 -0
  19. targetprocess/resources/attachments.py +164 -0
  20. targetprocess/resources/base.py +563 -0
  21. targetprocess/resources/bugs.py +20 -0
  22. targetprocess/resources/comments.py +23 -0
  23. targetprocess/resources/custom_activities.py +56 -0
  24. targetprocess/resources/custom_fields.py +27 -0
  25. targetprocess/resources/custom_rules.py +28 -0
  26. targetprocess/resources/entities.py +433 -0
  27. targetprocess/resources/entity_states.py +20 -0
  28. targetprocess/resources/entity_types.py +61 -0
  29. targetprocess/resources/epics.py +20 -0
  30. targetprocess/resources/features.py +20 -0
  31. targetprocess/resources/iterations.py +20 -0
  32. targetprocess/resources/priorities.py +85 -0
  33. targetprocess/resources/processes.py +65 -0
  34. targetprocess/resources/projects.py +20 -0
  35. targetprocess/resources/relation_types.py +59 -0
  36. targetprocess/resources/relations.py +34 -0
  37. targetprocess/resources/releases.py +20 -0
  38. targetprocess/resources/requests.py +20 -0
  39. targetprocess/resources/role_efforts.py +21 -0
  40. targetprocess/resources/roles.py +52 -0
  41. targetprocess/resources/severities.py +53 -0
  42. targetprocess/resources/tasks.py +20 -0
  43. targetprocess/resources/team_assignments.py +21 -0
  44. targetprocess/resources/team_iterations.py +24 -0
  45. targetprocess/resources/teams.py +20 -0
  46. targetprocess/resources/terms.py +28 -0
  47. targetprocess/resources/test_cases.py +20 -0
  48. targetprocess/resources/times.py +392 -0
  49. targetprocess/resources/user_stories.py +20 -0
  50. targetprocess/resources/users.py +20 -0
  51. targetprocess/resources/workflows.py +27 -0
  52. targetprocess/response_parser.py +113 -0
  53. targetprocess/transport.py +116 -0
  54. targetprocess/types.py +56 -0
  55. targetprocess_py-0.1.0.dist-info/METADATA +476 -0
  56. targetprocess_py-0.1.0.dist-info/RECORD +58 -0
  57. targetprocess_py-0.1.0.dist-info/WHEEL +4 -0
  58. targetprocess_py-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,33 @@
1
+ """Entity type name validation, shared by every layer that interpolates one."""
2
+
3
+ import re
4
+
5
+ _ENTITY_TYPE_RE = re.compile(r"[A-Za-z][A-Za-z0-9_]*")
6
+
7
+
8
+ def require_entity_type(entity_type: str) -> None:
9
+ """Refuse an entity type name that is not a plain TP identifier.
10
+
11
+ An entity type name is interpolated straight into a request path (and,
12
+ in ``priorities.for_entity_type``, into a ``where=`` filter). On the
13
+ generic ``entities`` resource it is caller-supplied at runtime - often
14
+ echoing a server-returned ``ResourceType`` - so anything beyond a bare
15
+ identifier changes what the request does: ``"UserStories/57731"`` turns
16
+ a create into an update of that entity, and a trailing slash or stray
17
+ whitespace slips past a name-keyed guard. TP collection names are plain
18
+ identifiers, so that is all that is accepted - the same stance as
19
+ ``RequestHandler._require_instance_path``: enforced, not merely
20
+ documented.
21
+
22
+ Args:
23
+ entity_type: The entity type name to check
24
+
25
+ Raises:
26
+ ValueError: ``entity_type`` is not letters, digits and underscores
27
+ starting with a letter.
28
+ """
29
+ if not _ENTITY_TYPE_RE.fullmatch(entity_type):
30
+ raise ValueError(
31
+ "entity_type must be a TP entity type name (letters, digits, "
32
+ f"underscore): {entity_type!r}"
33
+ )
@@ -0,0 +1,324 @@
1
+ """The ``General`` types that are not work items.
2
+
3
+ ``Project``, ``Team``, ``Release``, ``Iteration``, ``TeamIteration`` and
4
+ ``TestCase`` derive from TP's ``General`` base but not from ``Assignable``, so
5
+ they inherit :class:`targetprocess._base.GeneralEntity` and declare their own
6
+ effort and planning fields rather than the assignable set.
7
+
8
+ Several carry ``Effort``/``EffortCompleted``/``EffortToDo``/``Progress``/
9
+ ``Units`` as roll-ups of the work items inside them, identically. They are
10
+ repeated per class rather than hoisted into a shared mixin because TP declares
11
+ no such base: the grouping would be this library's invention, and the next type
12
+ to gain or lose one of the five would have to be pulled back out of it. The
13
+ repetition is the cost of each class listing exactly what its own ``/meta``
14
+ declares, which is the property ``scripts/check_model_coverage.py`` checks.
15
+
16
+ Re-exported by :mod:`targetprocess.models`, which stays the import surface
17
+ callers use.
18
+ """
19
+
20
+ from pydantic import Field
21
+
22
+ from targetprocess._base import GeneralEntity
23
+ from targetprocess._dates import TPDateTime
24
+ from targetprocess._nested import AssignedUsers, EntityRef, RefWithImportance
25
+
26
+
27
+ class Project(GeneralEntity):
28
+ """Project entity for organisational structure.
29
+
30
+ The container every other entity hangs off. Carries its own workflow state
31
+ and process, the effort roll-up of the work inside it, and the branding
32
+ fields TP renders in the project picker.
33
+
34
+ Attributes:
35
+ effort: Rolled-up effort
36
+ effort_completed: Rolled-up completed effort
37
+ effort_todo: Rolled-up remaining effort
38
+ progress: Completion ratio derived from effort (0.0 - 1.0)
39
+ units: The unit effort is expressed in (e.g. "h", "pt")
40
+ is_active: Whether the project is active
41
+ is_product: Whether the project is a product rather than a project
42
+ is_private: Whether the project is hidden from non-members
43
+ abbreviation: Short code TP renders in compact views
44
+ mail_reply_address: Address inbound request email is accepted on
45
+ color: Project colour TP renders in the UI
46
+ planned_start_date: Planned start date/time
47
+ planned_end_date: Planned end date/time
48
+ last_state_change_date: When the entity state last changed
49
+ forecast_end_date: TP's projected completion date
50
+ anticipated_end_date: Manually anticipated completion date
51
+ lead_time: Days from creation to completion
52
+ cycle_time: Days from work starting to completion
53
+ entity_state: Current workflow state reference
54
+ process: Process (workflow definition) reference
55
+ program: Program reference
56
+ company: Company reference
57
+ """
58
+
59
+ # Effort roll-up
60
+ effort: float | None = Field(default=None, alias="Effort", description="Rolled-up effort")
61
+ effort_completed: float | None = Field(
62
+ default=None, alias="EffortCompleted", description="Completed effort"
63
+ )
64
+ effort_todo: float | None = Field(
65
+ default=None, alias="EffortToDo", description="Remaining effort"
66
+ )
67
+ progress: float | None = Field(default=None, alias="Progress", description="Completion ratio")
68
+ units: str | None = Field(default=None, alias="Units", description="Effort unit")
69
+
70
+ # Flags and branding
71
+ is_active: bool | None = Field(default=None, alias="IsActive", description="Active status")
72
+ is_product: bool | None = Field(default=None, alias="IsProduct", description="Is a product")
73
+ is_private: bool | None = Field(default=None, alias="IsPrivate", description="Is private")
74
+ abbreviation: str | None = Field(
75
+ default=None, alias="Abbreviation", description="Short project code"
76
+ )
77
+ mail_reply_address: str | None = Field(
78
+ default=None, alias="MailReplyAddress", description="Inbound mail address"
79
+ )
80
+ color: str | None = Field(default=None, alias="Color", description="Display colour")
81
+
82
+ # Date tracking
83
+ planned_start_date: TPDateTime | None = Field(
84
+ default=None, alias="PlannedStartDate", description="Planned start"
85
+ )
86
+ planned_end_date: TPDateTime | None = Field(
87
+ default=None, alias="PlannedEndDate", description="Planned end"
88
+ )
89
+ last_state_change_date: TPDateTime | None = Field(
90
+ default=None, alias="LastStateChangeDate", description="Last state change"
91
+ )
92
+ forecast_end_date: TPDateTime | None = Field(
93
+ default=None, alias="ForecastEndDate", description="Forecast end"
94
+ )
95
+ anticipated_end_date: TPDateTime | None = Field(
96
+ default=None, alias="AnticipatedEndDate", description="Anticipated end"
97
+ )
98
+
99
+ # Flow metrics
100
+ lead_time: float | None = Field(default=None, alias="LeadTime", description="Lead time (days)")
101
+ cycle_time: float | None = Field(
102
+ default=None, alias="CycleTime", description="Cycle time (days)"
103
+ )
104
+
105
+ # Relationships
106
+ entity_state: EntityRef | None = Field(
107
+ default=None, alias="EntityState", description="Workflow state"
108
+ )
109
+ process: EntityRef | None = Field(default=None, alias="Process", description="Process")
110
+ program: EntityRef | None = Field(default=None, alias="Program", description="Program")
111
+ company: EntityRef | None = Field(default=None, alias="Company", description="Company")
112
+
113
+
114
+ class Team(GeneralEntity):
115
+ """Team entity for organisational structure.
116
+
117
+ A bare ``General`` upstream - TP adds only the activity flag and the
118
+ identity TP renders in team pickers.
119
+
120
+ Attributes:
121
+ is_active: Whether the team is active
122
+ abbreviation: Short code TP renders in compact views
123
+ emoji_icon: Emoji shorthand TP renders beside the team name
124
+ icon_uri: Path to the team's icon image
125
+ icon: Legacy icon field (deprecated upstream; prefer icon_uri)
126
+ """
127
+
128
+ is_active: bool | None = Field(default=None, alias="IsActive", description="Active status")
129
+ abbreviation: str | None = Field(
130
+ default=None, alias="Abbreviation", description="Short team code"
131
+ )
132
+ emoji_icon: str | None = Field(default=None, alias="EmojiIcon", description="Emoji icon")
133
+ icon_uri: str | None = Field(default=None, alias="IconUri", description="Icon image path")
134
+ icon: str | None = Field(
135
+ default=None, alias="Icon", description="Legacy icon (deprecated upstream)"
136
+ )
137
+
138
+
139
+ class Release(GeneralEntity):
140
+ """Release entity for planning and tracking releases.
141
+
142
+ A dated window grouping iterations, with the effort roll-up of everything
143
+ scheduled into it. ``start_date`` and ``end_date`` are required upstream.
144
+
145
+ Attributes:
146
+ effort: Rolled-up effort
147
+ effort_completed: Rolled-up completed effort
148
+ effort_todo: Rolled-up remaining effort
149
+ progress: Completion ratio derived from effort (0.0 - 1.0)
150
+ units: The unit effort is expressed in (e.g. "h", "pt")
151
+ is_current: Whether this is the current release
152
+ forecast_end_date: TP's projected completion date
153
+ process: Process (workflow definition) reference
154
+ """
155
+
156
+ effort: float | None = Field(default=None, alias="Effort", description="Rolled-up effort")
157
+ effort_completed: float | None = Field(
158
+ default=None, alias="EffortCompleted", description="Completed effort"
159
+ )
160
+ effort_todo: float | None = Field(
161
+ default=None, alias="EffortToDo", description="Remaining effort"
162
+ )
163
+ progress: float | None = Field(default=None, alias="Progress", description="Completion ratio")
164
+ units: str | None = Field(default=None, alias="Units", description="Effort unit")
165
+ is_current: bool | None = Field(
166
+ default=None, alias="IsCurrent", description="Is the current release"
167
+ )
168
+ forecast_end_date: TPDateTime | None = Field(
169
+ default=None, alias="ForecastEndDate", description="Forecast end"
170
+ )
171
+ process: EntityRef | None = Field(default=None, alias="Process", description="Process")
172
+
173
+
174
+ class Iteration(GeneralEntity):
175
+ """Iteration entity for agile sprints.
176
+
177
+ A project-level sprint inside a ``Release``. TP scopes iterations to the
178
+ release, not to a team - the team-scoped equivalent is
179
+ :class:`TeamIteration`.
180
+
181
+ Attributes:
182
+ effort: Rolled-up effort
183
+ effort_completed: Rolled-up completed effort
184
+ effort_todo: Rolled-up remaining effort
185
+ progress: Completion ratio derived from effort (0.0 - 1.0)
186
+ units: The unit effort is expressed in (e.g. "h", "pt")
187
+ velocity: Effort completed per unit time
188
+ duration: Iteration length in days
189
+ is_current: Whether this is the current iteration
190
+ can_be_finished: Whether TP will allow the iteration to be closed
191
+ forecast_end_date: TP's projected completion date
192
+ release: Release reference
193
+ """
194
+
195
+ effort: float | None = Field(default=None, alias="Effort", description="Rolled-up effort")
196
+ effort_completed: float | None = Field(
197
+ default=None, alias="EffortCompleted", description="Completed effort"
198
+ )
199
+ effort_todo: float | None = Field(
200
+ default=None, alias="EffortToDo", description="Remaining effort"
201
+ )
202
+ progress: float | None = Field(default=None, alias="Progress", description="Completion ratio")
203
+ units: str | None = Field(default=None, alias="Units", description="Effort unit")
204
+ velocity: float | None = Field(default=None, alias="Velocity", description="Velocity")
205
+ duration: int | None = Field(default=None, alias="Duration", description="Length in days")
206
+ is_current: bool | None = Field(
207
+ default=None, alias="IsCurrent", description="Is the current iteration"
208
+ )
209
+ can_be_finished: bool | None = Field(
210
+ default=None, alias="CanBeFinished", description="Can be closed"
211
+ )
212
+ forecast_end_date: TPDateTime | None = Field(
213
+ default=None, alias="ForecastEndDate", description="Forecast end"
214
+ )
215
+ release: EntityRef | None = Field(default=None, alias="Release", description="Release")
216
+
217
+ # Not declared in TP's /meta for Iteration and rejected by include= there;
218
+ # kept because removing a published field would break callers. The
219
+ # team-scoped sprint is TeamIteration.
220
+ team: EntityRef | None = Field(
221
+ default=None, alias="Team", description="Team (absent upstream; prefer TeamIteration)"
222
+ )
223
+
224
+
225
+ class TeamIteration(GeneralEntity):
226
+ """Team-specific iteration (sprint) entity.
227
+
228
+ In TP each team owns its own iterations; a TeamIteration carries the
229
+ team's sprint window, effort/velocity/capacity, and its Release.
230
+
231
+ Attributes:
232
+ effort: Rolled-up effort
233
+ effort_completed: Rolled-up completed effort
234
+ effort_todo: Rolled-up remaining effort
235
+ progress: Completion ratio derived from effort (0.0 - 1.0)
236
+ units: The unit effort is expressed in (e.g. "h", "pt")
237
+ velocity: Effort completed per unit time
238
+ capacity: Effort the team can absorb in the window
239
+ duration: Iteration length in days
240
+ is_current: Whether this is the team's current iteration
241
+ can_be_finished: Whether TP will allow the iteration to be closed
242
+ forecast_end_date: TP's projected completion date
243
+ team: Team reference
244
+ release: Release reference
245
+ """
246
+
247
+ effort: float | None = Field(default=None, alias="Effort", description="Rolled-up effort")
248
+ effort_completed: float | None = Field(
249
+ default=None, alias="EffortCompleted", description="Completed effort"
250
+ )
251
+ effort_todo: float | None = Field(
252
+ default=None, alias="EffortToDo", description="Remaining effort"
253
+ )
254
+ progress: float | None = Field(default=None, alias="Progress", description="Completion ratio")
255
+ units: str | None = Field(default=None, alias="Units", description="Effort unit")
256
+ velocity: float | None = Field(default=None, alias="Velocity", description="Team velocity")
257
+ capacity: float | None = Field(default=None, alias="Capacity", description="Team capacity")
258
+ duration: int | None = Field(default=None, alias="Duration", description="Length in days")
259
+ is_current: bool | None = Field(
260
+ default=None, alias="IsCurrent", description="Is the current iteration"
261
+ )
262
+ can_be_finished: bool | None = Field(
263
+ default=None, alias="CanBeFinished", description="Can be closed"
264
+ )
265
+ forecast_end_date: TPDateTime | None = Field(
266
+ default=None, alias="ForecastEndDate", description="Forecast end"
267
+ )
268
+ team: EntityRef | None = Field(default=None, alias="Team", description="Team")
269
+ release: EntityRef | None = Field(default=None, alias="Release", description="Release")
270
+
271
+
272
+ class TestCase(GeneralEntity):
273
+ """TestCase entity for quality assurance.
274
+
275
+ A ``General`` rather than an ``Assignable``: a test case has no effort, no
276
+ iteration and no workflow state - its status is the outcome of its last
277
+ run.
278
+
279
+ Attributes:
280
+ last_run_status: Outcome of the most recent run (e.g. "Passed")
281
+ last_run_date: When the test case was last run
282
+ last_failure_comment: Comment recorded against the last failure
283
+ last_status: Legacy boolean pass flag (deprecated; prefer last_run_status)
284
+ steps: Legacy free-text steps (deprecated upstream)
285
+ success: Legacy free-text expected result (deprecated upstream)
286
+ priority: Priority reference, scoped to this entity type
287
+ user_story: Parent user story reference (deprecated upstream)
288
+ """
289
+
290
+ last_run_status: str | None = Field(
291
+ default=None, alias="LastRunStatus", description="Last run outcome"
292
+ )
293
+ last_run_date: TPDateTime | None = Field(
294
+ default=None, alias="LastRunDate", description="Last run date/time"
295
+ )
296
+ last_failure_comment: str | None = Field(
297
+ default=None, alias="LastFailureComment", description="Last failure comment"
298
+ )
299
+ last_status: bool | None = Field(
300
+ default=None, alias="LastStatus", description="Legacy pass flag (deprecated upstream)"
301
+ )
302
+ steps: str | None = Field(
303
+ default=None, alias="Steps", description="Legacy steps (deprecated upstream)"
304
+ )
305
+ success: str | None = Field(
306
+ default=None, alias="Success", description="Legacy expected result (deprecated upstream)"
307
+ )
308
+ priority: RefWithImportance | None = Field(
309
+ default=None, alias="Priority", description="Priority"
310
+ )
311
+ user_story: EntityRef | None = Field(
312
+ default=None,
313
+ alias="UserStory",
314
+ description="Parent user story (deprecated upstream)",
315
+ )
316
+
317
+ # Neither is declared in TP's /meta for TestCase, and include= rejects both
318
+ # there; kept because removing a published field would break callers.
319
+ entity_state: EntityRef | None = Field(
320
+ default=None, alias="EntityState", description="Workflow state (absent upstream)"
321
+ )
322
+ assigned_user: AssignedUsers | None = Field(
323
+ default=None, alias="AssignedUser", description="Assigned users (absent upstream)"
324
+ )
@@ -0,0 +1,217 @@
1
+ """The join entities that connect two records rather than describing one.
2
+
3
+ ``Assignment``, ``TeamAssignment``, ``RoleEffort``, ``Relation`` and ``Time``
4
+ each pair an ``Assignable`` with a user, team, role or another entity. None
5
+ carries a ``Name``, and none sits in TP's ``General`` hierarchy, so all extend
6
+ :class:`targetprocess._base.Entity` directly.
7
+
8
+ Re-exported by :mod:`targetprocess.models`, which stays the import surface
9
+ callers use.
10
+ """
11
+
12
+ from pydantic import Field
13
+
14
+ from targetprocess._base import Entity
15
+ from targetprocess._dates import TPDateTime
16
+ from targetprocess._nested import EntityRef, UserRef
17
+
18
+
19
+ class Assignment(Entity):
20
+ """User-to-work-item role assignment (join entity).
21
+
22
+ Pairs a user (``GeneralUser``) with a ``Role`` on an ``Assignable`` -
23
+ the authoritative record of who is assigned to a work item and in which
24
+ capacity (``Owner`` only records who created it).
25
+
26
+ Attributes:
27
+ general_user: Assigned user reference
28
+ role: Role reference
29
+ assignable: Assignable (work item) reference
30
+ """
31
+
32
+ general_user: UserRef | None = Field(
33
+ default=None, alias="GeneralUser", description="Assigned user"
34
+ )
35
+ role: EntityRef | None = Field(default=None, alias="Role", description="Role")
36
+ assignable: EntityRef | None = Field(
37
+ default=None, alias="Assignable", description="Assignable (work item)"
38
+ )
39
+
40
+
41
+ class TeamAssignment(Entity):
42
+ """Team-to-work-item assignment (join entity).
43
+
44
+ Assigns a Team to an Assignable over a time window, carrying the
45
+ assignment's entity state.
46
+
47
+ Attributes:
48
+ start_date: Assignment start date/time
49
+ end_date: Assignment end date/time
50
+ team: Team reference
51
+ assignable: Assignable (work item) reference
52
+ entity_state: Workflow state reference
53
+ """
54
+
55
+ # Date tracking
56
+ start_date: TPDateTime | None = Field(
57
+ default=None, alias="StartDate", description="Start date/time"
58
+ )
59
+ end_date: TPDateTime | None = Field(default=None, alias="EndDate", description="End date/time")
60
+
61
+ # Relationships
62
+ team: EntityRef | None = Field(default=None, alias="Team", description="Team")
63
+ assignable: EntityRef | None = Field(
64
+ default=None, alias="Assignable", description="Assignable (work item)"
65
+ )
66
+ entity_state: EntityRef | None = Field(
67
+ default=None, alias="EntityState", description="Workflow state"
68
+ )
69
+
70
+
71
+ class RoleEffort(Entity):
72
+ """Per-role effort allocation on an Assignable (join entity).
73
+
74
+ Breaks an Assignable's effort down by Role.
75
+
76
+ Attributes:
77
+ initial_estimate: Initial effort estimate >= 0
78
+ effort: Total effort >= 0
79
+ effort_completed: Completed effort >= 0
80
+ effort_todo: Remaining effort >= 0
81
+ time_spent: Time spent >= 0
82
+ time_remain: Time remaining >= 0
83
+ assignable: Assignable (work item) reference
84
+ role: Role reference
85
+ """
86
+
87
+ # Effort / time tracking
88
+ initial_estimate: float | None = Field(
89
+ default=None, alias="InitialEstimate", ge=0, description="Initial estimate"
90
+ )
91
+ effort: float | None = Field(default=None, alias="Effort", ge=0, description="Total effort")
92
+ effort_completed: float | None = Field(
93
+ default=None, alias="EffortCompleted", ge=0, description="Completed effort"
94
+ )
95
+ effort_todo: float | None = Field(
96
+ default=None, alias="EffortToDo", ge=0, description="Remaining effort"
97
+ )
98
+ time_spent: float | None = Field(
99
+ default=None, alias="TimeSpent", ge=0, description="Time spent"
100
+ )
101
+ time_remain: float | None = Field(
102
+ default=None, alias="TimeRemain", ge=0, description="Time remaining"
103
+ )
104
+
105
+ # Relationships
106
+ assignable: EntityRef | None = Field(
107
+ default=None, alias="Assignable", description="Assignable (work item)"
108
+ )
109
+ role: EntityRef | None = Field(default=None, alias="Role", description="Role")
110
+
111
+
112
+ class Relation(Entity):
113
+ """Entity-to-entity relation (join entity).
114
+
115
+ Links two entities, typed by a ``RelationType`` (Dependency, Blocker,
116
+ Relation, Duplicate, ...). The direction carries meaning: the source is the
117
+ origin of the dependency - in a Blocker relation the source is the blocking
118
+ item and the target the blocked one.
119
+
120
+ TP exposes the same pair twice. ``Master``/``Slave`` are the original names
121
+ and are marked deprecated in ``/meta``; ``Inbound``/``Outbound`` are the
122
+ current ones. They are the same two records - ``Inbound`` mirrors
123
+ ``Master`` and ``Outbound`` mirrors ``Slave`` - so prefer the latter pair in
124
+ new code.
125
+
126
+ Attributes:
127
+ inbound: Source entity reference
128
+ outbound: Target entity reference
129
+ master: Source entity reference (deprecated upstream; prefer inbound)
130
+ slave: Target entity reference (deprecated upstream; prefer outbound)
131
+ relation_type: Relation-type reference
132
+ """
133
+
134
+ # Relationships
135
+ inbound: EntityRef | None = Field(default=None, alias="Inbound", description="Source entity")
136
+ outbound: EntityRef | None = Field(default=None, alias="Outbound", description="Target entity")
137
+ master: EntityRef | None = Field(
138
+ default=None, alias="Master", description="Source entity (deprecated upstream)"
139
+ )
140
+ slave: EntityRef | None = Field(
141
+ default=None, alias="Slave", description="Target entity (deprecated upstream)"
142
+ )
143
+ relation_type: EntityRef | None = Field(
144
+ default=None, alias="RelationType", description="Relation type"
145
+ )
146
+
147
+
148
+ class Time(Entity):
149
+ """Time entry logged against an Assignable (join entity).
150
+
151
+ Records spent and remaining hours for a User against an Assignable on a
152
+ given date.
153
+
154
+ ``assignable`` is the general reference; TP additionally exposes a narrower
155
+ back-reference per work-item type, of which exactly one is populated for
156
+ any given entry (a ``CustomActivity`` entry, for instance, has a null
157
+ ``assignable``). They are declared so an entry's origin is readable without
158
+ a second fetch.
159
+
160
+ Attributes:
161
+ spent: Hours spent >= 0
162
+ remain: Hours remaining >= 0
163
+ is_estimation: Entry records an estimate rather than actual time
164
+ date: Date/time the work is logged against
165
+ description: Free-text description of the work
166
+ assignable: Assignable (work item) reference
167
+ user: User the time is logged for
168
+ project: Project reference (TP derives it from the Assignable)
169
+ role: Role reference
170
+ user_story: User story the entry was logged against, if any
171
+ task: Task the entry was logged against, if any
172
+ bug: Bug the entry was logged against, if any
173
+ request: Request the entry was logged against, if any
174
+ test_plan: Test plan the entry was logged against, if any
175
+ test_plan_run: Test plan run the entry was logged against, if any
176
+ custom_activity: Custom activity the entry was logged against, if any
177
+ """
178
+
179
+ # Effort / time tracking
180
+ spent: float | None = Field(default=None, alias="Spent", ge=0, description="Hours spent")
181
+ remain: float | None = Field(default=None, alias="Remain", ge=0, description="Hours remaining")
182
+ is_estimation: bool | None = Field(
183
+ default=None, alias="IsEstimation", description="Entry is an estimation"
184
+ )
185
+
186
+ # Date tracking
187
+ date: TPDateTime | None = Field(default=None, alias="Date", description="Date/time logged")
188
+
189
+ # Content
190
+ description: str | None = Field(default=None, alias="Description", description="Description")
191
+
192
+ # Relationships
193
+ assignable: EntityRef | None = Field(
194
+ default=None, alias="Assignable", description="Assignable (work item)"
195
+ )
196
+ user: UserRef | None = Field(default=None, alias="User", description="User")
197
+ project: EntityRef | None = Field(default=None, alias="Project", description="Project")
198
+ role: EntityRef | None = Field(default=None, alias="Role", description="Role")
199
+
200
+ # Narrow back-references: at most one is populated per entry.
201
+ user_story: EntityRef | None = Field(
202
+ default=None, alias="UserStory", description="User story logged against"
203
+ )
204
+ task: EntityRef | None = Field(default=None, alias="Task", description="Task logged against")
205
+ bug: EntityRef | None = Field(default=None, alias="Bug", description="Bug logged against")
206
+ request: EntityRef | None = Field(
207
+ default=None, alias="Request", description="Request logged against"
208
+ )
209
+ test_plan: EntityRef | None = Field(
210
+ default=None, alias="TestPlan", description="Test plan logged against"
211
+ )
212
+ test_plan_run: EntityRef | None = Field(
213
+ default=None, alias="TestPlanRun", description="Test plan run logged against"
214
+ )
215
+ custom_activity: EntityRef | None = Field(
216
+ default=None, alias="CustomActivity", description="Custom activity logged against"
217
+ )