easyvista-python-client 0.4.3__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.
Files changed (77) hide show
  1. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/CHANGELOG.md +42 -1
  2. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/PKG-INFO +1 -1
  3. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/user_guide.rst +3 -2
  4. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/vendor-api-reference.md +10 -4
  5. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_async/client.py +107 -8
  6. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_sync/client.py +107 -8
  7. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_version.py +1 -1
  8. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/requests.py +68 -0
  9. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/workflow.py +8 -1
  10. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/pyproject.toml +1 -1
  11. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-asset-workflow/SKILL.md +1 -1
  12. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-client-setup/SKILL.md +1 -1
  13. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-directory/SKILL.md +1 -1
  14. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-document-workflow/SKILL.md +1 -1
  15. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-instance-discovery/SKILL.md +1 -1
  16. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-reporting-and-context/SKILL.md +1 -1
  17. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-search-syntax/SKILL.md +1 -1
  18. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-ticket-actions/SKILL.md +12 -6
  19. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/easyvista-ticket-workflow/SKILL.md +35 -1
  20. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/.gitignore +0 -0
  21. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/.pre-commit-config.yaml +0 -0
  22. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/.readthedocs.yaml +0 -0
  23. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/CONTRIBUTING.md +0 -0
  24. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/LICENSE +0 -0
  25. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/README.md +0 -0
  26. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/_static/.gitkeep +0 -0
  27. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/_templates/.gitkeep +0 -0
  28. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/api_reference.rst +0 -0
  29. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/conf.py +0 -0
  30. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/content.rst +0 -0
  31. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/development.rst +0 -0
  32. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/index.rst +0 -0
  33. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/installation.rst +0 -0
  34. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/publishing.rst +0 -0
  35. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/docs/sponsoring.rst +0 -0
  36. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/__init__.py +0 -0
  37. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_async/__init__.py +0 -0
  38. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_async/_concurrency.py +0 -0
  39. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_async/_transport.py +0 -0
  40. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_fields.py +0 -0
  41. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_html.py +0 -0
  42. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_sync/__init__.py +0 -0
  43. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_sync/_concurrency.py +0 -0
  44. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_sync/_transport.py +0 -0
  45. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/_transport.py +0 -0
  46. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/config.py +0 -0
  47. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/content/__init__.py +0 -0
  48. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/content/conversion.py +0 -0
  49. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/context.py +0 -0
  50. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/directory.py +0 -0
  51. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/discovery.py +0 -0
  52. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/exceptions.py +0 -0
  53. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/field_model.py +0 -0
  54. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/filters.py +0 -0
  55. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/__init__.py +0 -0
  56. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/action.py +0 -0
  57. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/asset.py +0 -0
  58. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/common.py +0 -0
  59. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/department.py +0 -0
  60. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/document.py +0 -0
  61. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/employee.py +0 -0
  62. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/generic.py +0 -0
  63. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/models/request.py +0 -0
  64. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/pagination.py +0 -0
  65. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/py.typed +0 -0
  66. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/references.py +0 -0
  67. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/reporting.py +0 -0
  68. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/__init__.py +0 -0
  69. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/actions.py +0 -0
  70. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/assets.py +0 -0
  71. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/departments.py +0 -0
  72. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/descriptor.py +0 -0
  73. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/discovery.py +0 -0
  74. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/documents.py +0 -0
  75. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/resources/employees.py +0 -0
  76. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/easyvista_python_client/timestamps.py +0 -0
  77. {easyvista_python_client-0.4.3 → easyvista_python_client-0.5.0}/skills/README.md +0 -0
@@ -15,6 +15,46 @@ is the error. Tags carry no `v` prefix.
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.5.0] - 2026-11-10
19
+
20
+ Two more of the vendor's ticket requests, and a guard that names one more
21
+ column. Neither new method takes a status: the package still has no status
22
+ setter.
23
+
24
+ ### Added
25
+
26
+ - **`suspend_ticket(rfc_number, *, allow_workflow_effect, comment=None,
27
+ done_by_id=None)`** and **`restart_ticket(...)`** (same signature), the
28
+ vendor's suspend and restart requests
29
+ ([suspend](https://docs.easyvista.com/docs/rest-api-suspend-an-incident-request.md),
30
+ [restart](https://docs.easyvista.com/docs/rest-api-reopen-an-incident-request.md)):
31
+ `PUT requests/{rfc_number}` with a `suspended` / `restarted` object. The guard
32
+ already named both bodies `WorkflowEffect.UNKNOWN`, so
33
+ `allow_workflow_effect` is a required keyword and the call is refused before
34
+ any request unless it includes `UNKNOWN`; an allowed request is sent once,
35
+ never retried. `comment` and `done_by_id` are left out of the body when
36
+ `None`. The return value is parsed as `close_ticket`'s is.
37
+
38
+ ### Changed
39
+
40
+ - **BREAKING:** the workflow guard now names `STATUS_ID_ON_CREATE` on an
41
+ existing action (`update_action`, or `PUT actions/{id}` through `send`), as
42
+ it does `STATUS_ID_ON_TERMINATE`. A write that passed before is refused
43
+ without `allow_workflow_effect=WorkflowEffect.UNKNOWN`. Measured 2026-11-09
44
+ and 2026-11-10 on one instance (tier 4, may not generalise): a write to it
45
+ on an existing action changed where the ticket's status went. Creating an
46
+ action or a task is unchanged: the server overwrites the column on create.
47
+
48
+ ### Fixed
49
+
50
+ - **The `reassign_action` documentation about `OWNING_GROUP_ID`.** It said the
51
+ ticket's owning group does not follow a reassigned step. Measured 2026-11-04
52
+ to 2026-11-10 on one instance (several tickets, may not generalise): it
53
+ follows after a few seconds to about two minutes (consistent with a
54
+ server-side job; inferred, not observed), and `LAST_UPDATE` moves with it;
55
+ the first reading was taken too soon. The status still does not move, the
56
+ step is not started and no action row is added.
57
+
18
58
  ## [0.4.3] - 2026-10-08
19
59
 
20
60
  A patch release of the content reader: a caller can recognise the images a
@@ -1690,7 +1730,8 @@ Initial public release.
1690
1730
  status/error code, with non-retryable validation errors (HTTP 590, code 2013).
1691
1731
  - `py.typed` marker — the package ships inline type information.
1692
1732
 
1693
- [Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.3...HEAD
1733
+ [Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.5.0...HEAD
1734
+ [0.5.0]: https://github.com/baraline/easyvista_python_client/compare/0.4.3...0.5.0
1694
1735
  [0.4.3]: https://github.com/baraline/easyvista_python_client/compare/0.4.2...0.4.3
1695
1736
  [0.4.2]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...0.4.2
1696
1737
  [0.4.1]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...0.4.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: easyvista-python-client
3
- Version: 0.4.3
3
+ Version: 0.5.0
4
4
  Summary: Typed Python client for the EasyVista Service Manager REST API
5
5
  Project-URL: Homepage, https://github.com/baraline/easyvista_python_client
6
6
  Project-URL: Documentation, https://easyvista-python-client.readthedocs.io/en/latest/
@@ -473,8 +473,9 @@ rule on your instance can run on any write.
473
473
  To escalate the open workflow step to another group without ending it, use
474
474
  :meth:`~easyvista_python_client.EasyvistaClient.reassign_action` (measured
475
475
  2026-10-02, one instance, so it may not generalise: the step stays open and the
476
- status does not move; the ticket's owning group, read on one ticket, did not
477
- follow).
476
+ status does not move; measured 2026-11-04 to 2026-11-10, several tickets, the
477
+ ticket's owning group follows the step's new group after a few seconds to about
478
+ two minutes).
478
479
 
479
480
  Creating a ticket starts its workflow -- the vendor's create page lists "The
480
481
  workflow associated with the ticket is started." among what a create does (tier
@@ -287,10 +287,16 @@ again five seconds later); the step **stayed open** (`END_DATE_UT` empty); the t
287
287
  status and `END_DATE_UT` **did not move**; the open actions were unchanged; and **no new
288
288
  action rows** appeared. On the **first ticket only** the step was recorded as a type-20
289
289
  action with `WORKFLOW_ID` set, its `WORKFLOW_ID` was unchanged, `DONE_BY_ID` stayed
290
- empty and no ticket field changed; and that is also the only ticket on which the
291
- ticket's own `OWNING_GROUP_ID` was read — it **stayed at the old group**, so the
292
- ticket's owning group did not follow the action's. Reassigning to a person
293
- (`done_by_id`) was **not measured**. Whether the UI wizard's notifications fire is not
290
+ empty and no ticket field changed. The ticket's own `OWNING_GROUP_ID` was read on
291
+ that ticket only, and had not moved; that reading was taken too soon. Measured
292
+ 2026-11-04 to 2026-11-10 on one instance (several tickets, may not generalise): the
293
+ ticket's `OWNING_GROUP_ID` **follows the step's new group** after a delay of a few
294
+ seconds to about two minutes (consistent with a server-side job; inferred, not
295
+ observed), and `LAST_UPDATE` moves with it; the status still does not move, the step
296
+ is not started and no action row is added. Also measured 2026-11-04 on one ticket:
297
+ after `done_by_id` was written on the step, the ticket's `OWNER_ID` followed about
298
+ 1 min 45 s later. The person's effect on the step was not measured beyond that one
299
+ `OWNER_ID` observation. Whether the UI wizard's notifications fire is not
294
300
  observable from the API.
295
301
 
296
302
  **Business rules can fire on any write** — "On Insert/On Update" of any record
@@ -610,6 +610,99 @@ class AsyncEasyvistaClient:
610
610
  )
611
611
  return parse(await self._transport.send(spec.allowing(allow_workflow_effect)))
612
612
 
613
+ async def suspend_ticket(
614
+ self,
615
+ rfc_number: str,
616
+ *,
617
+ allow_workflow_effect: WorkflowEffect | Iterable[WorkflowEffect],
618
+ comment: str | None = None,
619
+ done_by_id: int | None = None,
620
+ ) -> Request:
621
+ """Suspend a ticket -- the vendor's suspend request.
622
+
623
+ Sends ``PUT requests/{rfc}`` with a ``suspended`` body, as the vendor
624
+ documents it:
625
+ https://docs.easyvista.com/docs/rest-api-suspend-an-incident-request.md
626
+ (tier 1, re-read 2026-11-10). The page documents two optional
627
+ parameters, ``comment`` and ``done_by_id`` (an employee id), and no
628
+ target status. Both are left out of the body when ``None``. A ticket
629
+ that is already suspended is refused by the server (400, "The request
630
+ is already suspended. Operation aborted.").
631
+
632
+ Measured 2026-11-04 to 2026-11-10 on one instance (tier 4, may not
633
+ generalise): the ticket moved to the instance's suspended status and
634
+ one already-ended suspension action carrying the comment was added;
635
+ the workflow step stayed open. A status key sent in the body was
636
+ ignored (measured 2026-11-04). The request takes no status, and the
637
+ package has no status setter.
638
+
639
+ ``allow_workflow_effect`` is **required**: nothing documents what a
640
+ suspend does to the workflow, so the write is named
641
+ ``WorkflowEffect.UNKNOWN`` and refused with
642
+ :class:`~easyvista_python_client.EasyvistaWorkflowEffectRefused`
643
+ before any request is made unless the call allows it. The allowed
644
+ request is sent once, never retried. The return value is parsed like
645
+ :meth:`close_ticket`'s: the vendor answers 201 with an ``HREF`` only, so
646
+ re-read with :meth:`get_ticket` to see the status::
647
+
648
+ client.suspend_ticket(
649
+ rfc,
650
+ allow_workflow_effect=WorkflowEffect.UNKNOWN,
651
+ comment="Waiting for the requester",
652
+ )
653
+ """
654
+ spec, parse = requests_res.build_suspend_ticket(
655
+ rfc_number,
656
+ comment=comment,
657
+ done_by_id=done_by_id,
658
+ context=self._validation_context,
659
+ )
660
+ return parse(await self._transport.send(spec.allowing(allow_workflow_effect)))
661
+
662
+ async def restart_ticket(
663
+ self,
664
+ rfc_number: str,
665
+ *,
666
+ allow_workflow_effect: WorkflowEffect | Iterable[WorkflowEffect],
667
+ comment: str | None = None,
668
+ done_by_id: int | None = None,
669
+ ) -> Request:
670
+ """Restart a suspended ticket -- the vendor's restart request.
671
+
672
+ Sends ``PUT requests/{rfc}`` with a ``restarted`` body, as the vendor
673
+ documents it:
674
+ https://docs.easyvista.com/docs/rest-api-reopen-an-incident-request.md
675
+ (tier 1, re-read 2026-11-10). The page documents two optional
676
+ parameters, ``comment`` and ``done_by_id`` (an employee id), and no
677
+ target status; its example marks ``comment`` as required while its
678
+ table lists it as optional, so this method leaves it out of the body
679
+ when ``None``. On a ticket that is not suspended the server answers
680
+ 400 with "The request is not suspended. Operation aborted.".
681
+
682
+ Measured 2026-11-04 to 2026-11-10 on one instance (tier 4, may not
683
+ generalise): the request added one end-of-suspension action and, in
684
+ the tested case, returned the ticket to the status it was suspended
685
+ from; a restart on a ticket that was not suspended was refused with
686
+ the documented message, as HTTP 590 rather than the documented 400.
687
+ A status key sent in the body was ignored
688
+ (measured 2026-11-04). The request takes no status, and the package
689
+ has no status setter.
690
+
691
+ ``allow_workflow_effect`` is **required** and is
692
+ ``WorkflowEffect.UNKNOWN``, as for :meth:`suspend_ticket`: refused
693
+ before any request unless allowed, and sent once, never retried. The
694
+ return value is parsed like :meth:`close_ticket`'s (an ``HREF`` only)::
695
+
696
+ client.restart_ticket(rfc, allow_workflow_effect=WorkflowEffect.UNKNOWN)
697
+ """
698
+ spec, parse = requests_res.build_restart_ticket(
699
+ rfc_number,
700
+ comment=comment,
701
+ done_by_id=done_by_id,
702
+ context=self._validation_context,
703
+ )
704
+ return parse(await self._transport.send(spec.allowing(allow_workflow_effect)))
705
+
613
706
  # --- actions -------------------------------------------------------------
614
707
  async def create_action(
615
708
  self,
@@ -947,14 +1040,20 @@ class AsyncEasyvistaClient:
947
1040
  the **first ticket only** the step was recorded as a type-20 action
948
1041
  with ``WORKFLOW_ID`` set; its ``WORKFLOW_ID`` was unchanged,
949
1042
  ``DONE_BY_ID`` stayed empty, and no ticket field changed.
950
- **The ticket's own ``OWNING_GROUP_ID`` does not follow the action's
951
- group** -- it stayed 57 on the first ticket, the only one read for it,
952
- so reassigning a step is not reassigning the ticket. The lower-case
953
- key ``group_id`` was the one sent; the upper-case spelling was never
954
- needed. The person (``done_by_id``) was **not measured**: the same
955
- body shape is sent, but nothing here shows what the instance does
956
- with it. Whether the UI wizard's notifications fire is not observable
957
- from the API.
1043
+ The ticket's own ``OWNING_GROUP_ID`` was read at the time on one
1044
+ ticket only and had not moved; that reading was taken too soon.
1045
+ Measured 2026-11-04 to 2026-11-10 on one instance (several tickets,
1046
+ may not generalise): the ticket's ``OWNING_GROUP_ID`` follows the
1047
+ step's new group after a delay of a few seconds to about two minutes,
1048
+ consistent with a server-side job (inferred, not observed), and
1049
+ ``LAST_UPDATE`` moves with it. What still holds: the status does not
1050
+ move, the step is not started and no action row is added. Also
1051
+ measured 2026-11-04 on one ticket: after ``done_by_id`` was written on
1052
+ the step, the ticket's ``OWNER_ID`` followed about 1 min 45 s later.
1053
+ The lower-case key ``group_id`` was the one sent; the upper-case
1054
+ spelling was never needed. The person's effect on the step was not
1055
+ measured beyond that one ``OWNER_ID`` observation. Whether the UI
1056
+ wizard's notifications fire is not observable from the API.
958
1057
 
959
1058
  Not refused by the workflow guard: the group and the person are data
960
1059
  the workflow reads, not workflow state. Re-read with :meth:`get_action`
@@ -610,6 +610,99 @@ class EasyvistaClient:
610
610
  )
611
611
  return parse(self._transport.send(spec.allowing(allow_workflow_effect)))
612
612
 
613
+ def suspend_ticket(
614
+ self,
615
+ rfc_number: str,
616
+ *,
617
+ allow_workflow_effect: WorkflowEffect | Iterable[WorkflowEffect],
618
+ comment: str | None = None,
619
+ done_by_id: int | None = None,
620
+ ) -> Request:
621
+ """Suspend a ticket -- the vendor's suspend request.
622
+
623
+ Sends ``PUT requests/{rfc}`` with a ``suspended`` body, as the vendor
624
+ documents it:
625
+ https://docs.easyvista.com/docs/rest-api-suspend-an-incident-request.md
626
+ (tier 1, re-read 2026-11-10). The page documents two optional
627
+ parameters, ``comment`` and ``done_by_id`` (an employee id), and no
628
+ target status. Both are left out of the body when ``None``. A ticket
629
+ that is already suspended is refused by the server (400, "The request
630
+ is already suspended. Operation aborted.").
631
+
632
+ Measured 2026-11-04 to 2026-11-10 on one instance (tier 4, may not
633
+ generalise): the ticket moved to the instance's suspended status and
634
+ one already-ended suspension action carrying the comment was added;
635
+ the workflow step stayed open. A status key sent in the body was
636
+ ignored (measured 2026-11-04). The request takes no status, and the
637
+ package has no status setter.
638
+
639
+ ``allow_workflow_effect`` is **required**: nothing documents what a
640
+ suspend does to the workflow, so the write is named
641
+ ``WorkflowEffect.UNKNOWN`` and refused with
642
+ :class:`~easyvista_python_client.EasyvistaWorkflowEffectRefused`
643
+ before any request is made unless the call allows it. The allowed
644
+ request is sent once, never retried. The return value is parsed like
645
+ :meth:`close_ticket`'s: the vendor answers 201 with an ``HREF`` only, so
646
+ re-read with :meth:`get_ticket` to see the status::
647
+
648
+ client.suspend_ticket(
649
+ rfc,
650
+ allow_workflow_effect=WorkflowEffect.UNKNOWN,
651
+ comment="Waiting for the requester",
652
+ )
653
+ """
654
+ spec, parse = requests_res.build_suspend_ticket(
655
+ rfc_number,
656
+ comment=comment,
657
+ done_by_id=done_by_id,
658
+ context=self._validation_context,
659
+ )
660
+ return parse(self._transport.send(spec.allowing(allow_workflow_effect)))
661
+
662
+ def restart_ticket(
663
+ self,
664
+ rfc_number: str,
665
+ *,
666
+ allow_workflow_effect: WorkflowEffect | Iterable[WorkflowEffect],
667
+ comment: str | None = None,
668
+ done_by_id: int | None = None,
669
+ ) -> Request:
670
+ """Restart a suspended ticket -- the vendor's restart request.
671
+
672
+ Sends ``PUT requests/{rfc}`` with a ``restarted`` body, as the vendor
673
+ documents it:
674
+ https://docs.easyvista.com/docs/rest-api-reopen-an-incident-request.md
675
+ (tier 1, re-read 2026-11-10). The page documents two optional
676
+ parameters, ``comment`` and ``done_by_id`` (an employee id), and no
677
+ target status; its example marks ``comment`` as required while its
678
+ table lists it as optional, so this method leaves it out of the body
679
+ when ``None``. On a ticket that is not suspended the server answers
680
+ 400 with "The request is not suspended. Operation aborted.".
681
+
682
+ Measured 2026-11-04 to 2026-11-10 on one instance (tier 4, may not
683
+ generalise): the request added one end-of-suspension action and, in
684
+ the tested case, returned the ticket to the status it was suspended
685
+ from; a restart on a ticket that was not suspended was refused with
686
+ the documented message, as HTTP 590 rather than the documented 400.
687
+ A status key sent in the body was ignored
688
+ (measured 2026-11-04). The request takes no status, and the package
689
+ has no status setter.
690
+
691
+ ``allow_workflow_effect`` is **required** and is
692
+ ``WorkflowEffect.UNKNOWN``, as for :meth:`suspend_ticket`: refused
693
+ before any request unless allowed, and sent once, never retried. The
694
+ return value is parsed like :meth:`close_ticket`'s (an ``HREF`` only)::
695
+
696
+ client.restart_ticket(rfc, allow_workflow_effect=WorkflowEffect.UNKNOWN)
697
+ """
698
+ spec, parse = requests_res.build_restart_ticket(
699
+ rfc_number,
700
+ comment=comment,
701
+ done_by_id=done_by_id,
702
+ context=self._validation_context,
703
+ )
704
+ return parse(self._transport.send(spec.allowing(allow_workflow_effect)))
705
+
613
706
  # --- actions -------------------------------------------------------------
614
707
  def create_action(
615
708
  self,
@@ -947,14 +1040,20 @@ class EasyvistaClient:
947
1040
  the **first ticket only** the step was recorded as a type-20 action
948
1041
  with ``WORKFLOW_ID`` set; its ``WORKFLOW_ID`` was unchanged,
949
1042
  ``DONE_BY_ID`` stayed empty, and no ticket field changed.
950
- **The ticket's own ``OWNING_GROUP_ID`` does not follow the action's
951
- group** -- it stayed 57 on the first ticket, the only one read for it,
952
- so reassigning a step is not reassigning the ticket. The lower-case
953
- key ``group_id`` was the one sent; the upper-case spelling was never
954
- needed. The person (``done_by_id``) was **not measured**: the same
955
- body shape is sent, but nothing here shows what the instance does
956
- with it. Whether the UI wizard's notifications fire is not observable
957
- from the API.
1043
+ The ticket's own ``OWNING_GROUP_ID`` was read at the time on one
1044
+ ticket only and had not moved; that reading was taken too soon.
1045
+ Measured 2026-11-04 to 2026-11-10 on one instance (several tickets,
1046
+ may not generalise): the ticket's ``OWNING_GROUP_ID`` follows the
1047
+ step's new group after a delay of a few seconds to about two minutes,
1048
+ consistent with a server-side job (inferred, not observed), and
1049
+ ``LAST_UPDATE`` moves with it. What still holds: the status does not
1050
+ move, the step is not started and no action row is added. Also
1051
+ measured 2026-11-04 on one ticket: after ``done_by_id`` was written on
1052
+ the step, the ticket's ``OWNER_ID`` followed about 1 min 45 s later.
1053
+ The lower-case key ``group_id`` was the one sent; the upper-case
1054
+ spelling was never needed. The person's effect on the step was not
1055
+ measured beyond that one ``OWNER_ID`` observation. Whether the UI
1056
+ wizard's notifications fire is not observable from the API.
958
1057
 
959
1058
  Not refused by the workflow guard: the group and the person are data
960
1059
  the workflow reads, not workflow state. Re-read with :meth:`get_action`
@@ -5,4 +5,4 @@
5
5
  live only in ``__init__``.
6
6
  """
7
7
 
8
- __version__ = "0.4.3"
8
+ __version__ = "0.5.0"
@@ -140,3 +140,71 @@ def build_close_ticket(
140
140
  )
141
141
 
142
142
  return spec, parse
143
+
144
+
145
+ def _build_state_request(
146
+ wrapper: str,
147
+ rfc_number: str,
148
+ *,
149
+ comment: str | None,
150
+ done_by_id: int | None,
151
+ context: dict[str, Any] | None,
152
+ ) -> tuple[RequestSpec, Callable[[Any], Request]]:
153
+ body: dict[str, Any] = {}
154
+ if comment is not None:
155
+ body["comment"] = comment
156
+ if done_by_id is not None:
157
+ body["done_by_id"] = done_by_id
158
+ spec = RequestSpec("PUT", f"requests/{rfc_number}", json={wrapper: body})
159
+
160
+ def parse(data: Any) -> Request:
161
+ records = extract_records(data, REQUESTS.envelope_key)
162
+ return Request.model_validate(
163
+ records[0] if records else data, context=context
164
+ )
165
+
166
+ return spec, parse
167
+
168
+
169
+ def build_suspend_ticket(
170
+ rfc_number: str,
171
+ *,
172
+ comment: str | None = None,
173
+ done_by_id: int | None = None,
174
+ context: dict[str, Any] | None = None,
175
+ ) -> tuple[RequestSpec, Callable[[Any], Request]]:
176
+ """Build the ``{"suspended": {...}}`` PUT spec -- the vendor's suspend request.
177
+
178
+ https://docs.easyvista.com/docs/rest-api-suspend-an-incident-request.md
179
+ (tier 1): ``comment`` and ``done_by_id`` are the page's two optional
180
+ parameters, and both are left out of the body when ``None``.
181
+ """
182
+ return _build_state_request(
183
+ "suspended",
184
+ rfc_number,
185
+ comment=comment,
186
+ done_by_id=done_by_id,
187
+ context=context,
188
+ )
189
+
190
+
191
+ def build_restart_ticket(
192
+ rfc_number: str,
193
+ *,
194
+ comment: str | None = None,
195
+ done_by_id: int | None = None,
196
+ context: dict[str, Any] | None = None,
197
+ ) -> tuple[RequestSpec, Callable[[Any], Request]]:
198
+ """Build the ``{"restarted": {...}}`` PUT spec -- the vendor's restart request.
199
+
200
+ https://docs.easyvista.com/docs/rest-api-reopen-an-incident-request.md
201
+ (tier 1): ``comment`` and ``done_by_id`` are the page's two optional
202
+ parameters, and both are left out of the body when ``None``.
203
+ """
204
+ return _build_state_request(
205
+ "restarted",
206
+ rfc_number,
207
+ comment=comment,
208
+ done_by_id=done_by_id,
209
+ context=context,
210
+ )
@@ -35,7 +35,8 @@ site allowed it explicitly, with ``allow_workflow_effect=``.
35
35
  ``end_action``, ``suspended``, ``restarted`` -- as a top-level body key, in any
36
36
  casing, on any path. (2) On ticket and action routes, the columns that hold or
37
37
  select workflow state: a status, a catalog (which selects the workflow), the
38
- workflow, stage and step links, and on an existing action its end date, type,
38
+ workflow, stage and step links, and on an existing action its end date, the
39
+ statuses it sets (``status_id_on_terminate``, ``status_id_on_create``), type,
39
40
  parent or ticket (creating an action or a task names a narrower set, below).
40
41
  (3) Ticket sub-routes that are workflow commands rather than records, and
41
42
  ``requests/without-workflow`` and the deletion of a ticket. (4) A write to
@@ -114,11 +115,17 @@ _REQUEST_COLUMNS = frozenset(
114
115
  )
115
116
 
116
117
  #: Columns on ``actions/{id}`` that end, re-type, re-parent or move an action.
118
+ #: ``status_id_on_create``: workflow state. Measured 2026-11-09 and 2026-11-10
119
+ #: on one instance (tier 4, may not generalise): a write to it on an existing
120
+ #: action changed where the ticket's status went. Not in the create sets
121
+ #: below: on create the server overwrites it with the current status
122
+ #: (measured 2026-11-05).
117
123
  _ACTION_COLUMNS = frozenset(
118
124
  {
119
125
  "end_date_ut",
120
126
  "end_date",
121
127
  "status_id_on_terminate",
128
+ "status_id_on_create",
122
129
  "workflow_id",
123
130
  "stage_id",
124
131
  "process_step_id",
@@ -44,7 +44,7 @@ exclude = [
44
44
 
45
45
  [project]
46
46
  name = "easyvista-python-client"
47
- version = "0.4.3"
47
+ version = "0.5.0"
48
48
  description = "Typed Python client for the EasyVista Service Manager REST API"
49
49
  readme = "README.md"
50
50
  requires-python = ">=3.11"
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the assets resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and valid EasyVista credentials."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  `easyvista_python_client` ships both clients over one surface:
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the departments and employees resources (writes are additionally profile-gated)."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the documents sub-resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API. Every call here is a GET; nothing is created, updated or deleted."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the actions sub-resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -388,11 +388,17 @@ UI's transfer is a wizard), so the effect is measured, not specified. Measured
388
388
  2026-10-02 on one instance (Service Manager 2025.3; two tickets, so it may not
389
389
  generalise): the group was stored (`GROUP_ID` 57 to 50 on the open workflow step
390
390
  of both tickets); the step stayed open and the ticket's status did not move; the
391
- open actions were unchanged and no new action rows appeared. **The ticket's own
392
- `OWNING_GROUP_ID` does not follow the action's group** (it stayed 57 on the first
393
- ticket, the only one read for it), so reassigning a step is not reassigning the
394
- ticket. Reassigning to a person (`done_by_id`) was **not measured**, and whether
395
- the UI wizard's notifications fire is not observable from the API.
391
+ open actions were unchanged and no new action rows appeared. The ticket's own
392
+ `OWNING_GROUP_ID` was read on one ticket only and had not moved; that reading was
393
+ taken too soon. Measured 2026-11-04 to 2026-11-10 on one instance (several tickets,
394
+ may not generalise): **the ticket's `OWNING_GROUP_ID` follows the step's new group**
395
+ after a delay of a few seconds to about two minutes (consistent with a server-side
396
+ job; inferred, not observed; `LAST_UPDATE` moves with it); the status does not move,
397
+ the step is not started and no action row is added. Also measured 2026-11-04 on one
398
+ ticket: after `done_by_id` was written on the step, the ticket's `OWNER_ID` followed
399
+ about 1 min 45 s later. The person's effect on the step was not measured beyond that
400
+ one `OWNER_ID` observation, and whether the UI wizard's notifications fire is not
401
+ observable from the API.
396
402
  `reassign_action` is not refused by the workflow guard, because the group and the
397
403
  person are data the workflow reads, not workflow state.
398
404
 
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the requests resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.3"
8
+ version: "0.5.0"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -199,6 +199,40 @@ a `TypeError`), and a value that does not include `WorkflowEffect.INTERRUPTS` is
199
199
  refused before any request is sent (see the first Gotcha). `WorkflowEffect` and
200
200
  `EasyvistaWorkflowEffectRefused` are both importable from the package root.
201
201
 
202
+ ### Suspend and restart
203
+
204
+ `suspend_ticket` and `restart_ticket` are the vendor's suspend and restart requests
205
+ (tier 1: `rest-api-suspend-an-incident-request.md`,
206
+ `rest-api-reopen-an-incident-request.md`). Each takes an optional `comment` and an
207
+ optional `done_by_id`, and no status. They are not status setters. Both require
208
+ `allow_workflow_effect` and are refused before any request unless it includes
209
+ `WorkflowEffect.UNKNOWN`; an allowed request is sent once, never retried.
210
+
211
+ ```python
212
+ from easyvista_python_client import EasyvistaClient, WorkflowEffect
213
+
214
+ with EasyvistaClient.from_env() as client:
215
+ client.suspend_ticket(
216
+ "YOUR_RFC_NUMBER",
217
+ allow_workflow_effect=WorkflowEffect.UNKNOWN,
218
+ comment="Waiting for the requester",
219
+ )
220
+ client.restart_ticket(
221
+ "YOUR_RFC_NUMBER",
222
+ allow_workflow_effect=WorkflowEffect.UNKNOWN,
223
+ comment="Requester answered",
224
+ )
225
+ # Re-read: the answer carries an HREF only.
226
+ print(client.get_ticket("YOUR_RFC_NUMBER").status_id)
227
+ ```
228
+
229
+ Measured 2026-11-04 to 2026-11-10 on one instance (tier 4, may not generalise): a
230
+ suspend moves the ticket to the instance's suspended status and adds one
231
+ already-ended suspension action carrying the comment, with the workflow step left
232
+ open; a restart adds one end-of-suspension action and, in the tested case, returns
233
+ the ticket to the status it was suspended from; a restart on a ticket that is not suspended is refused with "The request is
234
+ not suspended. Operation aborted."
235
+
202
236
  ```python
203
237
  from easyvista_python_client import EasyvistaClient, PostRequest
204
238