lirayspy 0.0.5
Python client library for LiRAYS API - Fiber Network Design
Requires Python: >=3.8
Keywords: lirays, api, gis, fiber, network, design

# LiRAYS Python API Client

[![PyPI version](https://badge.fury.io/py/lirayspy.svg)](https://badge.fury.io/py/lirayspy)
[![Python Support](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://pypi.org/project/lirayspy/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A comprehensive Python client library for the **LiRAYS API** - the leading platform for fiber network design and geographic information systems (GIS). This library provides a clean, intuitive interface to interact with all LiRAYS API endpoints including authentication, project management, plan execution, layer management, feature operations, and real-time tool execution.

## Features

- **Authentication**: Secure JWT-based authentication with automatic token refresh
- **Project Management**: Complete CRUD operations for GIS projects
- **Plan Management**: Create and manage analysis plans with status tracking
- **Layer Management**: Handle 25+ geographic layer types with full classification support
- **Feature Operations**: Full geometry support (Points, LineStrings, Polygons) with batch operations
- **Tool Execution**: Real-time streaming execution of GIS analysis tools with progress tracking
- **Context Manager Support**: Automatic resource cleanup and connection management
- **Type Safety**: Full type hints and enum support for better development experience
- **Session Management**: Persistent sessions with automatic reconnection

## Installation

```bash
pip install lirayspy
```

## Quick Start

```python
from lirayspy import LiRAYSApiClient
from lirayspy.tools import LayerClass, GeomType, PlanStatus

# Initialize client with context manager (recommended)
with LiRAYSApiClient("https://api.lirays.com") as client:
    # Authenticate
    if client.login("your-email@example.com", "your-password"):
        print("Successfully authenticated!")
        
        # Create a project
        project = client.create_project(
            name="Fiber Network Expansion",
            description="Q4 2024 fiber network expansion project"
        )
        
        # Create a plan
        plan = client.create_plan(
            project_id=project["id"],
            name="Phase 1 Analysis",
            status=PlanStatus.IN_PROGRESS
        )
        
        # Create a layer for fiber infrastructure
        layer = client.create_layer(
            plan_id=plan["id"],
            layer_class=LayerClass.FIBER_24,
            name="24-Core Fiber Network"
        )
        
        # Add a point feature (network node)
        feature = client.create_feature(
            layer_id=layer["id"],
            geom_type=GeomType.POINT,
            coordinates=[-122.4194, 37.7749]  # San Francisco coordinates
        )
        
        print(f"Created feature: {feature['id']}")
```

## API Documentation

### Authentication & Session Management

```python
from lirayspy import LiRAYSApiClient

# Initialize client with custom configuration
client = LiRAYSApiClient(
    base_url="https://api.lirays.com",
    version="v1"  # API version
)

# Login with credentials
success = client.login("user@example.com", "password")
if success:
    print(f"Logged in as: {client.email}")
    print(f"Access token: {client.access_token}")

# Health check
if client.health_check():
    print("API is healthy and responding")

# Manual token refresh (usually automatic)
if client.refresh_token():
    print("Token refreshed successfully")

# Reset client configuration
client.reset(
    base_url="https://new-api.lirays.com",
    version="v2"
)

# Logout (clears all session data)
client.logout()

# Close session (cleanup resources)
client.close()
```

### Project Management

Complete CRUD operations for projects:

```python
# Create a new project
project = client.create_project(
    name="Smart City Infrastructure",
    description="IoT and fiber network integration project"  # Optional
)
print(f"Created project ID: {project['id']}")

# List projects with pagination and filtering
projects = client.get_projects(
    page=1,
    per_page=10,
    start_utc="2024-01-01T00:00:00Z",  # Optional date filter
    end_utc="2024-12-31T23:59:59Z",    # Optional date filter
    sort_by_updated=True,              # Sort by update date
    desc=True                          # Descending order
)
print(f"Found {len(projects['resources'])} projects")
print(f"Total pages: {projects['pages']}")

# Get specific project details
project_details = client.get_project(project["id"])
print(f"Project: {project_details['name']}")
print(f"Created: {project_details['created_at']}")

# Update project
updated_project = client.update_project(
    project["id"],
    name="Smart City Infrastructure v2.0",    # Optional
    description="Updated project scope"       # Optional
)

# Delete project (cascades to all child resources)
client.delete_project(project["id"])
```

### Plan Management

Plans organize analysis workflows within projects:

```python
from lirayspy.tools import PlanStatus

# Create a plan
plan = client.create_plan(
    project_id=project["id"],
    name="Network Optimization Analysis",
    description="Optimize fiber routing for maximum efficiency",  # Optional
    status=PlanStatus.IN_PROGRESS  # Optional, defaults to NOT_ASSIGNED
)

# Available plan statuses:
# PlanStatus.NOT_ASSIGNED, IN_PROGRESS, COMPLETED, CANCELLED, ARCHIVED

# List plans with filtering
plans = client.get_plans(
    page=1,
    per_page=20,
    project_id=project["id"],      # Filter by project
    start_utc="2024-01-01T00:00:00Z",  # Optional
    end_utc="2024-12-31T23:59:59Z",    # Optional
    sort_by_updated=True,
    desc=True
)

# Get specific plan
plan_details = client.get_plan(plan["id"])

# Update plan
updated_plan = client.update_plan(
    plan["id"],
    name="Network Optimization Analysis v2",  # Optional
    description="Updated analysis scope",     # Optional
    status=PlanStatus.COMPLETED               # Optional
)

# Delete plan (cascades to layers and features)
client.delete_plan(plan["id"])
```

### Layer Management

LiRAYS supports 25+ layer types for comprehensive network design:

```python
from lirayspy.tools import LayerClass

# Get system information
layer_classes = client.get_layer_classes()      # All available layer types
ready_layers = client.get_ready_layer_classes() # Ready-to-use layer types

# Create different types of layers
fiber_layer = client.create_layer(
    plan_id=plan["id"],
    layer_class=LayerClass.FIBER_144,
    name="High-Capacity Backbone"  # Optional
)

vault_layer = client.create_layer(
    plan_id=plan["id"],
    layer_class=LayerClass.VAULT_24X36,
    name="Distribution Vaults"
)

# List layers with filtering
layers = client.get_layers(
    page=1,
    per_page=50,
    plan_id=plan["id"],            # Filter by plan
    start_utc="2024-01-01T00:00:00Z",  # Optional
    end_utc="2024-12-31T23:59:59Z",    # Optional
    sort_by_updated=True,
    desc=True
)

# Get specific layer
layer_details = client.get_layer(fiber_layer["id"])

# Update layer
updated_layer = client.update_layer(
    fiber_layer["id"],
    layer_class=LayerClass.FIBER_288,  # Optional
    name="Updated Fiber Network"       # Optional
)

# Delete layer (cascades to all features)
client.delete_layer(fiber_layer["id"])
```

### GeoJSON Integration

Work with standard GeoJSON format for easy integration:

```python
import json

# Create layer from GeoJSON file
with open("network_data.geojson", "r") as f:
    geojson_data = json.load(f)

layer_from_geojson = client.create_layer_from_geojson(
    plan_id=plan["id"],
    layer_class=LayerClass.PARCELS,
    name="Property Parcels",       # Optional
    geojson=geojson_data
)

# Create layer from GeoJSON file path (automatic loading)
layer_from_file = client.create_layer_from_geojson(
    plan_id=plan["id"],
    layer_class=LayerClass.BUILDINGS,
    name="Building Footprints",
    geojson="buildings.geojson"  # File path
)

# Create from GeoJSON string
geojson_string = '''
{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [-122.4194, 37.7749]
            },
            "properties": {
                "name": "Network Node 1",
                "type": "MST"
            }
        }
    ]
}
'''

layer_from_string = client.create_layer_from_geojson(
    plan_id=plan["id"],
    layer_class=LayerClass.MST,
    geojson=geojson_string
)

# Export layer as GeoJSON
geojson_export = client.get_geojson_layer(
    plan_id=plan["id"],
    layer_class=LayerClass.FIBER_144
)

# Save exported GeoJSON
with open("exported_layer.geojson", "w") as f:
    json.dump(geojson_export, f, indent=2)
```

### Feature Operations

Complete geometry support with properties and batch operations:

```python
from lirayspy.tools import GeomType

# Create features with properties
point_feature = client.create_feature(
    layer_id=layer["id"],
    geom_type=GeomType.POINT,
    coordinates=[-122.4194, 37.7749],
    properties={                    # Optional custom properties
        "node_id": "MST_001",
        "capacity": 144,
        "installation_date": "2024-01-15",
        "active": True
    }
)

# Create line feature (fiber route)
line_feature = client.create_feature(
    layer_id=layer["id"],
    geom_type=GeomType.LINESTRING,
    coordinates=[
        [-122.4194, 37.7749],
        [-122.4184, 37.7759],
        [-122.4174, 37.7769]
    ],
    properties={
        "route_id": "ROUTE_001",
        "fiber_count": 144,
        "length_km": 2.5
    }
)

# Create polygon feature (service area)
polygon_feature = client.create_feature(
    layer_id=layer["id"],
    geom_type=GeomType.POLYGON,
    coordinates=[[
        [-122.4200, 37.7740],
        [-122.4180, 37.7740],
        [-122.4180, 37.7760],
        [-122.4200, 37.7760],
        [-122.4200, 37.7740]  # Must close the polygon
    ]],
    properties={
        "zone_id": "ZONE_A",
        "coverage_type": "residential",
        "priority": "high"
    }
)

# Batch create features with properties
batch_coordinates = [
    [-122.4100, 37.7800],
    [-122.4110, 37.7810],
    [-122.4120, 37.7820]
]

batch_properties = [
    {"node_id": "NODE_001", "type": "splice"},
    {"node_id": "NODE_002", "type": "distribution"},
    {"node_id": "NODE_003", "type": "terminal"}
]

batch_result = client.create_feature_batch(
    layer_id=layer["id"],
    geom_type=GeomType.POINT,
    features_coordinates=batch_coordinates,
    properties=batch_properties  # Optional, must match coordinates length
)

# List features with filtering
features = client.get_features(
    page=1,
    per_page=100,
    layer_id=layer["id"],          # Filter by layer
    start_utc="2024-01-01T00:00:00Z",  # Optional
    end_utc="2024-12-31T23:59:59Z",    # Optional
    sort_by_updated=True,
    desc=True
)

# Get specific feature
feature_details = client.get_feature(point_feature["id"])
print(f"Feature properties: {feature_details['properties']}")

# Update feature (geometry, type, or properties)
updated_feature = client.update_feature(
    point_feature["id"],
    geom_type=GeomType.POINT,      # Optional
    coordinates=[-122.4200, 37.7750],  # Optional
    properties={                   # Optional (replaces existing)
        "node_id": "MST_001_UPDATED",
        "capacity": 288,
        "last_maintenance": "2024-03-01"
    }
)

# Delete individual feature
client.delete_feature(point_feature["id"])

# Batch delete multiple features
feature_ids = [f["id"] for f in batch_result["features"]]
client.delete_feature_batch(feature_ids)
```

### Tool Execution & Analysis

Real-time streaming execution of GIS analysis tools:

```python
# Get system capabilities
available_actions = client.get_available_actions()
print(f"Available tools: {available_actions}")

# Get tool requirements and parameters
if "network_optimization" in available_actions:
    requirements = client.get_tool_required_payload("network_optimization")
    print(f"Required parameters: {requirements}")

# Execute tool with streaming progress updates
action_config = {
    "optimization_type": "cost_distance",
    "analysis_depth": 3,
    "include_redundancy": True,
    "cost_per_meter": 25.50
}

print("Executing network optimization tool...")
for result in client.execute_action(
    action="network_optimization",
    config=action_config,
    plan_id=plan["id"]  # Note: Uses plan_id, not input_ parameter
):
    status = result.get("status", "unknown")
    progress = result.get("progress", 0)
    
    print(f"Status: {status} | Progress: {progress}%")
    
    if status == "completed":
        print("✅ Analysis completed successfully!")
        if "result" in result:
            print(f"Results: {result['result']}")
        if "partial_result" in result:
            print(f"Data: {result['partial_result']}")
        break
    elif status == "failed":
        error = result.get("partial_result", {}).get("error", "Unknown error")
        print(f"❌ Analysis failed: {error}")
        break
    elif status == "partial":
        # Intermediate results available
        if "partial_result" in result:
            print(f"Partial data: {result['partial_result']}")

# Example: Mock API action for testing
for result in client.execute_action(
    action="mock_api_action",
    config={
        "size": 10,           # Number of updates
        "sleep_time": 0.5     # Delay between updates
    },
    plan_id=plan["id"]
):
    if result.get("status") in ["completed", "failed"]:
        break
```

### Base Information & System Status

Access system information and capabilities:

```python
# Health check
is_healthy = client.health_check()
print(f"API Status: {'Healthy' if is_healthy else 'Unhealthy'}")

# Get all available layer types
all_layer_classes = client.get_layer_classes()
print(f"Available layer types: {len(all_layer_classes)}")

# Get ready-to-use layer types (subset of all types)
ready_layer_classes = client.get_ready_layer_classes()
print(f"Ready layer types: {len(ready_layer_classes)}")

# Get available analysis tools
available_tools = client.get_available_actions()
print(f"Available analysis tools: {available_tools}")

# Get specific tool requirements
for tool in available_tools:
    try:
        requirements = client.get_tool_required_payload(tool)
        print(f"{tool} requirements: {requirements}")
    except Exception as e:
        print(f"Could not get requirements for {tool}: {e}")
```

## Advanced Usage

### Context Manager (Recommended)

Always use the context manager for automatic resource cleanup:

```python
with LiRAYSApiClient("https://api.lirays.com") as client:
    client.login("user@example.com", "password")
    
    # Perform operations...
    # Session is automatically closed when exiting the context
```

### Error Handling

```python
try:
    with LiRAYSApiClient("https://api.lirays.com") as client:
        if not client.login("user@example.com", "wrong-password"):
            raise Exception("Authentication failed")
            
        project = client.create_project("Test Project")
        
except Exception as e:
    print(f"Operation failed: {e}")
    # Context manager ensures cleanup even on errors
```

### Custom Configuration

```python
# Custom API endpoint and version
client = LiRAYSApiClient(
    base_url="https://custom-api.lirays.com",
    version="v2"
)

# Reset client configuration
client.reset(
    base_url="https://new-api.lirays.com",
    version="v1"
)
```

## Layer Types Reference

LiRAYS supports comprehensive layer classification for fiber network design:

### Primary Infrastructure
- `PLAN_AREA` - Project planning areas
- `PARCELS` - Property parcels and boundaries  
- `CABINET` - Network cabinets and enclosures

### Network Logical Points
- `MST` - Main Service Terminals
- `SPLICE` - Splice points and connections
- `SPLICE_MST` - Combined splice and MST points
- `RESIDENTIAL` - Residential connection points

### Hand-holes and Vaults
- `VAULT_24X36` - 24"×36" utility vaults
- `VAULT_17X30` - 17"×30" utility vaults  
- `VAULT_10X15` - 10"×15" utility vaults
- `VAULT_OTHERS` - Custom vault configurations

### Fiber Infrastructure
- `FIBER_24` - 24-count fiber cables
- `FIBER_48` - 48-count fiber cables
- `FIBER_72` - 72-count fiber cables
- `FIBER_144` - 144-count fiber cables
- `FIBER_288` - 288-count fiber cables
- `FIBER_TAIL` - Fiber tail connections
- `FIBER_DROP` - Drop fiber connections

### Conduits and Infrastructure
- `CONDUIT_075` - 0.75" conduit systems
- `CONDUIT_125` - 1.25" conduit systems
- `TUNNEL` - Underground tunnel systems

## Testing

The package includes a comprehensive test suite in the demo:

```python
# Run the complete API test suite
python demo/demo.py
```

The test suite covers:
- Authentication and health checks
- Complete CRUD operations for all resource types
- Batch operations and bulk processing
- Tool execution with progress tracking
- Data integrity verification
- Automatic cleanup and state restoration

## Examples

### Complete Workflow Example

```python
from lirayspy import LiRAYSApiClient
from lirayspy.tools import LayerClass, GeomType, PlanStatus

def create_fiber_network_project():
    """Complete example: Create a fiber network design project"""
    
    with LiRAYSApiClient("https://api.lirays.com") as client:
        # Authenticate
        if not client.login("engineer@network.com", "password"):
            return "Authentication failed"
        
        try:
            # Create project
            project = client.create_project(
                name="Downtown Fiber Expansion 2024",
                description="High-speed fiber network expansion for downtown district"
            )
            
            # Create analysis plan
            plan = client.create_plan(
                project_id=project["id"],
                name="Route Optimization Analysis",
                description="Optimize fiber routes for minimal cost and maximum coverage",
                status=PlanStatus.IN_PROGRESS
            )
            
            # Create infrastructure layers
            fiber_layer = client.create_layer(
                plan_id=plan["id"],
                layer_class=LayerClass.FIBER_144,
                name="Backbone Fiber Network"
            )
            
            vault_layer = client.create_layer(
                plan_id=plan["id"],
                layer_class=LayerClass.VAULT_24X36,
                name="Distribution Vaults"
            )
            
            # Add network infrastructure
            # Main distribution points
            vault_coords = [
                [-122.4194, 37.7749],  # Downtown hub
                [-122.4154, 37.7709],  # South district
                [-122.4234, 37.7789]   # North district
            ]
            
            vault_features = client.create_feature_batch(
                layer_id=vault_layer["id"],
                geom_type=GeomType.POINT,
                features_coordinates=vault_coords
            )
            
            # Fiber backbone route
            backbone_route = client.create_feature(
                layer_id=fiber_layer["id"],
                geom_type=GeomType.LINESTRING,
                coordinates=[
                    [-122.4194, 37.7749],
                    [-122.4184, 37.7759],
                    [-122.4174, 37.7769],
                    [-122.4164, 37.7779]
                ]
            )
            
            print(f"Created project: {project['name']}")
            print(f"Created plan: {plan['name']}")
            print(f"Created {len(vault_features['features'])} vault points")
            print(f"Created backbone route: {backbone_route['id']}")
            
            # Execute network analysis
            print("Running network optimization analysis...")
            for result in client.execute_action(
                action="network_optimization",
                config={"optimization_type": "cost_distance"},
                plan_id=plan["id"]
            ):
                if result.get("status") == "completed":
                    print("Network analysis completed!")
                    break
                elif result.get("status") == "failed":
                    print(f"Analysis failed: {result.get('error')}")
                    break
            
            return f"Project '{project['name']}' created successfully!"
            
        except Exception as e:
            return f"Error: {e}"

# Run the example
result = create_fiber_network_project()
print(result)
```

## Development

### Setting up Development Environment

```bash
# Clone the repository
git clone https://github.com/LiRAYSgj/Lirays-Api-Python-Client.git
cd Lirays-Api-Python-Client

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Run tests
python demo/demo.py

# Format code
black lirayspy/ demo/
isort lirayspy/ demo/

# Type checking
mypy lirayspy/
```

### Running Tests

```bash
# Run the comprehensive test suite
python demo/demo.py

# The test will prompt for:
# - API URL (default: https://api.lirays.com)
# - Email credentials
# - Password
```

## Requirements

- **Python**: 3.8+
- **Dependencies**:
  - `requests >= 2.31.0` - HTTP client library
  - `typing-extensions >= 4.0.0` (for Python < 3.10)

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Support

- **Documentation**: [GitHub Repository](https://github.com/LiRAYSgj/Lirays-Api-Python-Client)
- **Issues**: [Report Bug](https://github.com/LiRAYSgj/Lirays-Api-Python-Client/issues)
- **Email**: support@lirays.com

## Links

- **PyPI Package**: [https://pypi.org/project/lirayspy/](https://pypi.org/project/lirayspy/)
- **Source Code**: [https://github.com/LiRAYSgj/Lirays-Api-Python-Client](https://github.com/LiRAYSgj/Lirays-Api-Python-Client)
- **LiRAYS Platform**: [https://lirays.com](https://lirays.com)

---

**Built for the fiber network design community**