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
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ tasks:
dev:docs:
desc: run the mkdocs server with appropriate flags
cmds:
- cd docs && uv run mkdocs serve --open -a localhost:8001
- cd docs && uv run -m mkdocs serve --open -a localhost:8001
debug:get:
desc: use httpie to get payload from CC
summary: |
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ We follow a `git` like `command`, `sub-command` pattern, so it should feel quite

uv will install the alias `gal` for you to interact with the CLI. You can ask for help with:

::: mkdocs-typer
:module: gallagher.cli
:command: app
::: mkdocs-typer2
:module: gallagher.cli
:name: app

## Resources

Expand Down
1 change: 1 addition & 0 deletions docs/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ There are three types of schema definitions, each one of them suffixed with thei
- **Summary** is what is returned by the Gallagher API in operations such as [searches](https://gallaghersecurity.github.io/cc-rest-docs/ref/cardholders.html), these are generally a subset of the full object
- **Detail** are the full object found at a particular `href`, they compound on the `Summary` schema and add additional attributes
- **Responses** are resposnes sent back from the server, these will typically contain a set of `Summary` or `Detail` objects. When fetching _detailed_ responses for an object the server will often respond with a `Detail` object without a wrapper `Response` object.
- **Payloads** are objects that are sent to the server as part of a `POST` or `PUT` operation, these are suffixed with **Payload**, some of these also offer `Builder` classes to assist with construction of the payload.

I additional we have classes that defined responses which are suffixed with **Response**, these wrap structures which returns `hrefs` for `next` and `previous` responses and usually have a collection to hold the response.

Expand Down
24 changes: 12 additions & 12 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ theme:
name: material

markdown_extensions:
- mkdocs-typer
- codehilite
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- admonition
- codehilite
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- admonition

plugins:
plugins:
- social
- search
- mkdocstrings
- mkdocs-typer2

nav:
- Introduction: index.md
- Installation & Usage: installation.md
- Python SDK: python-sdk.md
- SQL and SQLAlchemy: sql.md
- Command Line Interface: cli.md
- Terminal User Interface: tui.md
- Terminal User Interface: tui.md
- Contributor's Manual: design.md
24 changes: 24 additions & 0 deletions gallagher/cc/access_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
""" Access Groups
"""

from gallagher.cc.core import Capabilities, APIEndpoint, EndpointConfig

from ..dto.detail import AccessGroupDetail
from ..dto.response import AccessGroupResponse


class AccessGroups(APIEndpoint):
"""Access Groups
Provides access to access group operations including listing,
retrieving, creating, and updating access groups.
"""

@classmethod
async def get_config(cls) -> EndpointConfig:
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.access_groups.access_groups,
dto_list=AccessGroupResponse,
dto_retrieve=AccessGroupDetail,
)
6 changes: 5 additions & 1 deletion gallagher/cc/cardholders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"""

from ..core import Capabilities, APIEndpoint, EndpointConfig
from ..core import (
Capabilities,
APIEndpoint,
EndpointConfig,
)

from ...dto.detail import (
CardholderDetail,
Expand Down
55 changes: 29 additions & 26 deletions gallagher/cc/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
NoAPIKeyProvidedError,
)

from ..dto.detail.discover import FeaturesDetail


def _check_api_key_format(api_key):
"""Validates that the Gallagher Key is in the right format.
Expand Down Expand Up @@ -88,6 +90,7 @@ def _sanitise_name_param(name: str) -> str:

return f"%{name}%"


def _get_authorization_headers():
"""Creates an authorization header for Gallagher API calls

Expand Down Expand Up @@ -115,7 +118,7 @@ def _get_authorization_headers():
client properly.
"""
raise NoAPIKeyProvidedError()

if not _check_api_key_format(api_key):
""" API key is not in the right format

Expand Down Expand Up @@ -162,7 +165,7 @@ class EndpointConfig:
sort: str = "id" # Can be set to id or -id

fields: Tuple[str] = () # Optional list of fields, blank = all
search: Tuple[str] = () # If the endpoint supports search, blank = none
search: Tuple[str] = () # If the endpoint supports search, blank = none

@classmethod
async def validate_endpoint(cls):
Expand All @@ -180,21 +183,22 @@ class Capabilities:
Discover response object, each endpoint will reference
one of the instance variable Href property to get the
path to the endpoint.

Gallagher recommends that the endpoints not be hardcoded
into the client and instead be discovered at runtime.

Note that if a feature has not been licensed by a client
then the path will be set to None, if the client attempts
to access the endpoint then the library will throw an exception

This value is memoized and should be performant
"""
CURRENT = DiscoveryResponse(
version="0.0.0.0", # Indicates that it's not been discovered
features=FeaturesDetail(),
)


class APIEndpoint:
"""Base class for all API objects

Expand Down Expand Up @@ -233,7 +237,7 @@ async def get_config(cls) -> EndpointConfig:
provide additional configuration options.
"""
raise NotImplementedError("get_config method not implemented")

@classmethod
def _ssl_context(cls):
"""Returns the SSL context for the endpoint
Expand All @@ -246,7 +250,7 @@ def _ssl_context(cls):
if not file_tls_certificate:
"""TLS certificate is required for SSL context"""
return None

context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
context.load_cert_chain(file_tls_certificate, file_private_key)
return context
Expand Down Expand Up @@ -288,9 +292,9 @@ async def _discover(cls):
from . import api_base

async with httpx.AsyncClient(
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:
# Don't use the _get wrapper here, we need to get the raw response
response = await _httpx_async.get(
api_base,
Expand Down Expand Up @@ -320,7 +324,6 @@ async def _discover(cls):
# copied not referenced.
cls.__config__ = await cls.get_config()


@classmethod
async def list(cls, skip=0):
"""For a list of objects for the given resource
Expand Down Expand Up @@ -374,9 +377,9 @@ async def search(
cls,
sort: SearchSortOrder = SearchSortOrder.ID,
top: int = 100,
name: Optional[str] = None, # TODO: en
division: str = None, # TODO: use division type
direct_division: str = None, # TODO: use division type
name: Optional[str] = None, # TODO: en
division: str = None, # TODO: use division type
direct_division: str = None, # TODO: use division type
description: Optional[str] = None,
fields: str | List[str] = "defaults",
**kwargs,
Expand Down Expand Up @@ -429,7 +432,6 @@ async def search(
params=params,
)


# Follow links methods, these are valid based on if the response
# classes make available a next, previous or update href, otherwise
# the client raises an NotImplementedError
Expand All @@ -452,7 +454,8 @@ async def next(cls, response):

if not response.next:
"""We have no where to go based on the passed response"""
raise DeadEndException("No further paths to follow for this endpoint")
raise DeadEndException(
"No further paths to follow for this endpoint")

return await cls._get(
response.next.href,
Expand Down Expand Up @@ -486,7 +489,7 @@ async def previous(cls, response):
@classmethod
async def follow(
cls,
asyncio_event: AsyncioEvent, # Not to be confused with Gallagher event
asyncio_event: AsyncioEvent, # Not to be confused with Gallagher event
params: dict[str, Any] = {},
):
"""Fetches update and follows next to get the next set of results
Expand Down Expand Up @@ -516,9 +519,9 @@ async def follow(
url = f"{cls.__config__.endpoint_follow.href}"

async with httpx.AsyncClient(
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:

while not asyncio_event.is_set():
try:
Expand Down Expand Up @@ -576,9 +579,9 @@ async def _get(
:param AppBaseModel response_class: DTO to be used for list requests
"""
async with httpx.AsyncClient(
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:

try:

Expand Down Expand Up @@ -627,9 +630,9 @@ async def _post(
parsing and sending out a body as part of the request.
"""
async with httpx.AsyncClient(
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:
proxy=proxy_address,
verify=cls._ssl_context(),
) as _httpx_async:

try:

Expand Down
20 changes: 20 additions & 0 deletions gallagher/cc/doors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from gallagher.cc.core import Capabilities, APIEndpoint, EndpointConfig

from ..dto.detail import DoorDetail
from ..dto.response import DoorResponse


class Doors(APIEndpoint):
"""Doors
Provides access to door operations including listing,
retrieving, creating, and updating doors.
"""

@classmethod
async def get_config(cls) -> EndpointConfig:
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.doors.doors,
dto_list=DoorResponse,
dto_retrieve=DoorDetail,
)
24 changes: 24 additions & 0 deletions gallagher/cc/lockers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
""" Lockers

"""

from gallagher.cc.core import Capabilities, APIEndpoint, EndpointConfig

from ..dto.detail import LockerDetail
from ..dto.response import LockerResponse


class Lockers(APIEndpoint):
"""Lockers

Provides access to locker operations including listing,
retrieving, creating, and updating lockers.
"""

@classmethod
async def get_config(cls) -> EndpointConfig:
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.locker_banks.locker_banks,
dto_list=LockerResponse,
dto_retrieve=LockerDetail,
)
24 changes: 24 additions & 0 deletions gallagher/cc/operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
""" Operators
"""

from gallagher.cc.core import Capabilities, APIEndpoint, EndpointConfig

from ..dto.detail import OperatorDetail
from ..dto.response import OperatorResponse


class Operators(APIEndpoint):
"""Operators
Provides access to operator operations including listing,
retrieving, creating, and updating operators.
"""

@classmethod
async def get_config(cls) -> EndpointConfig:
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.operator_groups.operator_groups,
dto_list=OperatorResponse,
dto_retrieve=OperatorDetail,
)
24 changes: 24 additions & 0 deletions gallagher/cc/receptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
""" Receptions

"""

from gallagher.cc.core import Capabilities, APIEndpoint, EndpointConfig

from ..dto.detail import ReceptionDetail
from ..dto.response import ReceptionResponse


class Receptions(APIEndpoint):
"""Receptions

Provides access to reception operations including listing,
retrieving, creating, and updating receptions.
"""

@classmethod
async def get_config(cls) -> EndpointConfig:
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.receptions.receptions,
dto_list=ReceptionResponse,
dto_retrieve=ReceptionDetail,
)
Loading