bugdantic 0.6.1__tar.gz → 0.6.2__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.6.1 → bugdantic-0.6.2}/PKG-INFO +1 -1
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic/bugzilla.py +149 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic.egg-info/PKG-INFO +1 -1
- {bugdantic-0.6.1 → bugdantic-0.6.2}/pyproject.toml +1 -1
- {bugdantic-0.6.1 → bugdantic-0.6.2}/test/test_bugzilla.py +47 -1
- {bugdantic-0.6.1 → bugdantic-0.6.2}/LICENSE +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/README.md +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic/__init__.py +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic/py.typed +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic/userstory.py +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic.egg-info/SOURCES.txt +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic.egg-info/dependency_links.txt +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic.egg-info/requires.txt +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/bugdantic.egg-info/top_level.txt +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/setup.cfg +0 -0
- {bugdantic-0.6.1 → bugdantic-0.6.2}/test/test_userstory.py +0 -0
|
@@ -9,6 +9,7 @@ from datetime import datetime
|
|
|
9
9
|
from typing import (
|
|
10
10
|
Any,
|
|
11
11
|
Generic,
|
|
12
|
+
Literal,
|
|
12
13
|
Optional,
|
|
13
14
|
Self,
|
|
14
15
|
TypeVar,
|
|
@@ -487,6 +488,100 @@ def comments_response_model(
|
|
|
487
488
|
)
|
|
488
489
|
|
|
489
490
|
|
|
491
|
+
# Data models for getting products
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class FlagType(BaseModel):
|
|
495
|
+
id: Optional[int] = None
|
|
496
|
+
name: Optional[str] = None
|
|
497
|
+
description: Optional[str] = None
|
|
498
|
+
cc_list: Optional[str] = None
|
|
499
|
+
sort_key: Optional[int] = None
|
|
500
|
+
is_active: Optional[bool] = None
|
|
501
|
+
is_requestable: Optional[bool] = None
|
|
502
|
+
is_requesteeble: Optional[bool] = None
|
|
503
|
+
is_multiplicable: Optional[bool] = None
|
|
504
|
+
grant_group: Optional[int] = None
|
|
505
|
+
request_group: Optional[int] = None
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class ComponentFlagTypes(BaseModel):
|
|
509
|
+
bug: Optional[list[FlagType]] = None
|
|
510
|
+
attachment: Optional[list[FlagType]] = None
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class ProductComponent(BaseModel):
|
|
514
|
+
id: Optional[int] = None
|
|
515
|
+
name: Optional[str] = None
|
|
516
|
+
description: Optional[str] = None
|
|
517
|
+
default_assigned_to: Optional[str] = None
|
|
518
|
+
default_qa_contact: Optional[str] = None
|
|
519
|
+
triage_owner: Optional[str] = None
|
|
520
|
+
team_name: Optional[str] = None
|
|
521
|
+
sort_key: Optional[int] = None
|
|
522
|
+
is_active: Optional[bool] = None
|
|
523
|
+
default_bug_type: Optional[str] = None
|
|
524
|
+
flag_types: Optional[ComponentFlagTypes] = None
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
class ProductVersion(BaseModel):
|
|
528
|
+
id: Optional[int] = None
|
|
529
|
+
name: Optional[str] = None
|
|
530
|
+
sort_key: Optional[int] = None
|
|
531
|
+
is_active: Optional[bool] = None
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class ProductMilestone(BaseModel):
|
|
535
|
+
id: Optional[int] = None
|
|
536
|
+
name: Optional[str] = None
|
|
537
|
+
sort_key: Optional[int] = None
|
|
538
|
+
is_active: Optional[bool] = None
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class Product(BaseModel):
|
|
542
|
+
id: Optional[int] = None
|
|
543
|
+
name: Optional[str] = None
|
|
544
|
+
description: Optional[str] = None
|
|
545
|
+
is_active: Optional[bool] = None
|
|
546
|
+
default_bug_type: Optional[str] = None
|
|
547
|
+
default_milestone: Optional[str] = None
|
|
548
|
+
default_version: Optional[str] = None
|
|
549
|
+
has_unconfirmed: Optional[bool] = None
|
|
550
|
+
classification: Optional[str] = None
|
|
551
|
+
components: Optional[list[ProductComponent]] = None
|
|
552
|
+
versions: Optional[list[ProductVersion]] = None
|
|
553
|
+
milestones: Optional[list[ProductMilestone]] = None
|
|
554
|
+
|
|
555
|
+
# BMO specific fields
|
|
556
|
+
default_platform: Optional[str] = None
|
|
557
|
+
default_op_sys: Optional[str] = None
|
|
558
|
+
default_security_group: Optional[str] = None
|
|
559
|
+
|
|
560
|
+
model_config = ConfigDict(extra="allow")
|
|
561
|
+
|
|
562
|
+
def to_dict(self) -> dict[str, Any]:
|
|
563
|
+
return self.model_dump(exclude_unset=True)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
ProductAccess = Literal["accessible", "selectable", "enterable"]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
ProductType = TypeVar("ProductType", bound=BaseModel)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class ProductsResponseGeneric(BaseModel, Generic[ProductType]):
|
|
573
|
+
products: list[ProductType] = []
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def products_response_model(
|
|
577
|
+
product_type: type[ProductType],
|
|
578
|
+
) -> type[ProductsResponseGeneric[ProductType]]:
|
|
579
|
+
return cast(
|
|
580
|
+
type[ProductsResponseGeneric[ProductType]],
|
|
581
|
+
ProductsResponseGeneric.__class_getitem__(product_type),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
490
585
|
class ErrorResponse(BaseModel):
|
|
491
586
|
code: int
|
|
492
587
|
message: str
|
|
@@ -770,6 +865,60 @@ class Bugzilla:
|
|
|
770
865
|
comment_ids, comment_type, include_fields, exclude_fields, page_size
|
|
771
866
|
)
|
|
772
867
|
|
|
868
|
+
def _products(
|
|
869
|
+
self,
|
|
870
|
+
product_type: type[ProductType],
|
|
871
|
+
ids: Optional[Sequence[int]] = None,
|
|
872
|
+
names: Optional[Sequence[str]] = None,
|
|
873
|
+
access: Sequence[ProductAccess] = ("accessible",),
|
|
874
|
+
include_fields: Optional[list[str]] = None,
|
|
875
|
+
exclude_fields: Optional[list[str]] = None,
|
|
876
|
+
) -> list[ProductType]:
|
|
877
|
+
"""Get products specified by id, name, or access type.
|
|
878
|
+
|
|
879
|
+
Products that don't exist or aren't accessible to the user are
|
|
880
|
+
silently omitted."""
|
|
881
|
+
params: dict[str, Sequence[QueryValue]] = {"type": list(access)}
|
|
882
|
+
if ids is not None:
|
|
883
|
+
params["ids"] = [str(item) for item in ids]
|
|
884
|
+
if names is not None:
|
|
885
|
+
params["names"] = list(names)
|
|
886
|
+
response = self.check_error(
|
|
887
|
+
self.request(
|
|
888
|
+
"GET",
|
|
889
|
+
"product",
|
|
890
|
+
params=params,
|
|
891
|
+
include_fields=include_fields,
|
|
892
|
+
exclude_fields=exclude_fields,
|
|
893
|
+
)
|
|
894
|
+
)
|
|
895
|
+
return products_response_model(product_type).model_validate(response).products
|
|
896
|
+
|
|
897
|
+
def products(
|
|
898
|
+
self,
|
|
899
|
+
ids: Optional[Sequence[int]] = None,
|
|
900
|
+
names: Optional[Sequence[str]] = None,
|
|
901
|
+
access: Sequence[ProductAccess] = ("accessible",),
|
|
902
|
+
include_fields: Optional[list[str]] = None,
|
|
903
|
+
exclude_fields: Optional[list[str]] = None,
|
|
904
|
+
) -> list[Product]:
|
|
905
|
+
return self._products(
|
|
906
|
+
Product, ids, names, access, include_fields, exclude_fields
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
def products_as(
|
|
910
|
+
self,
|
|
911
|
+
product_type: type[ProductType],
|
|
912
|
+
ids: Optional[Sequence[int]] = None,
|
|
913
|
+
names: Optional[Sequence[str]] = None,
|
|
914
|
+
access: Sequence[ProductAccess] = ("accessible",),
|
|
915
|
+
exclude_fields: Optional[list[str]] = None,
|
|
916
|
+
) -> list[ProductType]:
|
|
917
|
+
include_fields = model_field_names(product_type)
|
|
918
|
+
return self._products(
|
|
919
|
+
product_type, ids, names, access, include_fields, exclude_fields
|
|
920
|
+
)
|
|
921
|
+
|
|
773
922
|
def bug_history(
|
|
774
923
|
self, bug_id: int, new_since: Optional[datetime] = None
|
|
775
924
|
) -> BugHistory:
|
|
@@ -4,7 +4,7 @@ import pytest
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
from bugdantic import Bugzilla, BugzillaConfig, BugzillaResponseError
|
|
7
|
-
from bugdantic.bugzilla import BugComment
|
|
7
|
+
from bugdantic.bugzilla import BugComment, Product
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@pytest.fixture
|
|
@@ -156,3 +156,49 @@ def test_error_fails_whole_request(bugzilla):
|
|
|
156
156
|
with pytest.raises(BugzillaResponseError) as exc_info:
|
|
157
157
|
bugzilla.comments([18109498, 0, 18109500])
|
|
158
158
|
assert exc_info.value.response_data.code == 111
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_products_by_name(bugzilla):
|
|
162
|
+
result = bugzilla.products(names=["Web Compatibility"])
|
|
163
|
+
assert len(result) == 1
|
|
164
|
+
product = result[0]
|
|
165
|
+
assert isinstance(product, Product)
|
|
166
|
+
assert product.name == "Web Compatibility"
|
|
167
|
+
assert product.components
|
|
168
|
+
assert any(component.name == "Site Reports" for component in product.components)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_products_by_id(bugzilla):
|
|
172
|
+
[by_name] = bugzilla.products(names=["Web Compatibility"], include_fields=["id"])
|
|
173
|
+
result = bugzilla.products(ids=[by_name.id], include_fields=["id", "name"])
|
|
174
|
+
assert [(item.id, item.name) for item in result] == [
|
|
175
|
+
(by_name.id, "Web Compatibility")
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_products_missing(bugzilla):
|
|
180
|
+
assert bugzilla.products(names=["This product does not exist"]) == []
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_products_as(bugzilla):
|
|
184
|
+
class ComponentName(BaseModel):
|
|
185
|
+
name: str
|
|
186
|
+
|
|
187
|
+
class ProductComponents(BaseModel):
|
|
188
|
+
name: str
|
|
189
|
+
components: list[ComponentName]
|
|
190
|
+
|
|
191
|
+
result = bugzilla.products_as(
|
|
192
|
+
ProductComponents, names=["Web Compatibility", "Core"]
|
|
193
|
+
)
|
|
194
|
+
assert all(isinstance(item, ProductComponents) for item in result)
|
|
195
|
+
assert {item.name for item in result} == {"Web Compatibility", "Core"}
|
|
196
|
+
assert all(item.components for item in result)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_products_access(bugzilla):
|
|
200
|
+
class ProductName(BaseModel):
|
|
201
|
+
name: str
|
|
202
|
+
|
|
203
|
+
result = bugzilla.products_as(ProductName, access=["enterable"])
|
|
204
|
+
assert "Web Compatibility" in {item.name for item in result}
|
|
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
|