kd-cli 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kd/__init__.py +45 -0
- kd/api/__init__.py +15 -0
- kd/api/articles.py +232 -0
- kd/api/http.py +234 -0
- kd/api/hub.py +69 -0
- kd/api/saved_queries.py +76 -0
- kd/api/tags.py +107 -0
- kd/api/youtrack.py +414 -0
- kd/article_marker.py +348 -0
- kd/auth.py +96 -0
- kd/cli/__init__.py +5 -0
- kd/cli/core/__init__.py +25 -0
- kd/cli/core/command_base.py +133 -0
- kd/cli/core/command_parser.py +246 -0
- kd/cli/core/namespaces.py +175 -0
- kd/cli/core/option_registry.py +265 -0
- kd/cli/input.py +42 -0
- kd/cli/main.py +612 -0
- kd/cli/namespaces/__init__.py +38 -0
- kd/cli/namespaces/alias/__init__.py +87 -0
- kd/cli/namespaces/article/__init__.py +843 -0
- kd/cli/namespaces/init.py +42 -0
- kd/cli/namespaces/issue/__init__.py +959 -0
- kd/cli/namespaces/primer/__init__.py +337 -0
- kd/cli/namespaces/project/__init__.py +243 -0
- kd/cli/namespaces/query/__init__.py +465 -0
- kd/cli/namespaces/search/__init__.py +82 -0
- kd/cli/namespaces/system/__init__.py +528 -0
- kd/cli/namespaces/tag/__init__.py +337 -0
- kd/cli/namespaces/time/__init__.py +307 -0
- kd/cli/namespaces/user/__init__.py +99 -0
- kd/cli/output.py +287 -0
- kd/cli/tree.py +65 -0
- kd/client.py +1953 -0
- kd/config.py +853 -0
- kd/connection.py +156 -0
- kd/debug.py +107 -0
- kd/exceptions.py +189 -0
- kd/history.py +169 -0
- kd/models/__init__.py +75 -0
- kd/models/article.py +64 -0
- kd/models/assist.py +47 -0
- kd/models/attachment.py +20 -0
- kd/models/comment.py +25 -0
- kd/models/common.py +39 -0
- kd/models/issue.py +67 -0
- kd/models/project.py +60 -0
- kd/models/saved_query.py +54 -0
- kd/models/search.py +20 -0
- kd/models/tag.py +30 -0
- kd/models/user.py +42 -0
- kd/models/work_item.py +59 -0
- kd/primer.py +231 -0
- kd/query_parser.py +181 -0
- kd/resources/__init__.py +1 -0
- kd/resources/command-synonyms.yaml +19 -0
- kd/resources/config-template.yaml +71 -0
- kd/resources/error-messages.yaml +101 -0
- kd_cli-2.0.4.dist-info/METADATA +149 -0
- kd_cli-2.0.4.dist-info/RECORD +64 -0
- kd_cli-2.0.4.dist-info/WHEEL +5 -0
- kd_cli-2.0.4.dist-info/entry_points.txt +2 -0
- kd_cli-2.0.4.dist-info/licenses/LICENSE +21 -0
- kd_cli-2.0.4.dist-info/top_level.txt +1 -0
kd/models/article.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Article models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
from kd.models.tag import Tag
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArticleRef(IdEntity):
|
|
12
|
+
"""Lightweight cross-reference to an article.
|
|
13
|
+
|
|
14
|
+
Carries ``id_readable`` because articles are addressed by their
|
|
15
|
+
readable id (e.g. ``FB-A-1``) in the CLI. ``NamedEntity`` only has
|
|
16
|
+
``id`` and ``name`` and is reused for leaders, reporters, authors —
|
|
17
|
+
entities that do not have a human-readable id — so a dedicated type
|
|
18
|
+
keeps the shared base clean.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
22
|
+
|
|
23
|
+
id_readable: str | None = Field(default=None, alias="idReadable")
|
|
24
|
+
name: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Article(IdEntity):
|
|
28
|
+
"""Knowledge base article.
|
|
29
|
+
|
|
30
|
+
The ``content`` field is populated only when detail fields are
|
|
31
|
+
requested (get, create, update responses). List responses omit it
|
|
32
|
+
to keep payloads lightweight.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id_readable: str = Field(alias="idReadable")
|
|
36
|
+
summary: str
|
|
37
|
+
content: str | None = None
|
|
38
|
+
project: NamedEntity | None = None
|
|
39
|
+
reporter: NamedEntity | None = None
|
|
40
|
+
created: int | None = None
|
|
41
|
+
updated: int | None = None
|
|
42
|
+
has_children: bool = Field(default=False, alias="hasChildren")
|
|
43
|
+
parent_article: ArticleRef | None = Field(default=None, alias="parentArticle")
|
|
44
|
+
tags: list[Tag] = Field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ArticleCreate(BaseModel):
|
|
48
|
+
"""Input for creating an article."""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
51
|
+
|
|
52
|
+
project: IdEntity
|
|
53
|
+
summary: str
|
|
54
|
+
content: str | None = None
|
|
55
|
+
parent_article: IdEntity | None = Field(default=None, alias="parentArticle")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ArticleUpdate(BaseModel):
|
|
59
|
+
"""Input for updating an article."""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
62
|
+
|
|
63
|
+
summary: str | None = None
|
|
64
|
+
content: str | None = None
|
kd/models/assist.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Query and command assist models.
|
|
2
|
+
|
|
3
|
+
Used for ``/api/search/assist`` and ``/api/commands/assist`` — YouTrack's
|
|
4
|
+
autocomplete helpers for search queries and issue-command strings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AssistSuggestion(BaseModel):
|
|
13
|
+
"""One suggestion returned by an assist endpoint.
|
|
14
|
+
|
|
15
|
+
``option`` is the replacement string; the caret position fields describe
|
|
16
|
+
where the replacement should be inserted and which part of the current
|
|
17
|
+
input it matched. ``prefix``/``suffix`` are the characters to wrap around
|
|
18
|
+
``option`` when inserting (e.g. ``{`` / ``}`` for project keys with
|
|
19
|
+
spaces).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
23
|
+
|
|
24
|
+
option: str
|
|
25
|
+
description: str | None = None
|
|
26
|
+
prefix: str | None = None
|
|
27
|
+
suffix: str | None = None
|
|
28
|
+
completion_start: int | None = Field(default=None, alias="completionStart")
|
|
29
|
+
completion_end: int | None = Field(default=None, alias="completionEnd")
|
|
30
|
+
matching_start: int | None = Field(default=None, alias="matchingStart")
|
|
31
|
+
matching_end: int | None = Field(default=None, alias="matchingEnd")
|
|
32
|
+
group: str | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AssistResponse(BaseModel):
|
|
36
|
+
"""Envelope returned by both assist endpoints.
|
|
37
|
+
|
|
38
|
+
``query`` and ``caret`` echo the caller's input when requested in the
|
|
39
|
+
field set; ``commands/assist`` does not always return ``caret``. Both are
|
|
40
|
+
optional to accommodate the partial shape.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
44
|
+
|
|
45
|
+
query: str | None = None
|
|
46
|
+
caret: int | None = None
|
|
47
|
+
suggestions: list[AssistSuggestion] = Field(default_factory=list)
|
kd/models/attachment.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Attachment models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Attachment(IdEntity):
|
|
11
|
+
"""Issue or article attachment metadata."""
|
|
12
|
+
|
|
13
|
+
name: str
|
|
14
|
+
size: int | None = None
|
|
15
|
+
mime_type: str | None = Field(default=None, alias="mimeType")
|
|
16
|
+
extension: str | None = None
|
|
17
|
+
author: NamedEntity | None = None
|
|
18
|
+
created: int | None = None
|
|
19
|
+
updated: int | None = None
|
|
20
|
+
url: str | None = None
|
kd/models/comment.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Comment models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Comment(IdEntity):
|
|
11
|
+
"""Issue or article comment."""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
14
|
+
|
|
15
|
+
text: str
|
|
16
|
+
author: NamedEntity | None = None
|
|
17
|
+
created: int | None = None
|
|
18
|
+
updated: int | None = None
|
|
19
|
+
pinned: bool | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CommentCreate(BaseModel):
|
|
23
|
+
"""Input for adding a comment."""
|
|
24
|
+
|
|
25
|
+
text: str
|
kd/models/common.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Shared model types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IdEntity(BaseModel):
|
|
9
|
+
"""Base for all entities with an id field."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
12
|
+
|
|
13
|
+
id: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NamedEntity(IdEntity):
|
|
17
|
+
"""Entity with id and name — used for nested references (leader, reporter, author)."""
|
|
18
|
+
|
|
19
|
+
name: str | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FieldSet:
|
|
23
|
+
"""Helper for building YouTrack's explicit field selection strings.
|
|
24
|
+
|
|
25
|
+
Usage::
|
|
26
|
+
|
|
27
|
+
str(FieldSet("id", "name", leader=FieldSet("id", "name")))
|
|
28
|
+
# => "id,name,leader(id,name)"
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, *fields: str, **nested: FieldSet) -> None:
|
|
32
|
+
self._fields = fields
|
|
33
|
+
self._nested = nested
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
parts: list[str] = list(self._fields)
|
|
37
|
+
for name, subset in self._nested.items():
|
|
38
|
+
parts.append(f"{name}({subset})")
|
|
39
|
+
return ",".join(parts)
|
kd/models/issue.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Issue models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
from kd.models.attachment import Attachment
|
|
10
|
+
from kd.models.comment import Comment
|
|
11
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CustomField(BaseModel):
|
|
15
|
+
"""Custom field value representation.
|
|
16
|
+
|
|
17
|
+
``value`` is Any because YouTrack custom fields can be dicts with
|
|
18
|
+
a ``name`` key, primitives, or lists depending on field type.
|
|
19
|
+
``field_type`` serializes as ``$type`` — required by YouTrack for
|
|
20
|
+
create/update operations on enum custom fields.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
value: Any = None
|
|
27
|
+
field_type: str | None = Field(default=None, alias="$type")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Issue(IdEntity):
|
|
31
|
+
"""Full issue representation.
|
|
32
|
+
|
|
33
|
+
The ``comments``, ``links``, and ``attachments`` fields are populated
|
|
34
|
+
only when a rich field set is requested from the API.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
id_readable: str = Field(alias="idReadable")
|
|
38
|
+
summary: str
|
|
39
|
+
description: str | None = None
|
|
40
|
+
project: NamedEntity | None = None
|
|
41
|
+
reporter: NamedEntity | None = None
|
|
42
|
+
created: int | None = None
|
|
43
|
+
custom_fields: list[CustomField] = Field(default_factory=list, alias="customFields")
|
|
44
|
+
comments: list[Comment] | None = None
|
|
45
|
+
links: list[dict[str, Any]] | None = None
|
|
46
|
+
attachments: list[Attachment] | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class IssueCreate(BaseModel):
|
|
50
|
+
"""Input for creating an issue."""
|
|
51
|
+
|
|
52
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
53
|
+
|
|
54
|
+
project: IdEntity
|
|
55
|
+
summary: str
|
|
56
|
+
description: str | None = None
|
|
57
|
+
custom_fields: list[CustomField] | None = Field(default=None, alias="customFields")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IssueUpdate(BaseModel):
|
|
61
|
+
"""Input for updating an issue."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
64
|
+
|
|
65
|
+
summary: str | None = None
|
|
66
|
+
description: str | None = None
|
|
67
|
+
custom_fields: list[CustomField] | None = Field(default=None, alias="customFields")
|
kd/models/project.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Project models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Project(IdEntity):
|
|
11
|
+
"""Full project representation."""
|
|
12
|
+
|
|
13
|
+
name: str
|
|
14
|
+
short_name: str = Field(alias="shortName")
|
|
15
|
+
description: str | None = None
|
|
16
|
+
archived: bool = False
|
|
17
|
+
ring_id: str | None = Field(default=None, alias="ringId")
|
|
18
|
+
leader: NamedEntity | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BundleValue(BaseModel):
|
|
22
|
+
"""A single allowed value within an enum or state bundle."""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
ordinal: int = 0
|
|
28
|
+
is_resolved: bool | None = Field(default=None, alias="isResolved")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProjectCustomField(BaseModel):
|
|
32
|
+
"""Custom field attached to a project (flattened from nested API shape).
|
|
33
|
+
|
|
34
|
+
Constructed by the SDK from the raw API response, not parsed directly.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
field_type_id: str
|
|
39
|
+
project_field_type: str
|
|
40
|
+
can_be_empty: bool = True
|
|
41
|
+
empty_field_text: str | None = None
|
|
42
|
+
bundle_values: list[BundleValue] = Field(default_factory=list)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ProjectCreate(BaseModel):
|
|
46
|
+
"""Input for creating a project."""
|
|
47
|
+
|
|
48
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
short_name: str = Field(alias="shortName")
|
|
52
|
+
leader: IdEntity | None = None # Create only needs id
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TimeTrackingSettings(BaseModel):
|
|
56
|
+
"""Time tracking configuration per project."""
|
|
57
|
+
|
|
58
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
59
|
+
|
|
60
|
+
enabled: bool = False
|
kd/models/saved_query.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Saved query models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SavedQuery(IdEntity):
|
|
11
|
+
"""YouTrack saved query (saved search).
|
|
12
|
+
|
|
13
|
+
Minimal field set: id, name, query text, owner reference. Sharing settings
|
|
14
|
+
(``readSharingSettings`` / ``updateSharingSettings``) are intentionally
|
|
15
|
+
excluded from the default field parameter — personal CRUD works without
|
|
16
|
+
them, and modelling them pulls in a permissions surface that belongs to
|
|
17
|
+
a future milestone.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
name: str
|
|
21
|
+
query: str
|
|
22
|
+
owner: NamedEntity | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SavedQueryCreate(BaseModel):
|
|
26
|
+
"""Input for creating a saved query."""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
query: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SavedQueryUpdate(BaseModel):
|
|
35
|
+
"""Partial update for a saved query.
|
|
36
|
+
|
|
37
|
+
All fields optional. Dump with ``exclude_none=True`` so the POST body only
|
|
38
|
+
carries the fields the caller actually wants to change; YouTrack preserves
|
|
39
|
+
omitted fields (live-verified on ``vod.youtrack.cloud`` 2026-04-05).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
43
|
+
|
|
44
|
+
name: str | None = None
|
|
45
|
+
query: str | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class QueryValidationResult(BaseModel):
|
|
49
|
+
"""Result of validating a query string against the YouTrack search API."""
|
|
50
|
+
|
|
51
|
+
valid: bool
|
|
52
|
+
query: str
|
|
53
|
+
error: str | None = None
|
|
54
|
+
suggestion: str | None = None
|
kd/models/search.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Search result model for cross-entity search."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchResult(BaseModel):
|
|
9
|
+
"""Normalized result from cross-entity search.
|
|
10
|
+
|
|
11
|
+
Each result carries a type discriminator so consumers can identify
|
|
12
|
+
the source entity kind without inspecting the id format.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
16
|
+
|
|
17
|
+
type: str
|
|
18
|
+
id: str
|
|
19
|
+
title: str
|
|
20
|
+
project: str
|
kd/models/tag.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Tag models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TagColor(BaseModel):
|
|
11
|
+
"""Tag color with hex foreground/background values."""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
14
|
+
|
|
15
|
+
id: str
|
|
16
|
+
background: str | None = None
|
|
17
|
+
foreground: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Tag(IdEntity):
|
|
21
|
+
"""YouTrack tag.
|
|
22
|
+
|
|
23
|
+
Tags are shared entities that can be applied to both issues and articles.
|
|
24
|
+
The ``owner`` field is populated when detail fields are requested.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
owner: NamedEntity | None = None
|
|
29
|
+
color: TagColor | None = None
|
|
30
|
+
untag_on_resolve: bool = Field(default=False, alias="untagOnResolve")
|
kd/models/user.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""User models (Hub API entities)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class User(IdEntity):
|
|
11
|
+
"""User representation (works for both YT and Hub API responses)."""
|
|
12
|
+
|
|
13
|
+
login: str
|
|
14
|
+
name: str
|
|
15
|
+
email: str | None = None
|
|
16
|
+
ring_id: str | None = Field(default=None, alias="ringId")
|
|
17
|
+
banned: bool = False
|
|
18
|
+
guest: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UserCreate(BaseModel):
|
|
22
|
+
"""Input for creating a user via Hub API."""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
25
|
+
|
|
26
|
+
login: str
|
|
27
|
+
name: str
|
|
28
|
+
email: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Group(IdEntity):
|
|
32
|
+
"""User group representation."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
users_count: int | None = Field(default=None, alias="usersCount")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProjectTeam(IdEntity):
|
|
39
|
+
"""Project team (auto-created group per project)."""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
project: NamedEntity | None = None
|
kd/models/work_item.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Work item (time tracking) models.
|
|
2
|
+
|
|
3
|
+
Note: REST API requires type ID, not name. The SDK resolves names to IDs.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
from kd.models.common import IdEntity, NamedEntity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkItemType(IdEntity):
|
|
14
|
+
"""Work item type (Development, Testing, Documentation, etc.)."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
auto_attached: bool = Field(default=False, alias="autoAttached")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Duration(BaseModel):
|
|
21
|
+
"""Duration in minutes, matching the API's duration object."""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
24
|
+
|
|
25
|
+
minutes: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorkItem(IdEntity):
|
|
29
|
+
"""Time entry on an issue."""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
32
|
+
|
|
33
|
+
duration: Duration
|
|
34
|
+
text: str | None = None
|
|
35
|
+
type: WorkItemType | None = None
|
|
36
|
+
author: NamedEntity | None = None
|
|
37
|
+
date: int | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WorkItemCreate(BaseModel):
|
|
41
|
+
"""Input for logging work."""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
44
|
+
|
|
45
|
+
duration: Duration
|
|
46
|
+
text: str | None = None
|
|
47
|
+
type: IdEntity | None = None
|
|
48
|
+
date: int | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class WorkItemUpdate(BaseModel):
|
|
52
|
+
"""Input for editing a work item. All fields optional."""
|
|
53
|
+
|
|
54
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
55
|
+
|
|
56
|
+
duration: Duration | None = None
|
|
57
|
+
text: str | None = None
|
|
58
|
+
type: IdEntity | None = None
|
|
59
|
+
date: int | None = None
|