easyvista-python-client 0.2.0__tar.gz → 0.3.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.
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/CHANGELOG.md +125 -4
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/PKG-INFO +5 -2
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/README.md +4 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/publishing.rst +17 -5
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/user_guide.rst +23 -7
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/vendor-api-reference.md +131 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_async/client.py +21 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_sync/client.py +21 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_version.py +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/action.py +112 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/common.py +84 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/references.py +9 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/pyproject.toml +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-asset-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-client-setup/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-directory/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-document-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-instance-discovery/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-reporting-and-context/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-search-syntax/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-ticket-actions/SKILL.md +55 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-ticket-workflow/SKILL.md +1 -1
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/.gitignore +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/.pre-commit-config.yaml +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/.readthedocs.yaml +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/CONTRIBUTING.md +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/LICENSE +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/_static/.gitkeep +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/_templates/.gitkeep +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/api_reference.rst +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/conf.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/development.rst +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/index.rst +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/installation.rst +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/sponsoring.rst +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/__init__.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_async/__init__.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_async/_concurrency.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_async/_transport.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_fields.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_html.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_sync/__init__.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_sync/_concurrency.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_sync/_transport.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_transport.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/config.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/context.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/directory.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/discovery.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/exceptions.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/field_model.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/filters.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/__init__.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/asset.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/department.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/document.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/employee.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/generic.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/models/request.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/pagination.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/py.typed +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/reporting.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/__init__.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/actions.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/assets.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/departments.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/descriptor.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/discovery.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/documents.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/employees.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/resources/requests.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/timestamps.py +0 -0
- {easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/README.md +0 -0
|
@@ -4,11 +4,124 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
While the package is pre-1.0, breaking changes may land between minor
|
|
8
|
-
a deprecation policy will follow the 1.0 release.
|
|
7
|
+
While the package is pre-1.0, breaking changes may land between **minor**
|
|
8
|
+
versions; a deprecation policy will follow the 1.0 release. A patch release
|
|
9
|
+
never carries one. Each breaking change is marked `**BREAKING**` in its section
|
|
10
|
+
with the reasoning, so read the section for the version you are moving to.
|
|
11
|
+
|
|
12
|
+
**The git tag is what defines a version.** Where a section's narrative or date
|
|
13
|
+
disagrees with the tree its tag points at, the tag is authoritative and the prose
|
|
14
|
+
is the error. Tags carry no `v` prefix.
|
|
9
15
|
|
|
10
16
|
## [Unreleased]
|
|
11
17
|
|
|
18
|
+
## [0.3.0] - 2026-09-02
|
|
19
|
+
|
|
20
|
+
Makes the task-vs-action distinction inspectable. A GLPI comment corresponds to
|
|
21
|
+
an EasyVista **task**, not an action; actions carry effort and cost, so a
|
|
22
|
+
timeline mirrored without that distinction imports time-tracking and workflow
|
|
23
|
+
rows as conversation.
|
|
24
|
+
|
|
25
|
+
**Upgrading.** One breaking change, in `Action` (see `### Changed`). The minor
|
|
26
|
+
bump is deliberate: a dependant pinned `>=0.2.0,<0.3` does **not** pick this up,
|
|
27
|
+
and must widen its constraint on purpose — which is the point, because the
|
|
28
|
+
retyping below can change an answer silently rather than raise.
|
|
29
|
+
|
|
30
|
+
Before widening, check whether you read `action.ELAPSED_TIME`, `TIME_COST`,
|
|
31
|
+
`CONTRACTUAL_COST`, `START_DATE_UT` or `END_DATE_UT` off `model_extra`, or read
|
|
32
|
+
those keys out of a `model_dump(by_alias=True)` and treated them as strings. Both
|
|
33
|
+
change here. Nothing on the write side moves: `PostAction`, `PostTask` and
|
|
34
|
+
`ActionUpdate` are untouched.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Five columns declared on `Action` with real types: `elapsed_time` (minutes,
|
|
39
|
+
`int | None`), `time_cost` and `contractual_cost` (exact `Decimal | None`,
|
|
40
|
+
parsed from the API's French decimal comma), `start_date_ut` and
|
|
41
|
+
`end_date_ut` (aware `datetime | None`). They previously arrived only as
|
|
42
|
+
untyped `extra="allow"` strings.
|
|
43
|
+
|
|
44
|
+
**The `""` sentinel and `"0"` stay different answers.** `""` means the column
|
|
45
|
+
does not apply to this record and maps to `None`; `"0"` / `"0,00"` means it
|
|
46
|
+
applies and is zero, and maps to `0` / `Decimal("0.00")`. Collapsing the two
|
|
47
|
+
destroys the only signal that says whether a record tracks effort. Measured
|
|
48
|
+
over 1500 live rows: `ELAPSED_TIME` was `""` on 384 and `"0"` on 895.
|
|
49
|
+
- `Action.is_workflow_generated` — whether `WORKFLOW_ID` is set, i.e. whether
|
|
50
|
+
the workflow engine owns the row. Clean on 1500/1500 measured rows, and the
|
|
51
|
+
one structural fact in this area that holds.
|
|
52
|
+
|
|
53
|
+
It is **not** an `is_task()`, and the package deliberately does not ship one:
|
|
54
|
+
nothing on an action record says which route created it, and the
|
|
55
|
+
effort-shape heuristic that looks like it should is wrong in both directions
|
|
56
|
+
— 173 of 1500 rows had a `WORKFLOW_ID` with an empty `ELAPSED_TIME`, and 39
|
|
57
|
+
of 49 public comments carried a non-empty one, one of them with a real
|
|
58
|
+
`TIME_COST` of `99,00`. Which action types count as conversation is
|
|
59
|
+
per-deployment policy and stays with the caller.
|
|
60
|
+
- `scripts/tests/test_no_private_instance_identifiers.py` — a guard refusing any
|
|
61
|
+
private EasyVista instance identifier (hostname, domain, real catalog GUID or
|
|
62
|
+
code) in a tracked file. `.gitignore` has always stated that policy in prose;
|
|
63
|
+
it had no enforcement, and preparing this release put a real preprod hostname
|
|
64
|
+
into `docs/vendor-api-reference.md` — a tracked file that also ships in the
|
|
65
|
+
sdist — with all five gates passing, because none of them reads prose for
|
|
66
|
+
this. Both a public push and a PyPI upload are irreversible, so the check is
|
|
67
|
+
now mechanical. It carries its own negative control, and deliberately does
|
|
68
|
+
**not** refuse the illustrative account number or the synthetic
|
|
69
|
+
`EAZ_INC_000` stand-in the tracked tests already use.
|
|
70
|
+
- `models.common.OptionalDecimal`, the annotated type behind the two cost
|
|
71
|
+
columns. Accepts either decimal separator, so a dot-configured deployment
|
|
72
|
+
needs no setting, and **refuses a grouping separator** rather than guessing
|
|
73
|
+
— `'1.234,56'` and `'1,234.56'` are the same amount under opposite
|
|
74
|
+
conventions. Not exported from the package root.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- **BREAKING** — `ELAPSED_TIME`, `TIME_COST`, `CONTRACTUAL_COST`,
|
|
79
|
+
`START_DATE_UT` and `END_DATE_UT` are now declared fields on `Action` instead
|
|
80
|
+
of `extra="allow"` extras. Three consequences:
|
|
81
|
+
- `action.ELAPSED_TIME` (extra attribute access) raises `AttributeError`; use
|
|
82
|
+
`action.elapsed_time`. The same for the other four.
|
|
83
|
+
- They are gone from `action.model_extra`, and
|
|
84
|
+
`model_dump(by_alias=True)["ELAPSED_TIME"]` is now an `int | None` rather
|
|
85
|
+
than a `str`.
|
|
86
|
+
- `classify_fields()` bucketing is **unchanged** — none of the five starts
|
|
87
|
+
with `E_`, so all five have always landed in `.official`. What changed is
|
|
88
|
+
*presence*: `.official` (and the dump) now always carries all five keys,
|
|
89
|
+
`None` included, where previously a key the API did not return was simply
|
|
90
|
+
absent. Code that iterates `.official` sees five more keys on a default
|
|
91
|
+
`list_actions` row.
|
|
92
|
+
- **`None` is now ambiguous.** These columns are not on the default
|
|
93
|
+
`list_actions` projection, so on a default row every one reads `None`,
|
|
94
|
+
meaning *not returned* — not the `""` that means *does not apply*. The two
|
|
95
|
+
are indistinguishable once validated. Project them explicitly or read
|
|
96
|
+
item-level before reading meaning into a `None`.
|
|
97
|
+
- A `Decimal` in a dump is not JSON-serialisable, so
|
|
98
|
+
`json.dumps(action.model_dump(by_alias=True))` raises where a cost is
|
|
99
|
+
populated. Use `model_dump(mode="json")`, which renders the amount as a
|
|
100
|
+
string with a `.` decimal point rather than the `,` the API sent.
|
|
101
|
+
- A malformed value now raises where it previously passed through as a
|
|
102
|
+
string. This is the same trade `OptionalDateTime` already makes — a wrong
|
|
103
|
+
number is worse than a loud failure — and it carries the same blast radius:
|
|
104
|
+
the descriptor validates a page in a list comprehension, so one bad value
|
|
105
|
+
fails a whole `list_actions` call. See `O-COSTGROUP` in
|
|
106
|
+
`docs/vendor-api-reference.md`.
|
|
107
|
+
|
|
108
|
+
### Documentation
|
|
109
|
+
|
|
110
|
+
- `docs/vendor-api-reference.md` records, as tier 2 read on two 2025.3
|
|
111
|
+
deployments on 2026-09-02, that `/requests/{rfc_number}/tasks` is **POST
|
|
112
|
+
only** and that **no task read route exists** under any spelling — the only
|
|
113
|
+
timeline reads are `GET /actions`, `/actions/{id}` and
|
|
114
|
+
`/actions/{id}/{comment}`. A task is written as a task and read back as an
|
|
115
|
+
action, which is why there is no `list_tasks`/`iter_tasks` and why
|
|
116
|
+
`create_task` returns an `Action`. `create_task`'s docstring now says so
|
|
117
|
+
where a reader meets it.
|
|
118
|
+
- Same file, tier 4: the effort-column measurements above, and two side
|
|
119
|
+
findings. `ACTION_LABEL_*` is the label of the workflow **step**, not the
|
|
120
|
+
name of the action type — type 20 appeared under six different labels — so
|
|
121
|
+
it is a stable type name only for the non-workflow types. And types 14, 27
|
|
122
|
+
and 28 have an empty label in every language column at both list and item
|
|
123
|
+
level, so those ids cannot be named through the API at all (`O-ACTIONTYPE28`).
|
|
124
|
+
|
|
12
125
|
## [0.2.0] - 2026-09-02
|
|
13
126
|
|
|
14
127
|
The first release since `0.1.0`. A `0.2.0` section was prepared and dated
|
|
@@ -1002,6 +1115,13 @@ the release is additive.
|
|
|
1002
1115
|
|
|
1003
1116
|
Initial public release.
|
|
1004
1117
|
|
|
1118
|
+
> **The tag is authoritative, and it disagrees with this date.** The `0.1.0` tag
|
|
1119
|
+
> points at `3216a33` (2026-08-04), roughly 150 commits after `6df6a75`, the
|
|
1120
|
+
> commit this section was written to describe. Per the policy above, `0.1.0`
|
|
1121
|
+
> **is** the tree at `3216a33`; the date on this heading and the scope below
|
|
1122
|
+
> under-describe it. The tag is published, so it is not being moved — this note
|
|
1123
|
+
> exists so the discrepancy is recorded rather than rediscovered.
|
|
1124
|
+
|
|
1005
1125
|
### Added
|
|
1006
1126
|
|
|
1007
1127
|
- Synchronous `EasyvistaClient` and asynchronous `AsyncEasyvistaClient` over the
|
|
@@ -1022,6 +1142,7 @@ Initial public release.
|
|
|
1022
1142
|
status/error code, with non-retryable validation errors (HTTP 590, code 2013).
|
|
1023
1143
|
- `py.typed` marker — the package ships inline type information.
|
|
1024
1144
|
|
|
1025
|
-
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/
|
|
1026
|
-
[0.
|
|
1145
|
+
[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...HEAD
|
|
1146
|
+
[0.3.0]: https://github.com/baraline/easyvista_python_client/compare/0.2.0...0.3.0
|
|
1147
|
+
[0.2.0]: https://github.com/baraline/easyvista_python_client/compare/0.1.0...0.2.0
|
|
1027
1148
|
[0.1.0]: https://github.com/baraline/easyvista_python_client/releases/tag/0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easyvista-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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/
|
|
@@ -64,7 +64,10 @@ Typed Python client for the EasyVista Service Manager REST API. Sync + async,
|
|
|
64
64
|
Pydantic models, Bearer or Basic auth.
|
|
65
65
|
|
|
66
66
|
While the package is preparing for 1.0, breaking changes may land between
|
|
67
|
-
minor versions; a deprecation policy will follow the 1.0 release.
|
|
67
|
+
**minor** versions; a deprecation policy will follow the 1.0 release. A patch
|
|
68
|
+
release never carries one. Each breaking change is marked `**BREAKING**` in its
|
|
69
|
+
`CHANGELOG.md` section with the reasoning, so read the section for the version
|
|
70
|
+
you are moving to.
|
|
68
71
|
|
|
69
72
|
## Documentation
|
|
70
73
|
|
|
@@ -11,7 +11,10 @@ Typed Python client for the EasyVista Service Manager REST API. Sync + async,
|
|
|
11
11
|
Pydantic models, Bearer or Basic auth.
|
|
12
12
|
|
|
13
13
|
While the package is preparing for 1.0, breaking changes may land between
|
|
14
|
-
minor versions; a deprecation policy will follow the 1.0 release.
|
|
14
|
+
**minor** versions; a deprecation policy will follow the 1.0 release. A patch
|
|
15
|
+
release never carries one. Each breaking change is marked `**BREAKING**` in its
|
|
16
|
+
`CHANGELOG.md` section with the reasoning, so read the section for the version
|
|
17
|
+
you are moving to.
|
|
15
18
|
|
|
16
19
|
## Documentation
|
|
17
20
|
|
|
@@ -27,11 +27,23 @@ Cutting a release
|
|
|
27
27
|
#. Move the ``CHANGELOG.md`` ``[Unreleased]`` entries under the new version and update the
|
|
28
28
|
compare links at the bottom of the file.
|
|
29
29
|
#. Merge to ``main`` and let CI go green.
|
|
30
|
-
#. Publish a GitHub release whose tag is the version,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
#. Publish a GitHub release whose tag is the version, **unprefixed** -- ``0.3.0``
|
|
31
|
+
for version ``0.3.0``, never ``v0.3.0``.
|
|
32
|
+
|
|
33
|
+
.. warning::
|
|
34
|
+
|
|
35
|
+
This step previously ordered a ``v``-prefixed tag and said "``v``-prefixing
|
|
36
|
+
starts at ``v0.2.0``". That never happened: **both tags that exist are
|
|
37
|
+
bare** -- ``0.1.0`` and ``0.2.0`` -- so following the old instruction would
|
|
38
|
+
have produced a repository with two tag conventions. It also left the
|
|
39
|
+
``CHANGELOG.md`` compare links pointing at a ``v0.2.0`` that does not
|
|
40
|
+
exist, so two of them 404ed until 0.3.0 fixed them.
|
|
41
|
+
|
|
42
|
+
**The tag is what defines a version**; the prose is corrected to match the
|
|
43
|
+
tags, not the other way round. Keep every tag bare, and keep the
|
|
44
|
+
``CHANGELOG.md`` links bare with it. (The release workflow strips a leading
|
|
45
|
+
``v`` before comparing, so a prefixed tag would still *build* -- which is
|
|
46
|
+
exactly why this drifted unnoticed.)
|
|
35
47
|
|
|
36
48
|
The workflow then runs the test matrix (3.10--3.14) and the quality gates -- Ruff, mypy,
|
|
37
49
|
the generated-``_sync``-tree check, the hand-written-twin lint and a warnings-as-errors
|
|
@@ -1160,7 +1160,8 @@ Timestamps
|
|
|
1160
1160
|
``Request``'s timestamp fields (``submit_date_ut``, ``creation_date_ut``,
|
|
1161
1161
|
``max_resolution_date_ut``, ``expected_date_ut``, ``end_date_ut``,
|
|
1162
1162
|
``last_update``), ``Employee.last_update``, and ``Action.created_at`` /
|
|
1163
|
-
``Action.updated_at`` are
|
|
1163
|
+
``Action.updated_at`` / ``Action.start_date_ut`` / ``Action.end_date_ut`` are
|
|
1164
|
+
timezone-aware
|
|
1164
1165
|
:class:`datetime.datetime`, parsed from EasyVista's ISO-8601-with-offset wire
|
|
1165
1166
|
format (``2026-08-17T15:40:41.610+02:00``, millisecond precision — verified
|
|
1166
1167
|
live 2026-08-17). An unset date is ``None``. The ``_UT`` suffix is a naming
|
|
@@ -1193,14 +1194,29 @@ is still the raw wire string, so within one record dump
|
|
|
1193
1194
|
``json.dumps(record.model_dump(by_alias=True))`` and
|
|
1194
1195
|
``json.dumps(record.classify_fields().official)`` raise
|
|
1195
1196
|
``TypeError: Object of type datetime is not JSON serializable``. For a dump,
|
|
1196
|
-
pass ``model_dump(mode="json")``.
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1197
|
+
pass ``model_dump(mode="json")``.
|
|
1198
|
+
|
|
1199
|
+
**Since 0.3.0 an ``Action`` can also carry a ``Decimal``** — ``time_cost``
|
|
1200
|
+
and ``contractual_cost`` — which raises the same way
|
|
1201
|
+
(``TypeError: Object of type Decimal is not JSON serializable``).
|
|
1202
|
+
``model_dump(mode="json")`` handles it too, rendering the amount as a string.
|
|
1203
|
+
|
|
1204
|
+
``classify_fields()`` takes **no arguments**, so there is nowhere to put that
|
|
1205
|
+
keyword. The only recipe that covers *both* types is to classify the
|
|
1206
|
+
JSON-mode dump yourself — the buckets are keyed by wire column name, so
|
|
1207
|
+
``{k: dumped[k] for k in record.classify_fields().official}`` over
|
|
1208
|
+
``dumped = record.model_dump(mode="json", by_alias=True)`` gives the same
|
|
1202
1209
|
split with serialisable values.
|
|
1203
1210
|
|
|
1211
|
+
.. warning::
|
|
1212
|
+
|
|
1213
|
+
Rendering the bucket with
|
|
1214
|
+
:func:`~easyvista_python_client.format_ev_datetime` — which earlier
|
|
1215
|
+
revisions of this page offered as the alternative — **only ever handled
|
|
1216
|
+
``datetime``**, and now leaves a ``Decimal`` in place to raise on
|
|
1217
|
+
``json.dumps``. Use the JSON-mode dump above instead. Note the amount then
|
|
1218
|
+
renders with a ``.`` decimal point, not the ``,`` the API sent.
|
|
1219
|
+
|
|
1204
1220
|
Use :func:`~easyvista_python_client.format_ev_datetime` to render a
|
|
1205
1221
|
``datetime`` back into the literal EasyVista's grammar accepts (e.g. as an
|
|
1206
1222
|
interval bound above), and :func:`~easyvista_python_client.parse_ev_datetime`
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/docs/vendor-api-reference.md
RENAMED
|
@@ -104,6 +104,114 @@ and not proof of it; it omits `group_id`, `group_name` and `comment`, which
|
|
|
104
104
|
it lists `available_field_1`/`_6`, which `PostTask` does not declare and which
|
|
105
105
|
`extra_payload` reaches.
|
|
106
106
|
|
|
107
|
+
### A task is write-only as a resource, and read back as an action
|
|
108
|
+
|
|
109
|
+
**Tier 2, read 2026-09-02** on the development instance (100 paths), and
|
|
110
|
+
independently the same day on a second deployment (also 2025.3, also 100
|
|
111
|
+
paths). Both declare exactly:
|
|
112
|
+
|
|
113
|
+
| Path | Verbs |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `/requests/{rfc_number}/tasks` | **POST only** |
|
|
116
|
+
| `/requests/{rfc_number}/actions` | POST only |
|
|
117
|
+
| `/actions` | GET |
|
|
118
|
+
| `/actions/{id}` | GET, PATCH, PUT |
|
|
119
|
+
| `/actions/{id}/{comment}` | GET |
|
|
120
|
+
|
|
121
|
+
There is **no read route for a task** — no `GET /requests/{rfc}/tasks`, no
|
|
122
|
+
`/tasks/{id}`, nothing under any other spelling. The only timeline reads are
|
|
123
|
+
the three `/actions` routes. So a task is *written* through `tasks` and *read
|
|
124
|
+
back* through `actions`, and that is the whole story: a task and an action are
|
|
125
|
+
the same row in the same table, differing only in the state they are born in
|
|
126
|
+
(open vs already ended). This is why the package has `list_actions` and
|
|
127
|
+
deliberately **no `list_tasks`/`iter_tasks`** — there is no route to wrap.
|
|
128
|
+
|
|
129
|
+
A GET against the tasks path answers `403 "Unauthorized Method for your
|
|
130
|
+
profile"`, which per *Route topology* above proves nothing either way; the
|
|
131
|
+
spec's `paths` is what settles it.
|
|
132
|
+
|
|
133
|
+
**`create_task` returns an `Action`.** That is where a reader first meets the
|
|
134
|
+
confusion, and the annotation is correct rather than sloppy: there is no task
|
|
135
|
+
resource to model, so there is no `Task` read model and could not be one.
|
|
136
|
+
|
|
137
|
+
### The effort columns, and why they do not discriminate task from action
|
|
138
|
+
|
|
139
|
+
Five columns on an action record — `ELAPSED_TIME`, `TIME_COST`,
|
|
140
|
+
`CONTRACTUAL_COST`, `START_DATE_UT`, `END_DATE_UT` — are declared on `Action`
|
|
141
|
+
as of 0.3.0. Until then they arrived only as `extra="allow"` extras: untyped
|
|
142
|
+
strings, with a French decimal comma on the two costs.
|
|
143
|
+
|
|
144
|
+
**Tier 4, measured 2026-09-02, 1500 action rows on the development instance**
|
|
145
|
+
(one instance, one date, so it may not generalise), corroborated by an
|
|
146
|
+
independent measurement the same day on that second deployment (1465 timeline
|
|
147
|
+
entries across 120 tickets), which agreed on every point below.
|
|
148
|
+
|
|
149
|
+
**`""` and `"0"` are different answers.** `""` means the column does not apply
|
|
150
|
+
to this record; `"0"` (or `"0,00"`) means it applies and is zero.
|
|
151
|
+
|
|
152
|
+
| Column | `""` | zero | non-zero |
|
|
153
|
+
| --- | --- | --- | --- |
|
|
154
|
+
| `ELAPSED_TIME` | 384 | 895 (`'0'`) | 221 |
|
|
155
|
+
| `TIME_COST` | 691 | 808 (`'0,00'`) | 1 (`'99,00'`) |
|
|
156
|
+
| `CONTRACTUAL_COST` | 691 | 808 (`'0,00'`) | 1 (`'129,00'`) |
|
|
157
|
+
|
|
158
|
+
A parser that maps both to `0`, or both to `None`, destroys the only signal
|
|
159
|
+
that says whether a record tracks effort. `Action` preserves it: `None` for
|
|
160
|
+
`""`, `0` / `Decimal("0.00")` for the zeroes.
|
|
161
|
+
|
|
162
|
+
**The shape heuristic is false in both directions.** It is tempting to read
|
|
163
|
+
"workflow rows carry `WORKFLOW_ID`/`STAGE_ID` with `ELAPSED_TIME='0'` and
|
|
164
|
+
`'0,00'` costs, task-shaped rows carry none of it and empty effort" as a
|
|
165
|
+
task/action discriminator. Measured, it fails both ways:
|
|
166
|
+
|
|
167
|
+
* **173 of 1500** rows carried a `WORKFLOW_ID` *and* an empty `ELAPSED_TIME`
|
|
168
|
+
— 126 of them the type-20 `Analyse et résolution` workflow step. So
|
|
169
|
+
"workflow row ⇒ effort is `'0'`" is false.
|
|
170
|
+
* **171 of 1500** rows carried no `WORKFLOW_ID` *and* a non-empty
|
|
171
|
+
`ELAPSED_TIME`. Among them **39 of the 49** type-94 `Commentaire [Public]`
|
|
172
|
+
rows — ordinary public comments — usually with `ELAPSED_TIME='1'`. One
|
|
173
|
+
public comment carried `ELAPSED_TIME='12'`, `TIME_COST='99,00'` and
|
|
174
|
+
`CONTRACTUAL_COST='129,00'`. So "effort recorded ⇒ not a comment" is false,
|
|
175
|
+
and a filter built on it drops four public comments in five.
|
|
176
|
+
|
|
177
|
+
`ACTION_TYPE_ID` alone does not discriminate either, which the second
|
|
178
|
+
deployment measured directly: type 94 appeared in both shapes there (74 rows
|
|
179
|
+
with a `PARENT_ACTION_ID`, 18 without; 37 with a non-zero `ELAPSED_TIME`, 55
|
|
180
|
+
without).
|
|
181
|
+
|
|
182
|
+
What an effort column reports is **whether effort was recorded**, not what kind
|
|
183
|
+
of record this is. No column examined across those 1500 rows recorded which
|
|
184
|
+
route created it — stated as a measurement, not as a proof of absence: the
|
|
185
|
+
item-level record carries 88 columns, not all of which were tallied, and a
|
|
186
|
+
deployment may populate one this instance leaves empty. If you find a column
|
|
187
|
+
that does discriminate, it belongs here.
|
|
188
|
+
|
|
189
|
+
**What *is* clean: `WORKFLOW_ID`.** 1500/1500 rows — a `WORKFLOW_ID` is set iff
|
|
190
|
+
the workflow engine produced the row. No row of the conversation types (94
|
|
191
|
+
`Commentaire [Public]`, 95 `Note Interne [Privé]`, 7 `Appel`) carried one.
|
|
192
|
+
`Action.is_workflow_generated` exposes exactly that and nothing more. Deciding
|
|
193
|
+
which of the remaining types count as conversation is per-deployment policy —
|
|
194
|
+
an `action_type_id` allowlist — and stays with the caller.
|
|
195
|
+
|
|
196
|
+
**Side finding, tier 4, same measurement: `ACTION_LABEL_*` is the label of the
|
|
197
|
+
workflow *step*, not the name of the action type.** Type 20 appeared as
|
|
198
|
+
`Analyse et résolution` (126 rows), `Traitement` (10), `Traitement du refus`,
|
|
199
|
+
`Traitement de la demande`, `test` and `notif`; type 30 as `stocker le groupe
|
|
200
|
+
d'implémentation`, `Mise à jour SLA` and `sauvegarde`; type 82 under two
|
|
201
|
+
labels. So for **workflow** types the label varies row to row and cannot be
|
|
202
|
+
used as a type name. For the non-workflow types (94, 95, 7) it was stable and
|
|
203
|
+
is the type's real name. This qualifies the *Visibility is by action type*
|
|
204
|
+
note: `discover("ACTION_TYPE")` recovers real names for the human types, and
|
|
205
|
+
per-step text for the workflow ones.
|
|
206
|
+
|
|
207
|
+
**Types 14, 27 and 28 have an empty `ACTION_LABEL_*` in every language column**
|
|
208
|
+
— all six on the list projection and all twelve (`_EN`, `_FR`, `_GE`, `_IT`,
|
|
209
|
+
`_PO`, `_SP`, `_L1`..`_L6`) on the item GET. There is no `action-types` route
|
|
210
|
+
to ask, so on this deployment those ids **cannot be named through the API at
|
|
211
|
+
all**. What is known about 28 is behavioural, not nominal: it is the row that
|
|
212
|
+
carries the text passed to `set_status(comment=...)`, so it must not be
|
|
213
|
+
filtered out of a timeline read.
|
|
214
|
+
|
|
107
215
|
Also worth recording without acting on it: the instance's `POST /assets` schema
|
|
108
216
|
(tier 3) titles its array `asset` while its own example uses `assets`, which is
|
|
109
217
|
what this package sends and what works. That is an inconsistency inside one
|
|
@@ -143,8 +251,10 @@ spec's `paths` is what settles whether a route exists.
|
|
|
143
251
|
| Path | Verbs | Note |
|
|
144
252
|
| --- | --- | --- |
|
|
145
253
|
| `/requests/{rfc_number}/actions` | POST | create-only; no nested list, item or update |
|
|
254
|
+
| `/requests/{rfc_number}/tasks` | POST | create-only; **no task read route exists** — read them back through `/actions` |
|
|
146
255
|
| `/actions` | GET | the only action list |
|
|
147
256
|
| `/actions/{id}` | GET, PATCH, PUT | the only action item; **no DELETE** |
|
|
257
|
+
| `/actions/{id}/{comment}` | GET | `{comment}` is a memo-field *selector*, not a literal |
|
|
148
258
|
| `/requests/{RFC_NUMBER}/documents` | GET, POST | |
|
|
149
259
|
| `/requests/{RFC_NUMBER}/documents/{id}` | GET, DELETE | what this package sends by default |
|
|
150
260
|
| `/documents/{id}` | GET, DELETE | marked `deprecated`; opt in with `document_delete_path_style="top_level"` |
|
|
@@ -217,6 +327,27 @@ The vendor documents `catalog_guid` as the *preferred* identifier (tier 1) and
|
|
|
217
327
|
docstrings now hedge. Until someone either re-reads the vendor page and adds
|
|
218
328
|
the row here, or measures the omitted form live and dates it, the
|
|
219
329
|
documentation must not assert it.
|
|
330
|
+
* **O-COSTGROUP** — `TIME_COST` / `CONTRACTUAL_COST` are parsed by
|
|
331
|
+
`models/common._parse_ev_decimal`, which accepts either decimal separator and
|
|
332
|
+
**refuses a grouping separator** rather than guessing (`'1.234,56'` and
|
|
333
|
+
`'1,234.56'` are the same amount under opposite conventions). Every amount
|
|
334
|
+
observed live had exactly two fraction digits and no grouping (1500 rows,
|
|
335
|
+
2026-09-02), so the refusal has never fired. It **also** refuses three or more
|
|
336
|
+
fraction digits, for the same ambiguity (`'1,234'` could be `1.234` or a
|
|
337
|
+
comma-grouped `1234`) — which means a genuinely 3-decimal currency is refused
|
|
338
|
+
too. **Magnitude is not a trigger**: `'1000,00'` parses fine, since it carries
|
|
339
|
+
no grouping separator. Because the descriptor validates a page in a list
|
|
340
|
+
comprehension, a refusal fails a whole `list_actions` call, not one row. If a
|
|
341
|
+
refused literal is ever seen, record it here and widen the pattern with
|
|
342
|
+
evidence.
|
|
343
|
+
* **O-ACTIONTYPE28** — types 14, 27 and 28 have an empty `ACTION_LABEL_*` in
|
|
344
|
+
every language column at both list and item level, and there is no
|
|
345
|
+
`action-types` route, so nothing in the API can name them. Type 28 is known
|
|
346
|
+
behaviourally (it carries `set_status(comment=...)` text) and 14 and 27 not
|
|
347
|
+
at all. Settling this needs the EasyVista **admin console**, not the API: the
|
|
348
|
+
administration screen listing action types, and specifically which type ids
|
|
349
|
+
that deployment classes as *task* types. Nobody working on this package has
|
|
350
|
+
console access; if you do, transcribe the list here.
|
|
220
351
|
* **O-TASKDOC** — transcribe the vendor's create-a-task field table into the
|
|
221
352
|
section above, so `PostTask` can be diffed against tier 1. Until then
|
|
222
353
|
`action_type_guid` is declared on `PostAction` (tier 1, 2023.4+) and **not**
|
|
@@ -629,6 +629,27 @@ class AsyncEasyvistaClient:
|
|
|
629
629
|
no ``parent_action_id`` and works on a ticket at any stage, including
|
|
630
630
|
one whose open actions a status change has already drained.
|
|
631
631
|
|
|
632
|
+
**This returns an** :class:`~easyvista_python_client.Action`, and that
|
|
633
|
+
is not a mistake in the annotation: a task *is* an action record, and
|
|
634
|
+
there is no separate task resource to model. ``tasks`` is a create-only
|
|
635
|
+
route — the instance's own OpenAPI declares
|
|
636
|
+
``POST /requests/{rfc_number}/tasks`` and no other verb on it, and
|
|
637
|
+
declares no read route for a task anywhere (tier 2, read 2026-09-02 on
|
|
638
|
+
one deployment, 100 paths, and independently on a second the same day).
|
|
639
|
+
The only timeline reads are ``GET /actions``, ``GET /actions/{id}`` and
|
|
640
|
+
``GET /actions/{id}/{comment}``, so **a task is written as a task and
|
|
641
|
+
read back as an action** — which is why this package has
|
|
642
|
+
:meth:`list_actions` and no ``list_tasks``. A GET against the tasks
|
|
643
|
+
path answers 403, and per
|
|
644
|
+
``docs/vendor-api-reference.md`` no 403 on this API distinguishes an
|
|
645
|
+
absent route from a denied one, so the spec is what settles it.
|
|
646
|
+
|
|
647
|
+
A corollary worth stating, because it is where the read side goes
|
|
648
|
+
wrong: **once written, nothing on the record says which route created
|
|
649
|
+
it.** In particular the effort columns do not — see
|
|
650
|
+
:attr:`~easyvista_python_client.Action.is_workflow_generated` for the
|
|
651
|
+
measurements that refute the tempting heuristic.
|
|
652
|
+
|
|
632
653
|
Like :meth:`create_action`, the returned :class:`Action` carries **no
|
|
633
654
|
usable ``action_id``** — the create response is an HREF naming the
|
|
634
655
|
parent request. Diff :meth:`list_actions` across the call to address
|
|
@@ -629,6 +629,27 @@ class EasyvistaClient:
|
|
|
629
629
|
no ``parent_action_id`` and works on a ticket at any stage, including
|
|
630
630
|
one whose open actions a status change has already drained.
|
|
631
631
|
|
|
632
|
+
**This returns an** :class:`~easyvista_python_client.Action`, and that
|
|
633
|
+
is not a mistake in the annotation: a task *is* an action record, and
|
|
634
|
+
there is no separate task resource to model. ``tasks`` is a create-only
|
|
635
|
+
route — the instance's own OpenAPI declares
|
|
636
|
+
``POST /requests/{rfc_number}/tasks`` and no other verb on it, and
|
|
637
|
+
declares no read route for a task anywhere (tier 2, read 2026-09-02 on
|
|
638
|
+
one deployment, 100 paths, and independently on a second the same day).
|
|
639
|
+
The only timeline reads are ``GET /actions``, ``GET /actions/{id}`` and
|
|
640
|
+
``GET /actions/{id}/{comment}``, so **a task is written as a task and
|
|
641
|
+
read back as an action** — which is why this package has
|
|
642
|
+
:meth:`list_actions` and no ``list_tasks``. A GET against the tasks
|
|
643
|
+
path answers 403, and per
|
|
644
|
+
``docs/vendor-api-reference.md`` no 403 on this API distinguishes an
|
|
645
|
+
absent route from a denied one, so the spec is what settles it.
|
|
646
|
+
|
|
647
|
+
A corollary worth stating, because it is where the read side goes
|
|
648
|
+
wrong: **once written, nothing on the record says which route created
|
|
649
|
+
it.** In particular the effort columns do not — see
|
|
650
|
+
:attr:`~easyvista_python_client.Action.is_workflow_generated` for the
|
|
651
|
+
measurements that refute the tempting heuristic.
|
|
652
|
+
|
|
632
653
|
Like :meth:`create_action`, the returned :class:`Action` carries **no
|
|
633
654
|
usable ``action_id``** — the create response is an HREF naming the
|
|
634
655
|
parent request. Diff :meth:`list_actions` across the call to address
|
|
@@ -11,6 +11,7 @@ from .common import (
|
|
|
11
11
|
EasyvistaModel,
|
|
12
12
|
EasyvistaWriteModel,
|
|
13
13
|
OptionalDateTime,
|
|
14
|
+
OptionalDecimal,
|
|
14
15
|
OptionalInt,
|
|
15
16
|
_shipped_keys,
|
|
16
17
|
)
|
|
@@ -42,6 +43,42 @@ class Action(EasyvistaModel):
|
|
|
42
43
|
request instead of an item fetch per action — ``list_actions`` returns one
|
|
43
44
|
page and does not paginate, so it is a page's worth, not a ticket's.
|
|
44
45
|
|
|
46
|
+
**Effort columns: ``""`` and ``"0"`` are different answers.** ``elapsed_time``
|
|
47
|
+
(minutes), ``time_cost``, ``contractual_cost``, ``start_date_ut`` and
|
|
48
|
+
``end_date_ut`` are declared as of 0.3.0, having previously reached callers
|
|
49
|
+
only as untyped ``extra="allow"`` strings. When the API **returns** the
|
|
50
|
+
column, ``""`` means it does not apply to this record and ``"0"`` /
|
|
51
|
+
``"0,00"`` means it applies and is zero (tier 4, measured 2026-09-02 over
|
|
52
|
+
1500 rows on two instances -- so it may not generalise). This model preserves
|
|
53
|
+
that distinction deliberately -- ``None`` for ``""``, ``0`` /
|
|
54
|
+
``Decimal("0.00")`` for the zeroes -- because collapsing the two destroys the
|
|
55
|
+
only signal saying whether a record tracks effort at all. The two cost columns
|
|
56
|
+
arrive with a French decimal comma (``'99,00'``) and parse to an exact
|
|
57
|
+
:class:`~decimal.Decimal`. Either decimal separator is accepted; a grouping
|
|
58
|
+
separator and three-or-more fraction digits are **refused** rather than
|
|
59
|
+
guessed at, because ``'1.234,56'`` and ``'1,234.56'`` are the same amount
|
|
60
|
+
under opposite conventions. Magnitude is not a trigger -- ``'1000,00'``
|
|
61
|
+
parses. The parser is ``models/common.py::_parse_ev_decimal``, named as a
|
|
62
|
+
path rather than cross-referenced because it is private and carries no
|
|
63
|
+
rendered API-reference page.
|
|
64
|
+
|
|
65
|
+
.. warning::
|
|
66
|
+
|
|
67
|
+
**``None`` is ambiguous, and the default list row is the trap.** None of
|
|
68
|
+
these five columns rides the default ``list_actions`` projection, so on a
|
|
69
|
+
default list row every one of them reads ``None`` -- meaning *not
|
|
70
|
+
returned*, not *does not apply*. The two are indistinguishable on the
|
|
71
|
+
model. Project them explicitly (``list_actions(rfc, fields=[...,
|
|
72
|
+
"ELAPSED_TIME", "WORKFLOW_ID"])``) or read item-level with
|
|
73
|
+
:meth:`~easyvista_python_client.EasyvistaClient.get_action` before reading
|
|
74
|
+
any meaning into a ``None``. To tell the two apart from a raw response,
|
|
75
|
+
check whether the key is present at all -- once validated, that is gone.
|
|
76
|
+
|
|
77
|
+
What that signal does **not** settle is whether a row was created as a task
|
|
78
|
+
or as an action -- nothing on the record does, and the effort-shape heuristic
|
|
79
|
+
that looks like it should is measurably wrong in both directions. See
|
|
80
|
+
:attr:`is_workflow_generated`.
|
|
81
|
+
|
|
45
82
|
Naming: this model calls its two timestamps ``created_at``/``updated_at``
|
|
46
83
|
where :class:`~easyvista_python_client.models.request.Request` and
|
|
47
84
|
:class:`~easyvista_python_client.models.employee.Employee` mirror the wire
|
|
@@ -103,6 +140,29 @@ class Action(EasyvistaModel):
|
|
|
103
140
|
stage_id: OptionalInt = Field(default=None, alias="STAGE_ID")
|
|
104
141
|
workflow_id: OptionalInt = Field(default=None, alias="WORKFLOW_ID")
|
|
105
142
|
parent_action_id: OptionalInt = Field(default=None, alias="PARENT_ACTION_ID")
|
|
143
|
+
# --- effort and cost (EV-TASKSHAPE, added 0.3.0) -------------------------
|
|
144
|
+
# Until 0.3.0 these five reached callers only as untyped ``extra="allow"``
|
|
145
|
+
# strings. **The "" sentinel and "0" are different answers** -- "" means the
|
|
146
|
+
# column does not apply to this record, "0" that it applies and is zero --
|
|
147
|
+
# and ``OptionalInt``/``OptionalDecimal`` preserve exactly that: ``None`` for
|
|
148
|
+
# "", ``0`` for "0". Measured over 1500 live rows 2026-09-02, ELAPSED_TIME
|
|
149
|
+
# was "" on 384 and "0" on 895; the costs were "" on 691 and "0,00" on 808.
|
|
150
|
+
#
|
|
151
|
+
# ``elapsed_time`` is in MINUTES, is never derived from the window, and is
|
|
152
|
+
# stored verbatim even when it contradicts it -- but a zero-length window
|
|
153
|
+
# (``start_date_ut == end_date_ut``) stores 0 whatever was sent.
|
|
154
|
+
#
|
|
155
|
+
# Named for the wire (``start_date_ut``/``end_date_ut``) to match
|
|
156
|
+
# ``Request.end_date_ut``, rather than following this model's own
|
|
157
|
+
# ``created_at``/``updated_at``, which the class docstring flags as a wart.
|
|
158
|
+
# Both are the ACTION's effort window: unlike ``Request.end_date_ut``, which
|
|
159
|
+
# is stamped at RESOLUTION, an action's ``END_DATE_UT`` is set when the
|
|
160
|
+
# action itself is ended.
|
|
161
|
+
elapsed_time: OptionalInt = Field(default=None, alias="ELAPSED_TIME")
|
|
162
|
+
time_cost: OptionalDecimal = Field(default=None, alias="TIME_COST")
|
|
163
|
+
contractual_cost: OptionalDecimal = Field(default=None, alias="CONTRACTUAL_COST")
|
|
164
|
+
start_date_ut: OptionalDateTime = Field(default=None, alias="START_DATE_UT")
|
|
165
|
+
end_date_ut: OptionalDateTime = Field(default=None, alias="END_DATE_UT")
|
|
106
166
|
|
|
107
167
|
@model_validator(mode="after")
|
|
108
168
|
def _derive_action_id_from_href(self) -> Action:
|
|
@@ -150,6 +210,58 @@ class Action(EasyvistaModel):
|
|
|
150
210
|
"""
|
|
151
211
|
return localized_label(self.model_dump(by_alias=True), "ACTION_LABEL")
|
|
152
212
|
|
|
213
|
+
@property
|
|
214
|
+
def is_workflow_generated(self) -> bool:
|
|
215
|
+
"""Whether the workflow engine owns this row, i.e. ``WORKFLOW_ID`` is set.
|
|
216
|
+
|
|
217
|
+
A ticket's catalog workflow auto-spawns its own action rows -- about a
|
|
218
|
+
dozen on a freshly created ticket -- and each carries a ``WORKFLOW_ID``
|
|
219
|
+
naming the workflow instance. Rows created by a person or by an API
|
|
220
|
+
caller do not. Measured 2026-09-02 over 1500 live action rows on one
|
|
221
|
+
instance (tier 4, so it may not generalise): no row of the
|
|
222
|
+
conversation-bearing types (94 ``Commentaire [Public]``, 95 ``Note
|
|
223
|
+
Interne [Prive]``, 7 ``Appel``) carried one, and every row of the
|
|
224
|
+
workflow step types (1, 20, 21, 23, 30, 32, 33, 50, 65, 82, 84) that
|
|
225
|
+
the engine had produced did.
|
|
226
|
+
|
|
227
|
+
.. warning::
|
|
228
|
+
|
|
229
|
+
**This is not a task/action discriminator, and there is none.** A
|
|
230
|
+
task (``create_task``) and an action (``create_action``) are the same
|
|
231
|
+
record in the same table, told apart only by the state they are born
|
|
232
|
+
in -- open versus already ended -- and **neither ``WORKFLOW_ID`` nor
|
|
233
|
+
the effort columns recover which route created a row**. Measured
|
|
234
|
+
2026-09-02, both directions of the tempting shape heuristic fail on
|
|
235
|
+
one instance:
|
|
236
|
+
|
|
237
|
+
* 173 of 1500 rows had a ``WORKFLOW_ID`` **and** an empty
|
|
238
|
+
``ELAPSED_TIME`` -- 126 of them the type-20 ``Analyse et
|
|
239
|
+
resolution`` workflow step. So "workflow row => effort is 0" is
|
|
240
|
+
false.
|
|
241
|
+
* 39 of 49 type-94 ``Commentaire [Public]`` rows -- ordinary public
|
|
242
|
+
comments -- carried a **non-empty** ``ELAPSED_TIME``, usually
|
|
243
|
+
``'1'``; one carried ``ELAPSED_TIME='12'``, ``TIME_COST='99,00'``
|
|
244
|
+
and ``CONTRACTUAL_COST='129,00'``. So "effort set => not a
|
|
245
|
+
comment" is false, and a filter built on it drops four public
|
|
246
|
+
comments in five.
|
|
247
|
+
|
|
248
|
+
What an effort column reports is whether *effort was recorded*, not
|
|
249
|
+
what kind of record this is. Deciding which action types count as
|
|
250
|
+
conversation is per-deployment policy: pin the ``action_type_id``
|
|
251
|
+
allowlist your administrator confirms, and use this property only to
|
|
252
|
+
exclude the workflow engine's own rows. See
|
|
253
|
+
``docs/vendor-api-reference.md`` for the measurements.
|
|
254
|
+
|
|
255
|
+
A plain property, not a serialized field, so it never appears in
|
|
256
|
+
``model_dump`` or ``classify_fields``. ``False``, never ``None``, when
|
|
257
|
+
``WORKFLOW_ID`` is absent from the projection -- the default
|
|
258
|
+
``list_actions`` row omits it, so on a default list row this reads
|
|
259
|
+
``False`` for every action whether or not the engine owns it. Project
|
|
260
|
+
``WORKFLOW_ID`` explicitly (``list_actions(rfc,
|
|
261
|
+
fields=[..., "WORKFLOW_ID"])``) or read item-level before trusting it.
|
|
262
|
+
"""
|
|
263
|
+
return self.workflow_id is not None
|
|
264
|
+
|
|
153
265
|
|
|
154
266
|
class PostAction(EasyvistaWriteModel):
|
|
155
267
|
"""Payload for creating an action on a ticket.
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
from collections.abc import Sequence
|
|
6
7
|
from datetime import datetime, timezone
|
|
8
|
+
from decimal import Decimal
|
|
7
9
|
from typing import Annotated, Any
|
|
8
10
|
|
|
9
11
|
from pydantic import (
|
|
@@ -37,6 +39,88 @@ OptionalInt = Annotated[int | None, BeforeValidator(_empty_str_to_none)]
|
|
|
37
39
|
"""An ``int | None`` field that treats the API's ``""`` sentinel as ``None``."""
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
# An optional sign, digits, then at most one separator and one or two fraction
|
|
43
|
+
# digits. Deliberately refuses a grouping separator -- see the validator.
|
|
44
|
+
_EV_DECIMAL = re.compile(r"^[+-]?\d+(?:[.,]\d{1,2})?$")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_ev_decimal(value: Any) -> Any:
|
|
48
|
+
"""Parse EasyVista's locale-formatted money column into an exact ``Decimal``.
|
|
49
|
+
|
|
50
|
+
The instance renders a currency column with its own decimal separator: on
|
|
51
|
+
the verified deployment ``TIME_COST`` and ``CONTRACTUAL_COST`` come back as
|
|
52
|
+
``'0,00'`` / ``'99,00'`` — a **comma** — which is why this exists rather
|
|
53
|
+
than the column being a plain ``float``. Both separators are accepted, so a
|
|
54
|
+
dot-configured deployment needs no setting; the separator is a deployment's
|
|
55
|
+
locale, not an EasyVista constant.
|
|
56
|
+
|
|
57
|
+
``""`` maps to ``None`` and that is load-bearing: ``""`` means the column
|
|
58
|
+
does **not apply** to this record, while ``'0,00'`` means it applies and is
|
|
59
|
+
zero. Collapsing the two — to ``0`` or to ``None`` — destroys the only
|
|
60
|
+
signal that says whether a record tracks cost at all. See
|
|
61
|
+
:class:`~easyvista_python_client.models.action.Action` for what that signal
|
|
62
|
+
is worth and what it does **not** prove.
|
|
63
|
+
|
|
64
|
+
**Two shapes raise rather than being guessed at**, both of them formats this
|
|
65
|
+
parser has never seen live:
|
|
66
|
+
|
|
67
|
+
* **A grouping separator.** ``'1.234,56'`` and ``'1,234.56'`` are the same
|
|
68
|
+
amount under opposite conventions, and ``'9,999'`` is either ``9.999`` or
|
|
69
|
+
``9999`` with nothing in the value to say which.
|
|
70
|
+
* **Three or more fraction digits.** ``'1,234'`` is refused for the same
|
|
71
|
+
reason — it is indistinguishable from a comma-grouped ``1234`` — which
|
|
72
|
+
also means a genuinely 3-decimal currency is refused here.
|
|
73
|
+
|
|
74
|
+
Note what is **not** a trigger: magnitude. ``'1000,00'`` parses fine, because
|
|
75
|
+
it carries no grouping separator. An earlier revision of this docstring said
|
|
76
|
+
"an amount above 999 is the case to watch", which was wrong — what matters is
|
|
77
|
+
the *format*, not the size.
|
|
78
|
+
|
|
79
|
+
Every amount observed live carried exactly two fraction digits and no
|
|
80
|
+
grouping (1500 rows, 2026-09-02: ``'0,00'``, ``'99,00'``, ``'129,00'``).
|
|
81
|
+
Refusing is the same trade :func:`_empty_str_to_none_datetime` makes — a
|
|
82
|
+
wrong number is worse than a loud failure — and it carries the same cost:
|
|
83
|
+
``resources/descriptor.py`` validates a page in a list comprehension, so one
|
|
84
|
+
unparseable amount fails the whole ``list_actions`` call rather than that
|
|
85
|
+
row. See ``O-COSTGROUP`` in ``docs/vendor-api-reference.md``.
|
|
86
|
+
|
|
87
|
+
``Decimal``, not ``float``, because these are money: ``Decimal('0.10') +
|
|
88
|
+
Decimal('0.20') == Decimal('0.30')`` and the float equivalent does not.
|
|
89
|
+
A non-string value (an ``int``, ``float`` or ``Decimal`` handed in
|
|
90
|
+
directly) passes through to pydantic's own ``Decimal`` validator untouched.
|
|
91
|
+
"""
|
|
92
|
+
if value is None:
|
|
93
|
+
return None
|
|
94
|
+
if isinstance(value, str):
|
|
95
|
+
text = value.strip()
|
|
96
|
+
if not text:
|
|
97
|
+
return None
|
|
98
|
+
if not _EV_DECIMAL.match(text):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"{value!r} is not an EasyVista amount. Expected digits with at "
|
|
101
|
+
"most one decimal separator ('.' or ',') and ONE OR TWO "
|
|
102
|
+
"fraction digits, e.g. '0,00' or '99.00'. Refused, rather than "
|
|
103
|
+
"guessed at: a grouping separator (because '1.234,56' and "
|
|
104
|
+
"'1,234.56' are the same amount under opposite conventions), and "
|
|
105
|
+
"three or more fraction digits (because '1,234' is "
|
|
106
|
+
"indistinguishable from a comma-grouped 1234). Magnitude is not "
|
|
107
|
+
"a trigger -- '1000,00' parses. If this instance really formats "
|
|
108
|
+
"amounts this way, that is a finding worth recording -- report "
|
|
109
|
+
"the value."
|
|
110
|
+
)
|
|
111
|
+
return Decimal(text.replace(",", "."))
|
|
112
|
+
return value
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
OptionalDecimal = Annotated[Decimal | None, BeforeValidator(_parse_ev_decimal)]
|
|
116
|
+
"""An exact ``Decimal | None`` for an EasyVista currency column.
|
|
117
|
+
|
|
118
|
+
Accepts either decimal separator and maps the ``""`` sentinel to ``None``,
|
|
119
|
+
which is **not** the same answer as ``Decimal('0.00')`` — see
|
|
120
|
+
:func:`_parse_ev_decimal`.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
|
|
40
124
|
def _parse_with_context_formats(value: Any, info: ValidationInfo) -> datetime | None:
|
|
41
125
|
"""Try the caller's own timestamp formats, if it supplied any.
|
|
42
126
|
|
|
@@ -26,6 +26,7 @@ from __future__ import annotations
|
|
|
26
26
|
from collections.abc import Iterator, Sequence
|
|
27
27
|
from dataclasses import dataclass
|
|
28
28
|
from datetime import datetime
|
|
29
|
+
from decimal import Decimal
|
|
29
30
|
from functools import lru_cache
|
|
30
31
|
from typing import Any
|
|
31
32
|
|
|
@@ -141,7 +142,14 @@ def _scalar(value: Any) -> str | None:
|
|
|
141
142
|
return format_ev_datetime(value)
|
|
142
143
|
except ValueError:
|
|
143
144
|
return value.isoformat()
|
|
144
|
-
|
|
145
|
+
# ``Decimal`` joins ``str``/``int`` here because the read path retypes the
|
|
146
|
+
# currency columns (``TIME_COST``, ``CONTRACTUAL_COST``) into exact
|
|
147
|
+
# ``Decimal``s. Without this branch ``reference("TIME_COST")`` returned an
|
|
148
|
+
# EMPTY Reference on a populated column -- the same class of silent gap the
|
|
149
|
+
# ``datetime`` branch above was added to close. Rendered with ``str`` rather
|
|
150
|
+
# than the wire's own decimal comma: this extractor feeds labels and
|
|
151
|
+
# identifiers, not money formatting, and it must never raise.
|
|
152
|
+
if isinstance(value, (str, int, Decimal)) and str(value).strip():
|
|
145
153
|
return str(value).strip()
|
|
146
154
|
return None
|
|
147
155
|
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
`easyvista_python_client` ships both clients over one surface:
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/skills/easyvista-directory/SKILL.md
RENAMED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.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.10+, 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.
|
|
8
|
+
version: "0.3.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
@@ -130,6 +130,43 @@ Verified live 2026-08-28: tasks came back with `END_DATE_UT` and
|
|
|
130
130
|
mandatory; `PostTask` refuses a body missing either rather than letting the
|
|
131
131
|
server answer with a 590 that names no field.
|
|
132
132
|
|
|
133
|
+
**A task is write-only as a resource.** `POST requests/{rfc}/tasks` is the only
|
|
134
|
+
verb the route has, and the API declares **no task read route at all** (tier 2,
|
|
135
|
+
read 2026-09-02 on two 2025.3 deployments). So `create_task` returns an
|
|
136
|
+
`Action`, there is no `Task` read model and no `list_tasks` — a task is written
|
|
137
|
+
as a task and read back through `list_actions` like any other row. A GET against
|
|
138
|
+
the tasks path answers 403, which on this API proves nothing either way.
|
|
139
|
+
|
|
140
|
+
**No action-record column observed so far says which route created it**, so do
|
|
141
|
+
not try to reconstruct the distinction on the read side. In particular the effort
|
|
142
|
+
columns do not: measured over 1500 live rows 2026-09-02, 39 of 49
|
|
143
|
+
`Commentaire [Public]` comments carried a non-empty `ELAPSED_TIME` (one with
|
|
144
|
+
`TIME_COST='99,00'`), while 173 workflow rows carried an empty one. To pick
|
|
145
|
+
conversation out of a timeline, filter on an `action_type_id` allowlist your
|
|
146
|
+
administrator confirms, and use `action.is_workflow_generated` to drop the
|
|
147
|
+
workflow engine's own rows.
|
|
148
|
+
|
|
149
|
+
**`is_workflow_generated` needs `WORKFLOW_ID` projected, or it is always
|
|
150
|
+
`False`.** It reads that one column, and the column is NOT on the default
|
|
151
|
+
`list_actions` projection — so on default rows the filter silently drops nothing
|
|
152
|
+
and every workflow row survives. The same applies to all five effort columns:
|
|
153
|
+
unprojected they read `None`, which is indistinguishable from the `''` that means
|
|
154
|
+
"does not apply". Ask for them:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
actions = client.iter_actions(
|
|
158
|
+
"YOUR_RFC_NUMBER",
|
|
159
|
+
fields=[
|
|
160
|
+
"ACTION_ID", "ACTION_TYPE_ID", "WORKFLOW_ID",
|
|
161
|
+
"ELAPSED_TIME", "START_DATE_UT", "END_DATE_UT",
|
|
162
|
+
],
|
|
163
|
+
)
|
|
164
|
+
conversation = [
|
|
165
|
+
a for a in actions
|
|
166
|
+
if not a.is_workflow_generated and a.action_type_id in YOUR_COMMENT_TYPE_IDS
|
|
167
|
+
]
|
|
168
|
+
```
|
|
169
|
+
|
|
133
170
|
**If a caller creates an action and stops**, nothing is lost — the text is
|
|
134
171
|
stored — but the row renders without it until the action is ended. Use
|
|
135
172
|
**`end_action`**, which wraps the vendor's `PUT actions/{rfc_number}` /
|
|
@@ -532,3 +569,20 @@ with EasyvistaClient.from_env() as client:
|
|
|
532
569
|
columns. `getattr(record, "last_update")` raises `AttributeError` on an
|
|
533
570
|
`Action`; for code spanning record types, go through `classify_fields()` or
|
|
534
571
|
`.reference()`, where the wire alias is uniform.
|
|
572
|
+
- **On the effort columns, `''` and `'0'` are different answers.**
|
|
573
|
+
`elapsed_time` (minutes), `time_cost`, `contractual_cost`, `start_date_ut` and
|
|
574
|
+
`end_date_ut` are declared as of 0.3.0. `''` means the column does not apply
|
|
575
|
+
to this record and reads as `None`; `'0'` / `'0,00'` means it applies and is
|
|
576
|
+
zero, and reads as `0` / `Decimal("0.00")`. Do not normalise the two together
|
|
577
|
+
— that erases the only signal saying whether a record tracks effort at all.
|
|
578
|
+
The costs are exact `Decimal`s parsed from the API's decimal comma; an amount
|
|
579
|
+
with a grouping separator raises rather than being guessed at, and because a
|
|
580
|
+
page is validated all at once that fails the whole `list_actions` call.
|
|
581
|
+
- **`ACTION_LABEL_*` is the workflow STEP's label, not the action type's name**
|
|
582
|
+
(measured 2026-09-02, 1500 rows). Type 20 appeared under six labels:
|
|
583
|
+
`Analyse et résolution`, `Traitement`, `Traitement du refus`, `Traitement de
|
|
584
|
+
la demande`, `test` and `notif`. For the non-workflow
|
|
585
|
+
types (94, 95, 7) it is stable and is the real type name; for workflow types
|
|
586
|
+
it varies row to row, so never key on it. Types 14, 27 and 28 have an empty
|
|
587
|
+
label in every language column at both list and item level — the API cannot
|
|
588
|
+
name them, and only the admin console can.
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: "Requires Python 3.10+, 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.
|
|
8
|
+
version: "0.3.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
> **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_fields.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/_html.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/config.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/context.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/directory.py
RENAMED
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/filters.py
RENAMED
|
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
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/py.typed
RENAMED
|
File without changes
|
{easyvista_python_client-0.2.0 → easyvista_python_client-0.3.0}/easyvista_python_client/reporting.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|