Skip to content

Commit cdbec32

Browse files
committed
Merge branch 'develop' into atg-20250106-ihs-71
2 parents 276f550 + 60d001e commit cdbec32

File tree

8 files changed

+85
-26
lines changed

8 files changed

+85
-26
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
- name: "Check out repository code"
7676
uses: "actions/checkout@v4"
7777
- name: "Setup environment"
78-
run: "pip install ruff==0.7.1"
78+
run: "pip install ruff==0.8.6"
7979
- name: "Linting: ruff check"
8080
run: "ruff check ."
8181
- name: "Linting: ruff format"

changelog/158.added.me

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `count` method to both sync and async clients to retrieve the number of objects of a given kind

infrahub_sdk/client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,25 @@ async def _process_nodes_and_relationships(
525525

526526
return ProcessRelationsNode(nodes=nodes, related_nodes=related_nodes)
527527

528+
async def count(
529+
self,
530+
kind: str | type[SchemaType],
531+
at: Timestamp | None = None,
532+
branch: str | None = None,
533+
timeout: int | None = None,
534+
) -> int:
535+
"""Return the number of nodes of a given kind."""
536+
schema = await self.schema.get(kind=kind, branch=branch)
537+
538+
branch = branch or self.default_branch
539+
if at:
540+
at = Timestamp(at)
541+
542+
response = await self.execute_graphql(
543+
query=Query(query={schema.kind: {"count": None}}).render(), branch_name=branch, at=at, timeout=timeout
544+
)
545+
return int(response.get(schema.kind, {}).get("count", 0))
546+
528547
@overload
529548
async def all(
530549
self,
@@ -1585,6 +1604,25 @@ def execute_graphql(
15851604

15861605
# TODO add a special method to execute mutation that will check if the method returned OK
15871606

1607+
def count(
1608+
self,
1609+
kind: str | type[SchemaType],
1610+
at: Timestamp | None = None,
1611+
branch: str | None = None,
1612+
timeout: int | None = None,
1613+
) -> int:
1614+
"""Return the number of nodes of a given kind."""
1615+
schema = self.schema.get(kind=kind, branch=branch)
1616+
1617+
branch = branch or self.default_branch
1618+
if at:
1619+
at = Timestamp(at)
1620+
1621+
response = self.execute_graphql(
1622+
query=Query(query={schema.kind: {"count": None}}).render(), branch_name=branch, at=at, timeout=timeout
1623+
)
1624+
return int(response.get(schema.kind, {}).get("count", 0))
1625+
15881626
@overload
15891627
def all(
15901628
self,

infrahub_sdk/data.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import BaseModel, ConfigDict, Field
44

5-
from .node import InfrahubNode # noqa: TCH001
5+
from .node import InfrahubNode # noqa: TC001
66

77

88
class RepositoryBranchInfo(BaseModel):
@@ -13,7 +13,8 @@ class RepositoryData(BaseModel):
1313
model_config = ConfigDict(arbitrary_types_allowed=True)
1414
repository: InfrahubNode = Field(..., description="InfrahubNode representing a Repository")
1515
branches: dict[str, str] = Field(
16-
..., description="Dictionary with the name of the branch as the key and the active commit id as the value"
16+
...,
17+
description="Dictionary with the name of the branch as the key and the active commit id as the value",
1718
)
1819

1920
branch_info: dict[str, RepositoryBranchInfo] = Field(default_factory=dict)

poetry.lock

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pre-commit = "^2.20.0"
6767
types-toml = "*"
6868
types-ujson = "*"
6969
types-pyyaml = "*"
70-
ruff = "0.7.1"
70+
ruff = "0.8.6"
7171
pytest-xdist = "^3.3.1"
7272
types-python-slugify = "^8.0.0.3"
7373
invoke = "^2.2.0"
@@ -252,6 +252,7 @@ ignore = [
252252
"FURB110", # Replace ternary `if` expression with `or` operator
253253
"FURB113", # Use `lines.extend((" " * self.indentation + "}", "}"))` instead of repeatedly calling `lines.append()`
254254
"FURB177", # Prefer `Path.cwd()` over `Path().resolve()` for current-directory lookups
255+
"INP001", # File declares a package, but is nested under an implicit namespace package.
255256
"N802", # Function name should be lowercase
256257
"N806", # Variable in function should be lowercase
257258
"PERF203", # `try`-`except` within a loop incurs performance overhead
@@ -267,10 +268,12 @@ ignore = [
267268
"PLW1641", # Object does not implement `__hash__` method
268269
"PTH100", # `os.path.abspath()` should be replaced by `Path.resolve()`
269270
"PTH109", # `os.getcwd()` should be replaced by `Path.cwd()`
271+
"PYI061", # [*] `Literal[None]` can be replaced with `None`
270272
"RET504", # Unnecessary assignment to `data` before `return` statement
271273
"RUF005", # Consider `[*path, str(key)]` instead of concatenation
272274
"RUF015", # Prefer `next(iter(input_data["variables"].keys()))` over single element slice
273275
"RUF029", # Function is declared `async`, but doesn't `await` or use `async` features.
276+
"RUF056", # [*] Avoid providing a falsy fallback to `dict.get()` in boolean test positions. The default fallback `None` is already falsy.
274277
"S108", # Probable insecure usage of temporary file or directory
275278
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
276279
"S701", # By default, jinja2 sets `autoescape` to `False`. Consider using `autoescape=True`
@@ -282,7 +285,7 @@ ignore = [
282285
"SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements
283286
"SIM118", # Use `key in dict` instead of `key in dict.keys)
284287
"SIM910", # Use `data.get(key)` instead of `data.get(key, None)`
285-
"TCH003", # Move standard library import `collections.abc.Iterable` into a type-checking block
288+
"TC003", # Move standard library import `collections.abc.Iterable` into a type-checking block
286289
"UP031", # Use format specifiers instead of percent format
287290
]
288291

tests/unit/sdk/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,12 @@ async def mock_query_corenode_page1_1(httpx_mock: HTTPXMock, client: InfrahubCli
16801680
return httpx_mock
16811681

16821682

1683+
@pytest.fixture
1684+
async def mock_query_repository_count(httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01) -> HTTPXMock:
1685+
httpx_mock.add_response(method="POST", json={"data": {"CoreRepository": {"count": 5}}})
1686+
return httpx_mock
1687+
1688+
16831689
@pytest.fixture
16841690
async def mock_query_repository_page1_empty(
16851691
httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01

tests/unit/sdk/test_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ async def test_get_repositories(
7373
}
7474

7575

76+
@pytest.mark.parametrize("client_type", client_types)
77+
async def test_method_count(clients, mock_query_repository_count, client_type): # pylint: disable=unused-argument
78+
if client_type == "standard":
79+
count = await clients.standard.count(kind="CoreRepository")
80+
else:
81+
count = clients.sync.count(kind="CoreRepository")
82+
83+
assert count == 5
84+
85+
7686
@pytest.mark.parametrize("client_type", client_types)
7787
async def test_method_all_with_limit(clients, mock_query_repository_page1_2, client_type): # pylint: disable=unused-argument
7888
if client_type == "standard":

0 commit comments

Comments
 (0)