Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alpha-6 release ready #60

Merged
merged 45 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8a78b59
chore: update version number
devraj Aug 26, 2024
fcad2f5
fix: version and dependencies
devraj Sep 5, 2024
87c9341
chore: update packages
devraj Oct 2, 2024
be65395
refactor: adds pydantic email validator
devraj Oct 2, 2024
e167828
fix: markdown syntax
devraj Oct 8, 2024
c38f4f2
chore: update packages
devraj Oct 12, 2024
d0abd1f
fix: sign tags from this commit
devraj Oct 12, 2024
ea4e843
fix: version number in python project definition
devraj Oct 22, 2024
2ba309e
chore: updates packages
devraj Nov 1, 2024
4a745cb
refactor!: placeholder implementation of updates
devraj Nov 2, 2024
217b7b3
refactor: indentation
devraj Nov 2, 2024
9a6712d
refactor: adds versions attribute
devraj Nov 2, 2024
477a43a
docs: update description of discovery
devraj Nov 2, 2024
efb13a9
chore: update packages
devraj Nov 2, 2024
8c44cc7
refactor: adds exceptions for misconfigure api keys
devraj Nov 2, 2024
93ab7ef
chore: update packages
devraj Nov 2, 2024
65759f2
docs: updates notes on API key configuration
devraj Nov 2, 2024
9f29129
refactor: sample working poll application with parsing
devraj Nov 3, 2024
7d65110
refactor: uses constants not hardcoded values
devraj Nov 3, 2024
6011c83
refactor: use of asyncio events for breaking loop
devraj Nov 3, 2024
4808f2e
docs: port sql related concepts for docs
devraj Nov 3, 2024
631144b
refactor: normalise follow implementation refs #19
devraj Nov 3, 2024
ef4d527
fix: method reference
devraj Nov 3, 2024
3b31622
docs: update thoughts from #19 work
devraj Nov 3, 2024
9c4ea98
refactor: formatting
devraj Nov 3, 2024
a1a8d19
docs: adds snapshot of events/groups
devraj Nov 5, 2024
a3cd2f3
refactor: adds events/groups ahead of event poll refs #19
devraj Nov 5, 2024
1861b6a
chore: syntax
devraj Nov 5, 2024
e5dd9f2
refactor: stubs for events example
devraj Nov 6, 2024
b70a099
feat: configure events endpoint refs #19
devraj Nov 6, 2024
dde1436
refactor: upgrades to pydantic210 pre-release
devraj Nov 6, 2024
f534142
fix: removes lingering print statement
devraj Nov 11, 2024
162c5af
refactor: examples now shows it can follow events
devraj Nov 11, 2024
7cd7cd3
chore: update libraries
devraj Nov 11, 2024
0da5c0f
chore: package updates
devraj Nov 21, 2024
9a2dc51
chore: update events for label printing refs #61
devraj Nov 21, 2024
04eb78d
docs: rewrite statements
devraj Nov 30, 2024
113734d
chore: update packages
devraj Nov 30, 2024
9c61f51
chore: cleans dependencies
devraj Nov 30, 2024
8faf32d
fix: adds imaging package
devraj Nov 30, 2024
fe4c92f
docs: notes on release notes
devraj Dec 5, 2024
c90b294
chore: update packages
devraj Dec 5, 2024
ade88ee
chore: update packages
devraj Dec 23, 2024
815ded7
chore: updates typer
devraj Dec 23, 2024
0790652
feat: adds user agent string
devraj Dec 23, 2024
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ class HrefMixin(BaseModel):

These `Mixin` classes can also be used to declare attributes that seek to use the same pattern:

````python
```python
class DivisionDetail(
AppBaseModel,
IdentityMixin,
Expand All @@ -222,6 +222,7 @@ class DivisionDetail(
description: Optional[str] = None
server_display_name: Optional[str] = None
parent: OptionalHrefMixin = None
```

### Schemas

Expand All @@ -237,7 +238,7 @@ class AccessGroupRef(
""" Access Groups is what a user is assigned to to provide access to doors
"""
name: str
````
```

where the `HrefMixin` provides the `href` attribute:

Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tasks:
prompt: "Before we build, is the version {{.PROJ_VERSION}} number up to date?"
desc: tags the current commit
cmds:
- git tag -a v{{.PROJ_VERSION}} -m "v{{.PROJ_VERSION}}"
- git tag -s -a v{{.PROJ_VERSION}} -m "v{{.PROJ_VERSION}}"
- git push origin v{{.PROJ_VERSION}}
build:image:
prompt: Have you made sure the version number is up to date?
Expand Down
8,076 changes: 8,076 additions & 0 deletions assets/groups.json

Large diffs are not rendered by default.

82 changes: 75 additions & 7 deletions docs/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ In addition to the usual suspects (e.g pytest) we use:

A central feature to this project is the API client, focused on a superior developer experience and performance we spent substantial time in designing the Python interface to ensure it scales. We also ensure that we follow forward compatibility design patterns outlined by Gallagher (e.g [HATEOAS](https://gallaghersecurity.github.io/cc-rest-docs/ref/events.html)) so you as a developer don't have to worry about it.

While it's optional to read this chapter if you are simply using the API client or the tools. If you choose to develop the client further then this is a must read.
This chapter is optional if you are simply using the API client or the tools. It's aimed towards developers who choose to extend the client further.

## Data Transfer Objects

Expand Down Expand Up @@ -233,7 +233,7 @@ class Division(APIEndpoint):

[Typer](https://typer.tiangolo.com) enhances [click](https://click.palletsprojects.com/en/8.1.x/) by providing a mode `FastAPI` like developer experience (having been created by the developers of FastAPI). The design of our `cli` is highly inspired by tools like `git`, and follows the subcommand pattern.

While we pride ourselves in providing a complete set of CLI commands, this section outlines thoughts on the design of the command line interface for those working on extending it.
This section outlines thoughts on the design of the command line interface for those working on extending it.

### Extending DTOs for the CLI

Expand Down Expand Up @@ -301,6 +301,64 @@ class CardholderSummaryResponse(

## SQL

The SQL interface is built using the `shillelagh` library. The library exposes various `metadata` which the extension gathers to construct the SQL interface. The following is a guide to outline how that works and what you require to populate should you wish to extend or contribute to the SQL interface.

Each API Endpoint configuration that wishes to support the SQL interfaces returns a constant named `__shillelagh__` which is a tuple of classes that are used to query the endpoint. The classes must be a subclass of `AppBaseResponseModel` and must implement a `result_set` property that returns a reference to the property that provides the resultset for the SQL queries.

```python
# Write up Alarms for querying via the SQL interface
__shillelagh__ = (
Alarms,
)
```

The following demonstrates two examples where `CardholderSummaryResponse` returns the `results` property

```python
class CardholderSummaryResponse(AppBaseResponseModel):
"""Summary response for cardholder list and search

/api/cardholders is generally the endpoint that responds
to the query, it is dynamically configured from the discovery

"""

results: list[CardholderSummary]

@property
def result_set(self) -> list[CardholderSummary]:
""" Wrap summary response target property

the sql interface will call this property and each summary
response is expected to override this and return the appropriate
target property
"""
return self.results
```

whereas `AlarmSummaryResponse` nominates the `alarms` property.

```python
class AlarmSummaryResponse(AppBaseResponseModel):
"""AlarmSummaryResponse represents a single alarm"""

alarms: list[AlarmSummary]
updates: OptionalHrefMixin = None

@property
def result_set(self) -> list[AlarmSummary]:
""" Wrap summary response target property

the sql interface will call this property and each summary
response is expected to override this and return the appropriate
target property
"""
return self.alarms

```

Both of these make for the `results` of the SQL

## Maintainers Notes

This section primarily contains notes for the managers of the project, it covers topics like publication of releases.
Expand All @@ -316,13 +374,23 @@ The action `.github/workflows/publish-package.yml` is responsible for publishing

The `release` action will run the set of tests, and if they pass, it will publish the package to PyPI.

> [!IMPORTANT]
> In most instances you should not have to publish a release by hand. If there is ever a need to do that, we recommend that appropriate notes be left against the release.
!!! Important

In most instances you should not have to publish a release by hand. If there is ever a need to do that, we recommend that appropriate notes be left against the release.

### Writing Release Notes

Features
With every release we must ensure that this documentation is kept relevant and we leave a trace of what has changed in beautifully written release notes.

> " If you don't have documentation, you don't have a minimum viable product." - [Paige Bailey](https://github.com/dynamicwebpaige#-if-you-dont-have-documentation-you-dont-have-a-minimum-viable-product)

The project recommends the following pattern, which is enforced by a template in our Github workflow. We understand that you can use generative AI tools to write your release notes, but we believe that users of this library want to hear what us the maintainers thought is important as opposed to a computed list of changes.

We understand it takes that little bit longer to do this right, doing this well shows our level of care.

Improvements
- **Preamble**, a summary of the release where we outline the major motivation (be it a feature or a service release)
- **Features**, a concise list of what we have added to this release and did not exist before
- **Improvements**, a list of improvements made to existing features
- **Bug Fixes**, this is different to `improvements` these are genuine issues that were found

Bug Fixes
> Includes the sections relevant and leave out what's not. Be as human as possible, this is a conversation with our users.
6 changes: 3 additions & 3 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Our Python Toolkit focuses on enhancing the developer experience (DX) around the

!!! note

While Anomaly is part of Gallagher's Technical Partner Program, this project is not officially affiliated with Gallagher.
Anomaly is part of Gallagher's Technical Partner Program, however this project is **not** officially affiliated with Gallagher and is solely maintained by Anomaly.

## How to get started?

Expand All @@ -36,7 +36,7 @@ All of our tools are written using the Python programming languages. We provide

Gallagher publish their [API reference](https://gallaghersecurity.github.io/cc-rest-docs/ref/index.html) which is built from an OpenAPI spec with [Spectacle](https://github.com/sourcey/spectacle) documentation generator flavours. The OpenAPI spec is maintained by hand and [can be found on Github](https://github.com/GallagherSecurity/cc-rest-docs/tree/master/swagger).

While this is all you need to get started, it can be overwhelming to interact with the API directly.
Even with substantial documentation, it can be overwhelming to interact with the API directly due to concerns of security, understanding various Internet standards.

Our Python Toolkit aims to encapsulate the design requirements of the API and provide a Python interface for you to build your integrations. Simply put:

Expand All @@ -48,7 +48,7 @@ As we provided the viability of our commercial projects around Gallagher's infra

The API client draws inspiration from the works of [Stripe](https://stripe.com) or [pyndatic](https://pydantic.dev) who are known for providing a superb developer experience. Our aim is to provide a similar quality of developer experience for Gallagher projects.

While the entire project was built in self interest, we hope that many of you out there enjoy the developer experience if brings.
This project was started off in solving a problem for ourselves, but we hope that many of you out there enjoy the developer experience if brings.

## Background

Expand Down
17 changes: 17 additions & 0 deletions docs/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ poetry add gallagher[all]

### SDK

To use the API (or the associated tools, as they use the API client in return) you must have an API key supplied by the Command Centre instance. The same key is used if you were were using the API on premise or in the cloud.

You would typically read the API key from an environment variable or a secrets manager, depending on your setup.

You should only ever setup the API key once in your application instance. The rest of the client is designed to discover. An example of how you would do this would look like:

```python
from gallagher import cc
api_key = os.environ.get("GACC_API_KEY")
cc.api_key = api_key
```

following this you can call any of the SDK methods and the client will performance the necessary discovery and authentication. If you fail to set the API key, the client will raise the following exceptions:

- `NoAPIKeyProvidedError` - If the API key is not set.
- `ValueError` - If the API key does not conform to the expected format (which looks like eight tokens separated by `-`).

### Command Line Interface

### Terminal User Interface
Expand Down
72 changes: 71 additions & 1 deletion docs/docs/python-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ If you are fetching a `detail` then they are returned on their own as part of th

## API Endpoint Lifecycle

While it's not important for SDK users to understand how it works under the hood, it does pay to understand what its doing to fulfil your requests. Each endpoint inherits from a base class called `APIEndpoint` defined in `gallagher/cc/core.py` and provides a configuration that describes the behaviour of the endpoint (in accordance with the Command Centre API).
You do not need to look under the hood to work with the API client. This section was written for you to understand how we implement Gallagher's requirements for standard based development. Each endpoint inherits from a base class called `APIEndpoint` defined in `gallagher/cc/core.py` and provides a configuration that describes the behaviour of the endpoint (in accordance with the Command Centre API).

Before your request is sent, the endpoint will:

Expand Down Expand Up @@ -270,6 +270,76 @@ while items_summary.next:
determined from the response object. This ensures that we can update the SDK as the API changes
leaving your code intact.

# Updates and Changes

Entities like `Cardholders`, `Alarms`, `Items`, and `Event` provide `updates` or `changes`, that can be monitored for updates. Essentially these are long poll endpoints that:

- Provide a set of recent update as a `Summary` Response
- End with an `next` URL which provides the next set of updates
- Returns an empty set of updates if there are no updates within around 30 seconds
- Always returns a `next` URL to follow, even in the case of an empty set of updates

The SDK provide a clean `async` way of following these updates where you can run a `for` loop over an `async` generator which `yields` updates as they are available.

It uses an `asyncio` event to control the loop, and you can stop the loop by calling `event.clear()`. This is so you can control the event loop based on an application level trigger e.g a user navigating to a particular interface.

Here's a sample of how you can follow updates and stop the loop if there are no updates:

```python
import os
import asyncio

from gallagher import cc
from gallagher.cc.alarms import Alarms

async def main():
api_key = os.environ.get("GACC_API_KEY")
cc.api_key = api_key

# Used to control the event loop
event = asyncio.Event()
event.set()

async for updates in Alarms.follow(
event=event,
):

for update in updates.updates:
print(update)

# Examples of stopping the loop if
# we got no updates
if len(updates.updates) == 0:
event.clear()

if __name__ == "__main__":
asyncio.run(main())
```

Endpoints that provide either an `update` or a `change` method will provide:

- `endpoint_follow` which will be the HATEOS discovered endpoint
- `dto_follow` which is a DTO class (typically a Summary) to be used to parse the updates

Following is an extract from the `Alarm` class to demonstrate how it's configured:

```python
@classmethod
async def get_config(cls) -> EndpointConfig:
"""Return the configuration for Alarms

Arguments:
cls: class reference
"""
return EndpointConfig(
endpoint=Capabilities.CURRENT.features.alarms.alarms,
endpoint_follow=Capabilities.CURRENT.features.alarms.updates,
dto_follow=AlarmUpdateResponse,
dto_list=AlarmSummaryResponse,
dto_retrieve=AlarmDetail,
)
```

## Error Handling

### Exceptions
Expand Down
Empty file added examples/__init__.py
Empty file.
Empty file added examples/__main__.py
Empty file.
29 changes: 29 additions & 0 deletions examples/alarm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
""" Alarms

Demonstrations of various useful features around alarms, starting
with tailing the alarms log, and then moving on to acknowledging

"""
import os
import asyncio

from gallagher import cc
from gallagher.cc.alarms import Alarms

async def main():
api_key = os.environ.get("GACC_API_KEY")
cc.api_key = api_key

event = asyncio.Event()
event.set()

async for updates in Alarms.follow(
event=event,
):

for update in updates.updates:
print(update)

if len(updates.updates) == 0:
event.clear()



if __name__ == "__main__":
asyncio.run(main())
3 changes: 3 additions & 0 deletions examples/cardholder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
""" Cardholders

"""
Loading
Loading