Metadata-Version: 2.4
Name: roomzin-py
Version: 1.0.0
Summary: Official Python SDK for Roomzin
Author-email: Mehdi Javani <mehdy.javany@gmail.com>
License: Business Source License 1.1
Project-URL: Homepage, https://github.com/m-javani/roomzin-py
Project-URL: Repository, https://github.com/m-javani/roomzin-py
Project-URL: Bug Tracker, https://github.com/m-javani/roomzin-py/issues
Keywords: roomzin,sdk,cache,api,client
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: Other/Proprietary License
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24
Dynamic: license-file

# Roomzin Python SDK

Official Python SDK for [Roomzin](https://m-javani.github.io/roomzin-doc/) — a high-performance in-memory inventory engine for booking platforms.

The SDK provides a clean, Pythonic interface for communicating with Roomzin servers in both standalone and clustered deployments. It automatically manages routing, failover, connection pooling, and cluster topology changes.

---

## Features

- Automatic request routing (leader for writes, followers for reads)
- Built-in failover and cluster discovery
- Connection pooling
- Standalone and clustered deployment support
- Fully typed Python API (PEP 484)
- Context manager support for resource management

---

## Requirements

- Python 3.8 or later
- Roomzin Server v1.x

---

## Installation

```bash
pip install roomzin-py
# or
uv pip install roomzin-py
```

---

## Client Setup

### Standalone

```python
from roomzin_py import new_client, new_config_builder

cfg = (
    new_config_builder()
    .with_host("127.0.0.1")
    .with_tcp_port(7777)
    .with_token("abc123")
    .with_timeout(5.0)
    .with_keep_alive(30.0)
    .build()
)

client = new_client(cfg)
client.close()
```

### Cluster (Static Discovery)

```python
from roomzin_py import new_client, new_config_builder
from roomzin_py.types import NodeAddr

static_discovery = [
    NodeAddr("roomzin-0", "172.20.0.10", 7777, 8080),
    NodeAddr("roomzin-1", "172.20.0.11", 7777, 8080),
    NodeAddr("roomzin-2", "172.20.0.12", 7777, 8080),
]

cfg = (
    new_config_builder()
    .with_seed_node_ids("roomzin-0,roomzin-1,roomzin-2")
    .with_static_discovery(static_discovery)
    .with_tcp_port(7777)
    .with_api_port(8080)
    .with_token("abc123")
    .with_timeout(5.0)
    .with_keep_alive(30.0)
    .build()
)

client = new_client(cfg)
client.close()
```

### Cluster (HTTP Discovery)

```python
cfg = (
    new_config_builder()
    .with_seed_node_ids("roomzin-0,roomzin-1,roomzin-2")
    .with_http_discovery("http://discovery-service:8080/nodes")
    .with_tcp_port(7777)
    .with_api_port(8080)
    .with_token("abc123")
    .with_timeout(5.0)
    .with_keep_alive(30.0)
    .build()
)

client = new_client(cfg)
```

---

## Discovery Configuration

Roomzin SDKs need to know how to reach each Roomzin node in the cluster. The cluster nodes communicate with each other using internal address resolvers, but the SDK as an external client needs actual network addresses (IP:port or hostname:port) to connect.

The SDK fetches the cluster topology from the Roomzin cluster itself. This topology includes the node identities of the leader and followers. The SDK then uses discovery to resolve these node identities into actual network addresses.

Two discovery modes are supported:

### Static Discovery

The SDK gets the mapping once in config and never updates it. Use this when your cluster nodes have stable, predictable addresses.

### HTTP Discovery

The SDK periodically fetches the mapping from an HTTP endpoint. Use this when cluster nodes are dynamic (e.g., Kubernetes pods with changing IPs).

---

## Property Management

### set_prop
Adds or updates a property.

```python
client.set_prop(SetPropPayload(
    segment="downtown",
    area="manhattan",
    property_id="hotel_123",
    property_type="hotel",
    category="luxury",
    stars=4,
    latitude=40.7128,
    longitude=-74.0060,
    amenities=["wifi", "pool", "gym"]
))
```

### search_prop
Searches properties by segment, area, type, or location.

```python
# By segment
ids = client.search_prop(SearchPropPayload(segment="downtown"))

# By area
ids = client.search_prop(SearchPropPayload(
    segment="downtown",
    area="manhattan"
))

# By location (radius search)
ids = client.search_prop(SearchPropPayload(
    segment="downtown",
    latitude=40.7128,
    longitude=-74.0060
))
```

### prop_exist
Checks if a property exists.

```python
exists = client.prop_exist("hotel_123")
```

### prop_room_exist
Checks if a specific room type exists for a property.

```python
exists = client.prop_room_exist(PropRoomExistPayload(
    property_id="hotel_123",
    room_type="suite"
))
```

### prop_room_list
Lists all room types for a property.

```python
rooms = client.prop_room_list("hotel_123")
```

### prop_room_date_list
Lists dates with availability data for a property and room type.

```python
dates = client.prop_room_date_list(PropRoomDateListPayload(
    property_id="hotel_123",
    room_type="suite"
))
```

---

## Room Package Management

### set_room_pkg
Sets availability, price, and rate features for a room type on a date.

```python
client.set_room_pkg(SetRoomPkgPayload(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20",
    availability=10,
    final_price=199,
    rate_feature=["free_cancellation", "breakfast_included"]
))
```

### set_room_avl
Sets exact availability for a room type on a specific date.

```python
new_avail = client.set_room_avl(UpdRoomAvlPayload(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20",
    amount=20
))
```

### inc_room_avl
Increases availability (e.g., on cancellation).

```python
new_avail = client.inc_room_avl(UpdRoomAvlPayload(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20",
    amount=1
))
```

### dec_room_avl
Decreases availability (e.g., on booking).

```python
new_avail = client.dec_room_avl(UpdRoomAvlPayload(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20",
    amount=2
))
```

### get_prop_room_day
Gets availability and pricing for a specific room on a specific date.

```python
day = client.get_prop_room_day(GetRoomDayRequest(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20"
))
print(f"Avail: {day.availability}, Price: {day.final_price}")
```

---

## Search & Query

### search_avail
Searches available rooms by filters.

```python
results = client.search_avail(SearchAvailPayload(
    segment="downtown",
    room_type="suite",
    dates=["2026-07-20", "2026-07-21"],
    limit=50,
    min_price=100,
    max_price=300,
    amenities=["wifi", "pool"],
    rate_feature=["free_cancellation"]
))

for result in results:
    print(f"Property: {result.property_id}")
    for day in result.days:
        print(f"  {day.date}: Avail {day.availability}, Price {day.final_price}")
```

### get_segments
Lists all active segments with their property counts.

```python
segments = client.get_segments()
for seg in segments:
    print(f"{seg.segment}: {seg.count} properties")
```

### get_codes
Gets the current codec registry (used internally for validation).

```python
codes = client.get_codes()
print(codes.rate_features)
```

---

## Delete Operations

### del_room_day
Deletes availability for a specific room on a specific date.

```python
client.del_room_day(DelRoomDayRequest(
    property_id="hotel_123",
    room_type="suite",
    date="2026-07-20"
))
```

### del_prop_day
Deletes all data for a property on a specific date.

```python
client.del_prop_day(DelPropDayRequest(
    property_id="hotel_123",
    date="2026-07-20"
))
```

### del_prop_room
Deletes a room type from a property.

```python
client.del_prop_room(DelPropRoomPayload(
    property_id="hotel_123",
    room_type="suite"
))
```

### del_prop
Deletes an entire property.

```python
client.del_prop("hotel_123")
```

### del_segment
Deletes a segment and all properties within it.

```python
client.del_segment("downtown")
```

---

## Error Handling

All methods raise `RoomzinError`. Use the helper functions to classify errors:

```python
from roomzin_py import is_request, is_retry, is_client, is_internal

try:
    client.set_room_pkg(payload)
except RoomzinError as e:
    if is_request(e):
        # Business rule violation - fix the request
        print(f"Request error: {e.code}")
    elif is_retry(e):
        # Temporary condition - retry with backoff
        time.sleep(0.1)
        client.set_room_pkg(payload)
    elif is_client(e):
        # Authentication or protocol errors
        print(f"Client error: {e.message}")
    elif is_internal(e):
        # Unexpected server response
        raise RuntimeError("Internal error") from e
    else:
        # Fatal error
        raise
```

### Error Categories

| Category | Description | Action |
|----------|-------------|--------|
| **Client** | Authentication or protocol errors | Check credentials and configuration |
| **Request** | Invalid input or business rule violation | Fix request, don't retry |
| **Retry** | Temporary server condition (429, 503, 308) | Retry with backoff |
| **Internal** | Unexpected server response | Log and investigate |

---

## Client Lifecycle

Create a **single client** during application startup and reuse it throughout your application.

```python
# ✅ Good - create once, reuse
client = new_client(cfg)
# Use client everywhere...
client.close()

# ❌ Bad - creating per request
for req in requests:
    client = new_client(cfg)  # Don't do this
    client.set_room_pkg(req)
    client.close()
```

The client is safe for concurrent use and manages TCP connections internally. The client also supports context manager usage:

```python
with new_client(cfg) as client:
    # Use client
    ...
# Automatically closed
```

---

## API Reference

For the complete interface definition, see [`CacheClientAPI`](roomzin_py/api/client.py). All types are documented with Python docstrings.

---

## Documentation

For Roomzin concepts, deployment, and administration:

[https://m-javani.github.io/roomzin-doc/docs.html](https://m-javani.github.io/roomzin-doc/docs.html)

---

## Contributing

Contributions are welcome! Please open an issue before proposing large changes.

All contributions are subject to the BUSL-1.1 License terms.

---

## License

This SDK is licensed under the [BUSL-1.1 License](LICENSE).

**Note:** This SDK communicates with Roomzin Server, which requires a valid Roomzin license.

---

## Support

- **Documentation**: [roomzin-doc](https://m-javani.github.io/roomzin-doc/)
- **Community Q&A**: [GitHub Discussions](https://github.com/m-javani/roomzin-doc/discussions)
- **Issues**: [GitHub Issues](https://github.com/roomzin/roomzin-py/issues)
- **Security**: [mehdy.javany@gmail.com](mailto:mehdy.javany@gmail.com)

---

## Related Repositories

- [Roomzin Quickstart](https://github.com/m-javani/roomzin-quickstart) — Local Docker cluster
- [Roomzin Bench](https://github.com/m-javani/roomzin-bench) — Benchmarking tool
