# pyGuardPoint

> Python wrapper for the GuardPoint 10 access control system REST API (OData-based).
> Manages cardholders, cards, readers, controllers, alarm zones, access events,
> and real-time WebSocket event streaming.

## Install

```bash
pip install pyGuardPoint
```

## Quick start

```python
from pyGuardPoint import GuardPoint, Cardholder, GuardPointError

# Local server (no mTLS)
gp = GuardPoint(host='http://192.168.1.100:10695', username='admin', pwd='admin')

# Remote server with mutual TLS (P12 client certificate)
gp = GuardPoint(
    host='https://sensoraccess.duckdns.org',
    username='admin',
    pwd='admin',
    p12_file='/path/to/cert.p12',
    p12_pwd='password',
)

# Create a cardholder
ch = Cardholder(firstName='Jane', lastName='Doe', pinCode='1234')
created = gp.new_card_holder(ch)   # returns Cardholder (not a UID string)
print(created.uid)

# Update
created.description = 'VIP'
gp.update_card_holder(created)     # returns True

# Delete
gp.delete_card_holder(created)     # returns True

# Search
results = gp.get_card_holders(search_terms='Jane', limit=10)

# Read-only resources
areas         = gp.get_areas()
readers       = gp.get_readers()
controllers   = gp.get_controllers()
security_grps = gp.get_security_groups()
access_grps   = gp.get_access_groups()
alarm_zones   = gp.get_alarm_zones()
sites         = gp.get_sites()
departments   = gp.get_departments()
weekly_progs  = gp.get_weekly_programs()
events        = gp.get_access_events(limit=50)
```

## Async client

```python
import asyncio
from pyGuardPoint import GuardPointAsyncIO

async def main():
    gp = GuardPointAsyncIO(host='https://...', username='admin', pwd='admin',
                           p12_file='cert.p12', p12_pwd='pwd')
    cardholders = await gp.get_card_holders(limit=10)
    await gp.close()

asyncio.run(main())
```

## Real-time events (SignalR WebSocket)

```python
import asyncio, threading
from pyGuardPoint import GuardPoint

gp = GuardPoint(host='https://...', username='admin', pwd='admin',
                p12_file='cert.p12', p12_pwd='pwd')

client = gp.get_signal_client()

async def on_open(): print('connected')
async def on_access_event(msg): print('access event:', msg)

client.on_open(on_open)
client.on('AccessEventArrived', on_access_event)

# start_listening() blocks — run in a daemon thread
t = threading.Thread(target=gp.start_listening, args=(client,), daemon=True)
t.start()
```

## Key classes

- `GuardPoint` — synchronous client (http.client)
- `GuardPointAsyncIO` — async client (aiohttp), mirrors all methods with `async def`
- `GuardPointThreaded` — callback-based wrapper for GUI / threaded applications
- `Cardholder` — main entity; supports nested `cards`, `cardholderPersonalDetail`,
  `cardholderCustomizedField`, `securityGroup`, `insideArea`
- `Card` — access card with `cardCode`, `cardType`, `status`, `cardholderUID`
- `CardholderPersonalDetail` — company, email, department, phone
- `CardholderCustomizedField` — server-defined custom string/number/bool fields
- `AccessEvent`, `AlarmEvent`, `AuditEvent` — event log entries

## Auth options

```python
from pyGuardPoint import GuardPointAuthType

# Default: bearer token (username + password → JWT)
gp = GuardPoint(host=..., username='admin', pwd='admin')

# API key
gp = GuardPoint(host=..., auth=GuardPointAuthType.BEARER_TOKEN, key='<uuid-api-key>')

# Basic auth
gp = GuardPoint(host=..., auth=GuardPointAuthType.BASIC, username='admin', key='<api-key>')
```

## Important behaviours

- `new_card_holder()` and `new_card()` return the **created object**, not a UID string.
- `update_*()` and `delete_*()` return `True` on success, raise `GuardPointError` on failure.
- The library tracks field changes (Observable pattern) — only modified fields are sent in PATCH requests.
- No-argument list methods (`get_areas`, `get_security_groups`, etc.) fetch all records; do not pass `limit=`.
- The server soft-deletes cardholders — a fetch immediately after deletion may still return the record.
- Card codes must be hex characters only (0-9, A-F), max 8 characters.

## Running the bundled tests

The package includes an integration test suite in `tests/` with a bundled client certificate
for the public demo server (`https://sensoraccess.duckdns.org`).

```bash
python -m pyGuardPoint.tests.run_tests          # sync runner
python -m pyGuardPoint.tests.run_tests_async    # async runner
python -m pyGuardPoint.tests.run_tests_signalr  # SignalR + event simulation
```

Or with pytest (from the package root):
```bash
pytest tests/
```

## Source and examples

Full source, examples, and issue tracker:
https://github.com/SensorAccess/pyGuardPoint
