TaxisLibrary/taxis_client.py
2025-09-23 17:02:06 +03:00

409 lines
16 KiB
Python

import aiohttp
from pydantic import BaseModel, Field
from typing import Optional, List, Union, Dict, Any
from datetime import datetime
from enum import Enum
import uuid
# Enums
class DriverStatus(str, Enum):
FREE = "free"
IN_SYSTEM_RESERVE = "in_system_reserve"
ON_ORDER = "on_ordrer"
OFFLINE = "offline"
class OrderStatus(str, Enum):
NEW = "new"
SEARCHING_TAXI = "searching_taxi"
IN_PROCESS = "in_process"
COMPLETED = "completed"
CUSTOMER_CANCELED = "customer_canceled"
DRIVER_CANCELED = "driver_canceled"
class PromptFor(str, Enum):
TAXI = "TAXI"
CLIENT = "CLIENT"
# Models
class ApiKeyOut(BaseModel):
api_key: str
class CarIn(BaseModel):
mark: str = Field(max_length=255)
model: str = Field(max_length=255)
color: str = Field(max_length=255)
number: str = Field(max_length=255)
class CarOut(BaseModel):
id: int
created_at: datetime
updated_at: datetime
mark: str = Field(max_length=255)
model: str = Field(max_length=255)
color: str = Field(max_length=255)
number: str = Field(max_length=255)
driver: Optional['DriverOut'] = None
class ClientIn(BaseModel):
name: Optional[str] = Field(default=None, max_length=255)
lastname: Optional[str] = Field(default=None, max_length=255)
surname: Optional[str] = Field(default=None, max_length=255)
phone: Optional[str] = Field(default=None, max_length=20)
class ClientOut(BaseModel):
id: int
name: Optional[str] = Field(default=None, max_length=255)
lastname: Optional[str] = Field(default=None, max_length=255)
surname: Optional[str] = Field(default=None, max_length=255)
phone: Optional[str] = Field(default=None, max_length=20)
created_at: datetime
updated_at: datetime
orders: Optional[List['OrderOut']] = None
class DriverAddressUpdate(BaseModel):
address: str
class DriverIn(BaseModel):
name: Optional[str] = Field(default=None, max_length=255)
lastname: Optional[str] = Field(default=None, max_length=255)
surname: Optional[str] = Field(default=None, max_length=255)
phone: Optional[str] = Field(default=None, max_length=20)
status: Optional[DriverStatus] = Field(default=DriverStatus.OFFLINE)
address: Optional[str] = Field(default=None, max_length=255)
latitude: Optional[Union[float, str]] = None
longitude: Optional[Union[float, str]] = None
car_id: Optional[int] = None
class DriverOut(BaseModel):
id: int
created_at: datetime
updated_at: datetime
name: Optional[str] = Field(default=None, max_length=255)
lastname: Optional[str] = Field(default=None, max_length=255)
surname: Optional[str] = Field(default=None, max_length=255)
phone: Optional[str] = Field(default=None, max_length=20)
status: Optional[DriverStatus] = Field(default=DriverStatus.OFFLINE)
address: Optional[str] = Field(default=None, max_length=255)
latitude: Optional[str] = None
longitude: Optional[str] = None
car: Optional['CarOut'] = None
orders: Optional[List['OrderOut']] = None
class Geocode(BaseModel):
longitude: float
latitude: float
class GlobalSettingsOut(BaseModel):
created_at: datetime
updated_at: datetime
id: int = 1
nearby_radius: Optional[int] = Field(default=3000)
class GlobalSettingsUpdate(BaseModel):
nearby_radius: Optional[int] = None
nominatium_id: Optional[int] = None
openstreetmap_id: Optional[int] = None
class NominatiumSettingsOut(BaseModel):
id: int
created_at: datetime
updated_at: datetime
url: Optional[str] = Field(default=None, max_length=255)
token: Optional[str] = Field(default=None, max_length=255)
class OpenStreetMapSettingsOut(BaseModel):
id: int
created_at: datetime
updated_at: datetime
url: Optional[str] = Field(default=None, max_length=255)
token: Optional[str] = Field(default=None, max_length=255)
class OrderIn(BaseModel):
status: Optional[OrderStatus] = Field(default=OrderStatus.NEW)
from_address: Optional[str] = Field(default=None, max_length=255)
from_latitude: Optional[Union[float, str]] = None
from_longitude: Optional[Union[float, str]] = None
to_address: Optional[str] = Field(default=None, max_length=255)
to_latitude: Optional[Union[float, str]] = None
to_longitude: Optional[Union[float, str]] = None
client_id: int
driver_id: Optional[int] = None
class OrderInUpdate(BaseModel):
status: Optional[OrderStatus] = Field(default=OrderStatus.NEW)
from_address: Optional[str] = Field(default=None, max_length=255)
from_latitude: Optional[Union[float, str]] = None
from_longitude: Optional[Union[float, str]] = None
to_address: Optional[str] = Field(default=None, max_length=255)
to_latitude: Optional[Union[float, str]] = None
to_longitude: Optional[Union[float, str]] = None
client_id: Optional[int] = None
driver_id: Optional[int] = None
class OrderOut(BaseModel):
id: int
created_at: datetime
updated_at: datetime
status: Optional[OrderStatus] = Field(default=OrderStatus.NEW)
client: Optional['ClientOut'] = None
driver: Optional['DriverOut'] = None
from_address: Optional[str] = Field(default=None, max_length=255)
from_latitude: Optional[str] = None
from_longitude: Optional[str] = None
to_address: Optional[str] = Field(default=None, max_length=255)
to_latitude: Optional[str] = None
to_longitude: Optional[str] = None
class PromptIn(BaseModel):
name: str = Field(max_length=255)
content: str
prompt_for: Optional[PromptFor] = Field(default=PromptFor.TAXI)
class PromptOut(PromptIn):
id: int
created_at: datetime
updated_at: datetime
class AddressRequest(BaseModel):
address_from: str
address_to: str
class DistanceRequest(BaseModel):
coord_from: Geocode
coord_to: Geocode
class ValidationError(BaseModel):
loc: List[Union[str, int]]
msg: str
type: str
class HTTPValidationError(BaseModel):
detail: List[ValidationError]
# Resolve forward references
CarOut.update_forward_refs()
ClientOut.update_forward_refs()
DriverOut.update_forward_refs()
OrderOut.update_forward_refs()
# Client
class TaxisClient:
def __init__(self, base_url: str, api_key: Optional[str] = None):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
async def _request(self, method: str, endpoint: str, params: Dict[str, Any] = None, json_data: Dict[str, Any] = None) -> Any:
url = f"{self.base_url}/{endpoint.strip('/')}"
params = params or {}
json_data = json_data or {}
headers={
"Authorization": f"Bearer {self.api_key}" if self.api_key else None
}
async with aiohttp.ClientSession(headers=headers) as session:
async with session.request(method, url, params=params, json=json_data) as resp:
if resp.status == 401:
raise Exception("Unauthorized")
resp.raise_for_status()
if resp.content_type == "application/json":
return await resp.json()
return await resp.text()
async def login(self, username: str, password: str, grant_type: str = "password",
scope: str = "", client_id: Optional[str] = None,
client_secret: Optional[str] = None) -> 'ApiKeyOut':
data = {
"grant_type": grant_type,
"username": username,
"password": password,
"scope": scope,
"client_id": client_id,
"client_secret": client_secret
}
result = await self._request("POST", "/auth/login", json_data=data)
self.api_key = result["api_key"]
self.session.headers.update({"Authorization": f"Bearer {self.api_key}"})
return ApiKeyOut(**result)
# Prompts
async def list_prompts(self, **params) -> List['PromptOut']:
result = await self._request("GET", "/prompts/", params=params)
return [PromptOut(**item) for item in result]
async def create_prompt(self, prompt: 'PromptIn') -> 'PromptOut':
result = await self._request("POST", "/prompts/", json_data=prompt.dict())
return PromptOut(**result)
async def get_prompt(self, prompt_id: int, **params) -> 'PromptOut':
result = await self._request("GET", f"/prompts/{prompt_id}", params=params)
return PromptOut(**result)
async def update_prompt(self, prompt_id: int, prompt: 'PromptIn') -> 'PromptOut':
result = await self._request("PUT", f"/prompts/{prompt_id}", json_data=prompt.dict())
return PromptOut(**result)
async def delete_prompt(self, prompt_id: int) -> None:
await self._request("DELETE", f"/prompts/{prompt_id}")
# Cars
async def list_cars(self, **params) -> List['CarOut']:
result = await self._request("GET", "/cars/", params=params)
return [CarOut(**item) for item in result]
async def create_car(self, car: 'CarIn') -> 'CarOut':
result = await self._request("POST", "/cars/", json_data=car.dict())
return CarOut(**result)
async def get_car(self, car_id: int, **params) -> 'CarOut':
result = await self._request("GET", f"/cars/{car_id}", params=params)
return CarOut(**result)
async def update_car(self, car_id: int, car: 'CarIn') -> 'CarOut':
result = await self._request("PUT", f"/cars/{car_id}", json_data=car.dict())
return CarOut(**result)
async def delete_car(self, car_id: int) -> None:
await self._request("DELETE", f"/cars/{car_id}")
# Drivers
async def list_drivers(self, **params) -> List['DriverOut']:
result = await self._request("GET", "/drivers/", params=params)
return [DriverOut(**item) for item in result]
async def create_driver(self, driver: 'DriverIn') -> 'DriverOut':
result = await self._request("POST", "/drivers/", json_data=driver.dict())
return DriverOut(**result)
async def get_driver(self, driver_id: int, **params) -> 'DriverOut':
result = await self._request("GET", f"/drivers/{driver_id}", params=params)
return DriverOut(**result)
async def get_driver_by_phone(self, phone: str, **params) -> 'DriverOut':
result = await self._request("GET", f"/drivers/phone/{phone}", params=params)
return DriverOut(**result)
async def update_driver(self, driver_id: int, driver: 'DriverIn') -> 'DriverOut':
result = await self._request("PUT", f"/drivers/{driver_id}", json_data=driver.dict())
return DriverOut(**result)
async def delete_driver(self, driver_id: int) -> None:
await self._request("DELETE", f"/drivers/{driver_id}")
async def update_driver_address(self, driver_id: int, address: 'DriverAddressUpdate') -> 'DriverOut':
result = await self._request("PUT", f"/drivers/{driver_id}/address", json_data=address.dict())
return DriverOut(**result)
async def update_driver_status(self, driver_id: int, status: 'DriverStatus') -> 'DriverOut':
result = await self._request("PATCH", f"/drivers/{driver_id}/status", params={"status": status.value})
return DriverOut(**result)
async def get_nearest_driver(self, lat: float, lon: float, exclude_ids: Optional[List[int]] = None, **params) -> 'DriverOut':
params = params.copy()
params.update({"lat": lat, "lon": lon})
if exclude_ids:
params["exclude_ids"] = exclude_ids
result = await self._request("GET", "/drivers/nearest", params=params)
return DriverOut(**result)
async def get_nearby_drivers(self, lat: float, lon: float, limit: int = 5, exclude_ids: Optional[List[int]] = None, **params) -> List['DriverOut']:
params = params.copy()
params.update({"lat": lat, "lon": lon, "limit": limit})
if exclude_ids:
params["exclude_ids"] = exclude_ids
result = await self._request("GET", "/drivers/nearest_list", params=params)
return [DriverOut(**item) for item in result]
# Clients
async def list_clients(self, **params) -> List['ClientOut']:
result = await self._request("GET", "/clients/", params=params)
return [ClientOut(**item) for item in result]
async def create_client(self, client: 'ClientIn') -> 'ClientOut':
result = await self._request("POST", "/clients/", json_data=client.dict())
return ClientOut(**result)
async def get_client(self, client_id: int, **params) -> 'ClientOut':
result = await self._request("GET", f"/clients/{client_id}", params=params)
return ClientOut(**result)
async def get_client_by_phone(self, phone: str, **params) -> 'ClientOut':
result = await self._request("GET", f"/clients/phone/{phone}", params=params)
return ClientOut(**result)
async def update_client(self, client_id: int, client: 'ClientIn') -> 'ClientOut':
result = await self._request("PUT", f"/clients/{client_id}", json_data=client.dict())
return ClientOut(**result)
async def delete_client(self, client_id: int) -> None:
await self._request("DELETE", f"/clients/{client_id}")
# Orders
async def list_orders(self, **params) -> List['OrderOut']:
result = await self._request("GET", "/orders/", params=params)
return [OrderOut(**item) for item in result]
async def create_order(self, order: 'OrderIn') -> 'OrderOut':
result = await self._request("POST", "/orders/", json_data=order.dict())
return OrderOut(**result)
async def get_order(self, order_id: int, **params) -> 'OrderOut':
result = await self._request("GET", f"/orders/{order_id}", params=params)
return OrderOut(**result)
async def update_order(self, order_id: int, order: 'OrderInUpdate') -> 'OrderOut':
result = await self._request("PUT", f"/orders/{order_id}", json_data=order.dict())
return OrderOut(**result)
async def delete_order(self, order_id: int) -> None:
await self._request("DELETE", f"/orders/{order_id}")
async def update_order_status(self, order_id: int, status: 'OrderStatus') -> 'OrderOut':
result = await self._request("PATCH", f"/orders/{order_id}/status", params={"status": status.value})
return OrderOut(**result)
async def update_order_driver(self, order_id: int, driver_id: int) -> 'OrderOut':
result = await self._request("PATCH", f"/orders/{order_id}/driver", params={"driver_id": driver_id})
return OrderOut(**result)
async def delete_order_driver(self, order_id: int) -> 'OrderOut':
result = await self._request("DELETE", f"/orders/{order_id}/driver")
return OrderOut(**result)
# Settings
async def get_global_settings(self, **params) -> 'GlobalSettingsOut':
result = await self._request("GET", "/settings/global", params=params)
return GlobalSettingsOut(**result)
async def update_global_settings(self, settings: 'GlobalSettingsUpdate') -> 'GlobalSettingsOut':
result = await self._request("PUT", "/settings/global", json_data=settings.dict())
return GlobalSettingsOut(**result)
async def get_nominatium_settings(self, nominatium_id: int, **params) -> 'NominatiumSettingsOut':
result = await self._request("GET", f"/settings/nominatium/{nominatium_id}", params=params)
return NominatiumSettingsOut(**result)
async def get_openstreetmap_settings(self, openstreetmap_id: int, **params) -> 'OpenStreetMapSettingsOut':
result = await self._request("GET", f"/settings/openstreetmap/{openstreetmap_id}", params=params)
return OpenStreetMapSettingsOut(**result)
# Union
async def geocode_address(self, address: str, **params) -> 'Geocode':
params = params.copy()
params.update({"address": address})
result = await self._request("GET", "/union/geocode", params=params)
return Geocode(**result)
async def get_distance(self, request: 'DistanceRequest') -> float:
result = await self._request("POST", "/union/distance", json_data=request.dict())
return float(result)
async def get_distance_by_address(self, request: 'AddressRequest') -> float:
result = await self._request("POST", "/union/distance_by_address", json_data=request.dict())
return float(result)