Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## 0.6.6 — 2026-10-28

A minor addition: the GLPI v2 `/Administration/Group` routes, so a caller can
find and create groups. Existing methods are unchanged.

### Added

- **`search_groups`, `iter_search_groups`, `get_group`, `create_group`,
`update_group` and `delete_group`** on both clients, from a new `GroupMixin`
and the `GROUP_ENDPOINT` constant.
- **`skip_entity: bool = False`** on `search_groups` and `iter_search_groups`,
as on `search_users`: `True` omits the `GLPI-Entity`, `GLPI-Profile` and
`GLPI-Entity-Recursive` headers so the search spans every entity the caller
can see. The other group methods always send them.
- **`GetGroup`, `PostGroup`, `PatchGroup` and `DeleteGroup`**, modelled on
`components.schemas.Group` (parent, entity, the `maybe_*` / `may_contain_*`
flags, LDAP fields and timestamps), exported from the package root and from
`glpi_python_client.models`.

## 0.6.5 — 2026-10-14

A patch release that lets a caller find the Document behind an image GLPI left
Expand Down
23 changes: 23 additions & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,29 @@ Entities
:undoc-members:
:show-inheritance:

Groups
------

.. autoclass:: GetGroup
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: PostGroup
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: PatchGroup
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: DeleteGroup
:members:
:undoc-members:
:show-inheritance:

Plugin: Fields (custom fields)
------------------------------

Expand Down
2 changes: 2 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ The full set is re-exported from the package root, including
``GetUser`` / ``PostUser`` / ``PatchUser`` / ``DeleteUser``,
``GetLocation`` / ``PostLocation`` / ``PatchLocation`` / ``DeleteLocation``,
``GetEntity`` / ``PostEntity`` / ``PatchEntity`` / ``DeleteEntity``,
``GetGroup`` / ``PostGroup`` / ``PatchGroup`` / ``DeleteGroup``,
``GetFollowup``, ``GetTicketTask``, ``GetSolution``, ``GetTimelineDocument``,
``GetTeamMember``, and ``GetDocument`` together with their post / patch /
delete variants. The read-only ``GetDocumentItem`` and ``FollowupDocuments``
Expand Down Expand Up @@ -1152,6 +1153,7 @@ batches until the API returns fewer rows than the requested
* :meth:`GlpiClient.iter_search_tickets`
* :meth:`GlpiClient.iter_search_users`
* :meth:`GlpiClient.iter_search_entities`
* :meth:`GlpiClient.iter_search_groups`

.. code-block:: python

Expand Down
10 changes: 9 additions & 1 deletion glpi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
DeleteDocument,
DeleteEntity,
DeleteFollowup,
DeleteGroup,
DeleteKBArticle,
DeleteKBArticleComment,
DeleteKBCategory,
Expand All @@ -60,6 +61,7 @@
GetDocumentItem,
GetEntity,
GetFollowup,
GetGroup,
GetKBArticle,
GetKBArticleComment,
GetKBArticleRevision,
Expand Down Expand Up @@ -96,6 +98,7 @@
PatchDocument,
PatchEntity,
PatchFollowup,
PatchGroup,
PatchKBArticle,
PatchKBArticleComment,
PatchKBCategory,
Expand All @@ -114,6 +117,7 @@
PostDocument,
PostEntity,
PostFollowup,
PostGroup,
PostKBArticle,
PostKBArticleComment,
PostKBCategory,
Expand All @@ -133,7 +137,7 @@
date_window,
)

__version__ = "0.6.5"
__version__ = "0.6.6"

__all__ = [
"AsyncGlpiClient",
Expand All @@ -145,6 +149,7 @@
"DeleteDocument",
"DeleteEntity",
"DeleteFollowup",
"DeleteGroup",
"DeleteKBArticle",
"DeleteKBArticleComment",
"DeleteKBCategory",
Expand All @@ -165,6 +170,7 @@
"GetDocumentItem",
"GetEntity",
"GetFollowup",
"GetGroup",
"GetKBArticle",
"GetKBArticleComment",
"GetKBArticleRevision",
Expand Down Expand Up @@ -212,6 +218,7 @@
"PatchDocument",
"PatchEntity",
"PatchFollowup",
"PatchGroup",
"PatchKBArticle",
"PatchKBArticleComment",
"PatchKBCategory",
Expand All @@ -230,6 +237,7 @@
"PostDocument",
"PostEntity",
"PostFollowup",
"PostGroup",
"PostKBArticle",
"PostKBArticleComment",
"PostKBCategory",
Expand Down
2 changes: 2 additions & 0 deletions glpi_python_client/_async/clients/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from glpi_python_client._async.clients.api.administration import (
EntityMixin,
GroupMixin,
UserMixin,
)
from glpi_python_client._async.clients.api.assets import ComputerMixin
Expand Down Expand Up @@ -49,6 +50,7 @@
"DocumentMixin",
"EntityMixin",
"FollowupMixin",
"GroupMixin",
"KBArticleCommentMixin",
"KBArticleMixin",
"KBArticleRevisionMixin",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""GLPI ``/Administration`` mixins for the GLPI client.

The submodules expose the user and entity mixins used by this tree's
The submodules expose the user, entity and group mixins used by this tree's
client -- :class:`glpi_python_client.AsyncGlpiClient` on the async
surface, :class:`glpi_python_client.GlpiClient` on the generated one.
"""

from __future__ import annotations

from glpi_python_client._async.clients.api.administration._entity import EntityMixin
from glpi_python_client._async.clients.api.administration._group import GroupMixin
from glpi_python_client._async.clients.api.administration._user import UserMixin

__all__ = ["EntityMixin", "UserMixin"]
__all__ = ["EntityMixin", "GroupMixin", "UserMixin"]
218 changes: 218 additions & 0 deletions glpi_python_client/_async/clients/api/administration/_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""GLPI ``/Administration/Group`` mixin.

The mixin exposes search, fetch, create, update, and delete helpers for the
GLPI group resource. Every call sends the client's ``GLPI-Entity`` header
(and the profile and recursion headers that travel with it) except where a
search is told to skip it with ``skip_entity=True``.
"""

from __future__ import annotations

from collections.abc import AsyncIterator

from glpi_python_client._async.clients.commons._constants import GROUP_ENDPOINT, GlpiId
from glpi_python_client._async.clients.commons._transport import TransportMixin
from glpi_python_client.models.api_schema.administration._group import (
DeleteGroup,
GetGroup,
PatchGroup,
PostGroup,
)


class GroupMixin(TransportMixin):
"""CRUD helpers for ``/Administration/Group``."""

async def search_groups(
self,
rsql_filter: str = "",
*,
limit: int = 50,
start: int = 0,
skip_entity: bool = False,
) -> list[GetGroup]:
"""Search GLPI groups with an optional RSQL filter.

Parameters
----------
rsql_filter : str, optional
Raw RSQL filter forwarded as the ``filter`` query parameter,
for example ``"name==Support"``. Empty by default.
limit : int, optional
Maximum number of records returned by the GLPI server.
start : int, optional
Zero-based offset of the first record returned.
skip_entity : bool, optional
When ``True`` the ``GLPI-Entity`` header is omitted so the
search spans every entity the caller has access to.

Returns
-------
list[GetGroup]
Groups matching the filter.
"""

params: dict[str, object] = {"limit": limit, "start": start}
if rsql_filter:
params["filter"] = rsql_filter
return await self._resource_list(
GROUP_ENDPOINT, GetGroup, params=params, skip_entity=skip_entity
)

async def iter_search_groups(
self,
rsql_filter: str = "",
*,
batch_size: int = 50,
skip_entity: bool = False,
) -> AsyncIterator[list[GetGroup]]:
"""Yield successive pages of GLPI groups until exhausted.

Pagination advances the ``start`` offset after each batch and stops
when the server returns fewer items than ``batch_size``.

Parameters
----------
rsql_filter : str, optional
Raw RSQL filter forwarded as the ``filter`` query parameter.
batch_size : int, optional
Number of records requested per page (default 50).
skip_entity : bool, optional
When ``True`` the ``GLPI-Entity`` header is omitted so the
search spans every entity the caller has access to.

Yields
------
list[GetGroup]
One page of groups per iteration. The last yielded batch may
be shorter than ``batch_size``.
"""

start = 0
while True:
batch = await self.search_groups(
rsql_filter,
limit=batch_size,
start=start,
skip_entity=skip_entity,
)
if batch:
yield batch
if len(batch) < batch_size:
break
start += batch_size

async def get_group(self, group_id: GlpiId) -> GetGroup:
"""Fetch one GLPI group by identifier.

Parameters
----------
group_id : GlpiId
Numeric identifier of the group to retrieve.

Returns
-------
GetGroup
Validated group payload.

Raises
------
GlpiStatusError
If the GLPI server returns a non-success HTTP status.
"""

return await self._resource_get(
f"{GROUP_ENDPOINT}/{group_id}",
GetGroup,
failure_message=f"Failed to get group {group_id}",
)

async def create_group(self, group: PostGroup) -> int:
"""Create one GLPI group.

Parameters
----------
group : PostGroup
Request body describing the group to create.

Returns
-------
int
Identifier assigned by the GLPI server.

Raises
------
GlpiStatusError
If the GLPI server returns a non-success HTTP status.
GlpiProtocolError
If the create response is missing the ``id`` field.
"""

return await self._resource_create(
GROUP_ENDPOINT,
group,
failure_message="Failed to create group",
missing_message="GLPI group create response did not include an ID",
log_message_factory=lambda new_id: f"GLPI API created group {new_id}",
)

async def update_group(self, group_id: GlpiId, group: PatchGroup) -> None:
"""Update one GLPI group with a partial body.

Parameters
----------
group_id : GlpiId
Numeric identifier of the group to update.
group : PatchGroup
Partial request body.

Returns
-------
None

Raises
------
GlpiStatusError
If the GLPI server returns a non-success HTTP status.
"""

await self._resource_update(
f"{GROUP_ENDPOINT}/{group_id}",
group,
failure_message=f"Failed to update group {group_id}",
log_message=f"GLPI API updated group {group_id}",
)

async def delete_group(
self, group_id: GlpiId, *, force: bool | None = None
) -> None:
"""Delete one GLPI group by identifier.

Parameters
----------
group_id : GlpiId
Numeric identifier of the group to delete.
force : bool | None, optional
When ``True`` the group is permanently deleted instead of
being moved to the trash.

Returns
-------
None

Raises
------
GlpiStatusError
If the GLPI server returns a non-success HTTP status.
"""

await self._resource_delete(
f"{GROUP_ENDPOINT}/{group_id}",
failure_message=f"Failed to delete group {group_id}",
log_message=f"GLPI API deleted group {group_id}",
force=force,
delete_model_cls=DeleteGroup,
)


__all__ = ["GroupMixin"]
Loading
Loading