bugdantic 0.2.6__tar.gz → 0.2.7__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.7}/PKG-INFO +5 -5
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic/bugzilla.py +107 -2
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic.egg-info/PKG-INFO +5 -5
- bugdantic-0.2.7/bugdantic.egg-info/requires.txt +8 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/pyproject.toml +5 -5
- {bugdantic-0.2.6 → bugdantic-0.2.7}/test/test_bugzilla.py +11 -0
- bugdantic-0.2.6/bugdantic.egg-info/requires.txt +0 -8
- {bugdantic-0.2.6 → bugdantic-0.2.7}/LICENSE +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/README.md +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic/__init__.py +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic/py.typed +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic.egg-info/SOURCES.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic.egg-info/dependency_links.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/bugdantic.egg-info/top_level.txt +0 -0
- {bugdantic-0.2.6 → bugdantic-0.2.7}/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.7
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import enum
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
@@ -7,7 +8,7 @@ from typing import Any, Mapping, MutableMapping, Optional, Sequence
|
|
|
7
8
|
from urllib.parse import urljoin
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
10
|
-
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
11
12
|
|
|
12
13
|
Json = dict[str, "Json"] | list["Json"] | str | int | float | bool | None
|
|
13
14
|
|
|
@@ -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,34 @@ 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: Optional[str] = None
|
|
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
|
+
@field_validator("data", mode="before")
|
|
360
|
+
@classmethod
|
|
361
|
+
def encode_data(cls, value: bytes | str) -> str:
|
|
362
|
+
if isinstance(value, bytes):
|
|
363
|
+
return base64.b64encode(value).decode("ascii")
|
|
364
|
+
return value
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class AttachmentCreateResponse(BaseModel):
|
|
368
|
+
ids: list[int]
|
|
369
|
+
|
|
370
|
+
|
|
295
371
|
@dataclass
|
|
296
372
|
class BugzillaConfig:
|
|
297
373
|
base_url: str
|
|
@@ -343,6 +419,9 @@ class Bugzilla:
|
|
|
343
419
|
|
|
344
420
|
if self.config.allow_writes or method in {"GET", "OPTIONS", "HEAD"}:
|
|
345
421
|
retry = 0
|
|
422
|
+
if self.config.max_retries < 0:
|
|
423
|
+
raise ValueError("max_retries must be at least 0")
|
|
424
|
+
response = None
|
|
346
425
|
while retry <= self.config.max_retries:
|
|
347
426
|
retry += 1
|
|
348
427
|
response = self.client.request(
|
|
@@ -350,6 +429,7 @@ class Bugzilla:
|
|
|
350
429
|
)
|
|
351
430
|
if response.status_code != 503:
|
|
352
431
|
break
|
|
432
|
+
assert response is not None
|
|
353
433
|
try:
|
|
354
434
|
response.raise_for_status()
|
|
355
435
|
except Exception as e:
|
|
@@ -490,3 +570,28 @@ class Bugzilla:
|
|
|
490
570
|
return update_result.bugs
|
|
491
571
|
|
|
492
572
|
return []
|
|
573
|
+
|
|
574
|
+
def create_attachment(
|
|
575
|
+
self, attachment: AttachmentCreate, bug_id: Optional[int] = None
|
|
576
|
+
) -> list[int]:
|
|
577
|
+
"""Add an attachment to one or more bugs"""
|
|
578
|
+
if bug_id is not None:
|
|
579
|
+
id_str = str(bug_id)
|
|
580
|
+
elif attachment.ids:
|
|
581
|
+
id_str = str(attachment.ids[0])
|
|
582
|
+
else:
|
|
583
|
+
raise ValueError("Missing bug ids for attachment")
|
|
584
|
+
|
|
585
|
+
path = f"bug/{id_str}/attachment"
|
|
586
|
+
|
|
587
|
+
json_body = attachment.model_dump(exclude_none=True)
|
|
588
|
+
if not json_body.get("ids"):
|
|
589
|
+
json_body["ids"] = [bug_id]
|
|
590
|
+
|
|
591
|
+
response = self.request("POST", path, json_body=json_body)
|
|
592
|
+
|
|
593
|
+
if self.config.allow_writes:
|
|
594
|
+
create_result = AttachmentCreateResponse.model_validate(response)
|
|
595
|
+
return create_result.ids
|
|
596
|
+
|
|
597
|
+
return []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugdantic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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.7"
|
|
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
|