bugdantic 0.2.6__tar.gz → 0.2.8__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.
- {bugdantic-0.2.6 → bugdantic-0.2.8}/PKG-INFO +5 -5
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic/bugzilla.py +151 -2
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic.egg-info/PKG-INFO +5 -5
- bugdantic-0.2.8/bugdantic.egg-info/requires.txt +8 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/pyproject.toml +5 -5
- {bugdantic-0.2.6 → bugdantic-0.2.8}/test/test_bugzilla.py +11 -0
- bugdantic-0.2.6/bugdantic.egg-info/requires.txt +0 -8
- {bugdantic-0.2.6 → bugdantic-0.2.8}/LICENSE +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/README.md +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic/__init__.py +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic/py.typed +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic.egg-info/SOURCES.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic.egg-info/dependency_links.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/bugdantic.egg-info/top_level.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugdantic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Bugzilla client based on pydantic
|
|
5
5
|
Author-email: James Graham <james@hoppipolla.co.uk>
|
|
6
6
|
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -11,8 +11,8 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: httpx[http2]>=0.28.1
|
|
12
12
|
Requires-Dist: pydantic>=2.11.3
|
|
13
13
|
Provides-Extra: test
|
|
14
|
-
Requires-Dist: mypy==1.
|
|
15
|
-
Requires-Dist: ruff==0.
|
|
16
|
-
Requires-Dist: pytest==8.4.
|
|
17
|
-
Requires-Dist: pytest-ruff==0.
|
|
14
|
+
Requires-Dist: mypy==1.18.2; extra == "test"
|
|
15
|
+
Requires-Dist: ruff==0.14.1; extra == "test"
|
|
16
|
+
Requires-Dist: pytest==8.4.2; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-ruff==0.5; extra == "test"
|
|
18
18
|
Dynamic: license-file
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import enum
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from datetime import datetime
|
|
6
|
-
from typing import Any, Mapping, MutableMapping, Optional, Sequence
|
|
7
|
+
from typing import Any, Mapping, MutableMapping, Optional, Sequence, Self
|
|
7
8
|
from urllib.parse import urljoin
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
@@ -55,7 +56,7 @@ class Flag(BaseModel):
|
|
|
55
56
|
modification_date: datetime
|
|
56
57
|
status: str
|
|
57
58
|
setter: str
|
|
58
|
-
requestee: str
|
|
59
|
+
requestee: Optional[str] = None
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
class Change(BaseModel):
|
|
@@ -71,6 +72,50 @@ class History(BaseModel):
|
|
|
71
72
|
changes: list[Change]
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
class BugComment(BaseModel):
|
|
76
|
+
"""A comment retrieved from a bug (cf. :class:`Comment`, used in updates)."""
|
|
77
|
+
|
|
78
|
+
id: Optional[int] = None
|
|
79
|
+
bug_id: Optional[int] = None
|
|
80
|
+
attachment_id: Optional[int] = None
|
|
81
|
+
count: Optional[int] = None
|
|
82
|
+
text: Optional[str] = None
|
|
83
|
+
raw_text: Optional[str] = None
|
|
84
|
+
creator: Optional[str] = None
|
|
85
|
+
time: Optional[datetime] = None
|
|
86
|
+
creation_time: Optional[datetime] = None
|
|
87
|
+
is_private: Optional[bool] = None
|
|
88
|
+
is_markdown: Optional[bool] = None
|
|
89
|
+
tags: Optional[list[str]] = None
|
|
90
|
+
|
|
91
|
+
model_config = ConfigDict(extra="allow")
|
|
92
|
+
|
|
93
|
+
def to_dict(self) -> dict[str, Any]:
|
|
94
|
+
return self.model_dump(exclude_unset=True)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Attachment(BaseModel):
|
|
98
|
+
id: Optional[int] = None
|
|
99
|
+
bug_id: Optional[int] = None
|
|
100
|
+
file_name: Optional[str] = None
|
|
101
|
+
summary: Optional[str] = None
|
|
102
|
+
content_type: Optional[str] = None
|
|
103
|
+
creator: Optional[str] = None
|
|
104
|
+
creation_time: Optional[datetime] = None
|
|
105
|
+
last_change_time: Optional[datetime] = None
|
|
106
|
+
size: Optional[int] = None
|
|
107
|
+
data: Optional[str] = None
|
|
108
|
+
is_private: Optional[bool] = None
|
|
109
|
+
is_obsolete: Optional[bool] = None
|
|
110
|
+
is_patch: Optional[bool] = None
|
|
111
|
+
flags: Optional[list[Flag]] = None
|
|
112
|
+
|
|
113
|
+
model_config = ConfigDict(extra="allow")
|
|
114
|
+
|
|
115
|
+
def to_dict(self) -> dict[str, Any]:
|
|
116
|
+
return self.model_dump(exclude_unset=True)
|
|
117
|
+
|
|
118
|
+
|
|
74
119
|
class Bug(BaseModel):
|
|
75
120
|
actual_time: Optional[float] = None
|
|
76
121
|
alias: Optional[str] = None
|
|
@@ -123,6 +168,9 @@ class Bug(BaseModel):
|
|
|
123
168
|
cf_webcompat_priority: Optional[str] = None
|
|
124
169
|
cf_webcompat_score: Optional[str] = None
|
|
125
170
|
|
|
171
|
+
comments: Optional[list[BugComment]] = None
|
|
172
|
+
attachments: Optional[list[Attachment]] = None
|
|
173
|
+
|
|
126
174
|
model_config = ConfigDict(extra="allow")
|
|
127
175
|
|
|
128
176
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -292,6 +340,78 @@ class BugsUpdateResponse(BaseModel):
|
|
|
292
340
|
faults: Optional[list[Any]] = None
|
|
293
341
|
|
|
294
342
|
|
|
343
|
+
# Data models for creating attachments
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class AttachmentCreate(BaseModel):
|
|
347
|
+
ids: Optional[list[int | str]] = None
|
|
348
|
+
data: str
|
|
349
|
+
file_name: str
|
|
350
|
+
summary: str
|
|
351
|
+
content_type: str
|
|
352
|
+
comment: Optional[str] = None
|
|
353
|
+
is_markdown: Optional[bool] = None
|
|
354
|
+
is_patch: Optional[bool] = None
|
|
355
|
+
is_private: Optional[bool] = None
|
|
356
|
+
flags: Optional[list[FlagChange]] = None
|
|
357
|
+
bug_flags: Optional[list[FlagChange]] = None
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def from_raw_data(
|
|
361
|
+
cls,
|
|
362
|
+
data: str | bytes,
|
|
363
|
+
file_name: str,
|
|
364
|
+
summary: str,
|
|
365
|
+
content_type: str,
|
|
366
|
+
ids: Optional[list[int | str]] = None,
|
|
367
|
+
comment: Optional[str] = None,
|
|
368
|
+
is_markdown: Optional[bool] = None,
|
|
369
|
+
is_patch: Optional[bool] = None,
|
|
370
|
+
is_private: Optional[bool] = None,
|
|
371
|
+
flags: Optional[list[FlagChange]] = None,
|
|
372
|
+
bug_flags: Optional[list[FlagChange]] = None,
|
|
373
|
+
) -> Self:
|
|
374
|
+
if isinstance(data, str):
|
|
375
|
+
data = data.encode("utf8")
|
|
376
|
+
return cls(
|
|
377
|
+
ids=ids,
|
|
378
|
+
data=base64.b64encode(data).decode("ascii"),
|
|
379
|
+
file_name=file_name,
|
|
380
|
+
summary=summary,
|
|
381
|
+
content_type=content_type,
|
|
382
|
+
comment=comment,
|
|
383
|
+
is_markdown=is_markdown,
|
|
384
|
+
is_patch=is_patch,
|
|
385
|
+
is_private=is_private,
|
|
386
|
+
flags=flags,
|
|
387
|
+
bug_flags=bug_flags,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class AttachmentCreateResponse(BaseModel):
|
|
392
|
+
attacher: str
|
|
393
|
+
bug_id: int
|
|
394
|
+
content_type: str
|
|
395
|
+
creation_time: datetime
|
|
396
|
+
creator: str
|
|
397
|
+
creator_detail: User
|
|
398
|
+
data: str
|
|
399
|
+
description: str
|
|
400
|
+
file_name: str
|
|
401
|
+
flags: Optional[list[FlagChange]]
|
|
402
|
+
id: int
|
|
403
|
+
is_obsolete: bool
|
|
404
|
+
is_patch: bool
|
|
405
|
+
is_private: bool
|
|
406
|
+
last_change_time: datetime
|
|
407
|
+
size: int
|
|
408
|
+
summary: str
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class AttachmentsCreateResponse(BaseModel):
|
|
412
|
+
attachments: Mapping[str, AttachmentCreateResponse]
|
|
413
|
+
|
|
414
|
+
|
|
295
415
|
@dataclass
|
|
296
416
|
class BugzillaConfig:
|
|
297
417
|
base_url: str
|
|
@@ -343,6 +463,9 @@ class Bugzilla:
|
|
|
343
463
|
|
|
344
464
|
if self.config.allow_writes or method in {"GET", "OPTIONS", "HEAD"}:
|
|
345
465
|
retry = 0
|
|
466
|
+
if self.config.max_retries < 0:
|
|
467
|
+
raise ValueError("max_retries must be at least 0")
|
|
468
|
+
response = None
|
|
346
469
|
while retry <= self.config.max_retries:
|
|
347
470
|
retry += 1
|
|
348
471
|
response = self.client.request(
|
|
@@ -350,6 +473,7 @@ class Bugzilla:
|
|
|
350
473
|
)
|
|
351
474
|
if response.status_code != 503:
|
|
352
475
|
break
|
|
476
|
+
assert response is not None
|
|
353
477
|
try:
|
|
354
478
|
response.raise_for_status()
|
|
355
479
|
except Exception as e:
|
|
@@ -490,3 +614,28 @@ class Bugzilla:
|
|
|
490
614
|
return update_result.bugs
|
|
491
615
|
|
|
492
616
|
return []
|
|
617
|
+
|
|
618
|
+
def create_attachment(
|
|
619
|
+
self, attachment: AttachmentCreate, bug_id: Optional[int] = None
|
|
620
|
+
) -> list[AttachmentCreateResponse]:
|
|
621
|
+
"""Add an attachment to one or more bugs"""
|
|
622
|
+
if bug_id is not None:
|
|
623
|
+
id_str = str(bug_id)
|
|
624
|
+
elif attachment.ids:
|
|
625
|
+
id_str = str(attachment.ids[0])
|
|
626
|
+
else:
|
|
627
|
+
raise ValueError("Missing bug ids for attachment")
|
|
628
|
+
|
|
629
|
+
path = f"bug/{id_str}/attachment"
|
|
630
|
+
|
|
631
|
+
json_body = attachment.model_dump(exclude_none=True)
|
|
632
|
+
if not json_body.get("ids"):
|
|
633
|
+
json_body["ids"] = [bug_id]
|
|
634
|
+
|
|
635
|
+
response = self.request("POST", path, json_body=json_body)
|
|
636
|
+
|
|
637
|
+
if self.config.allow_writes:
|
|
638
|
+
create_result = AttachmentsCreateResponse.model_validate(response)
|
|
639
|
+
return list(create_result.attachments.values())
|
|
640
|
+
|
|
641
|
+
return []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugdantic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Bugzilla client based on pydantic
|
|
5
5
|
Author-email: James Graham <james@hoppipolla.co.uk>
|
|
6
6
|
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -11,8 +11,8 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: httpx[http2]>=0.28.1
|
|
12
12
|
Requires-Dist: pydantic>=2.11.3
|
|
13
13
|
Provides-Extra: test
|
|
14
|
-
Requires-Dist: mypy==1.
|
|
15
|
-
Requires-Dist: ruff==0.
|
|
16
|
-
Requires-Dist: pytest==8.4.
|
|
17
|
-
Requires-Dist: pytest-ruff==0.
|
|
14
|
+
Requires-Dist: mypy==1.18.2; extra == "test"
|
|
15
|
+
Requires-Dist: ruff==0.14.1; extra == "test"
|
|
16
|
+
Requires-Dist: pytest==8.4.2; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-ruff==0.5; extra == "test"
|
|
18
18
|
Dynamic: license-file
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
{name = "James Graham", email = "james@hoppipolla.co.uk"}
|
|
6
6
|
]
|
|
7
7
|
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.8"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
classifiers = [
|
|
11
11
|
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
|
@@ -20,8 +20,8 @@ dependencies = [
|
|
|
20
20
|
|
|
21
21
|
[project.optional-dependencies]
|
|
22
22
|
test = [
|
|
23
|
-
"mypy==1.
|
|
24
|
-
"ruff==0.
|
|
25
|
-
"pytest==8.4.
|
|
26
|
-
"pytest-ruff==0.
|
|
23
|
+
"mypy==1.18.2",
|
|
24
|
+
"ruff==0.14.1",
|
|
25
|
+
"pytest==8.4.2",
|
|
26
|
+
"pytest-ruff==0.5",
|
|
27
27
|
]
|
|
@@ -32,3 +32,14 @@ def test_serach_include_history(bugzilla):
|
|
|
32
32
|
for expected_id, bug in zip(bugs, sorted(result, key=lambda x: x.id)):
|
|
33
33
|
assert bug.id == expected_id
|
|
34
34
|
assert isinstance(bug.history, list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_search_include_comments_and_attachments(bugzilla):
|
|
38
|
+
bugs = [423488, 1749533]
|
|
39
|
+
result = bugzilla.search(
|
|
40
|
+
{"id": bugs}, include_fields=["id", "comments", "attachments"]
|
|
41
|
+
)
|
|
42
|
+
for expected_id, bug in zip(bugs, sorted(result, key=lambda x: x.id)):
|
|
43
|
+
assert bug.id == expected_id
|
|
44
|
+
assert isinstance(bug.comments, list)
|
|
45
|
+
assert isinstance(bug.attachments, list)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|