""" Main client facade for Moy Nalog API. Based on PHP library's ApiClient class. """ import json from typing import Any from ._http import AsyncHTTPClient from .auth import AuthProviderImpl from .income import IncomeAPI from .payment_type import PaymentTypeAPI from .receipt import ReceiptAPI from .tax import TaxAPI from .user import UserAPI class Client: """ Main async client for Moy Nalog API. Provides factory methods for API modules and authentication methods. Maps to PHP ApiClient functionality with async support. Example: >>> client = Client() >>> token = await client.create_new_access_token("inn", "password") >>> await client.authenticate(token) >>> income_api = client.income() >>> result = await income_api.create("Service", 100, 1) """ def __init__( self, base_url: str = "https://lknpd.nalog.ru/api", storage_path: str | None = None, device_id: str | None = None, timeout: float = 10.0, ): """ Initialize Moy Nalog API client. Args: base_url: API base URL (default: https://lknpd.nalog.ru/api) storage_path: Optional file path for token storage device_id: Optional device ID (auto-generated if not provided) timeout: HTTP request timeout in seconds """ self.base_url = base_url self.timeout = timeout # Initialize auth provider self.auth_provider = AuthProviderImpl( base_url=base_url, storage_path=storage_path, device_id=device_id, ) # Initialize HTTP client with auth middleware self.http_client = AsyncHTTPClient( base_url=f"{base_url}/v1", auth_provider=self.auth_provider, default_headers={ "Content-Type": "application/json", "Accept": "application/json, text/plain, */*", "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", "Referrer": "https://lknpd.nalog.ru/auth/login", }, timeout=timeout, ) # User profile data (for receipt operations) self._user_profile: dict[str, Any] | None = None async def create_new_access_token(self, username: str, password: str) -> str: """ Create new access token using INN and password. Maps to PHP ApiClient::createNewAccessToken(). Args: username: INN (tax identification number) password: Password Returns: JSON string with access token data Raises: UnauthorizedException: For invalid credentials DomainException: For other API errors """ return await self.auth_provider.create_new_access_token(username, password) async def create_phone_challenge(self, phone: str) -> dict[str, Any]: """ Start phone-based authentication challenge. Maps to PHP ApiClient::createPhoneChallenge(). Args: phone: Phone number (e.g., "79000000000") Returns: Dictionary with challengeToken, expireDate, expireIn Raises: DomainException: For API errors """ return await self.auth_provider.create_phone_challenge(phone) async def create_new_access_token_by_phone( self, phone: str, challenge_token: str, verification_code: str ) -> str: """ Complete phone-based authentication with SMS code. Maps to PHP ApiClient::createNewAccessTokenByPhone(). Args: phone: Phone number challenge_token: Token from create_phone_challenge() verification_code: SMS verification code Returns: JSON string with access token data Raises: UnauthorizedException: For invalid verification DomainException: For other API errors """ return await self.auth_provider.create_new_access_token_by_phone( phone, challenge_token, verification_code ) async def authenticate(self, access_token: str) -> None: """ Authenticate client with access token. Maps to PHP ApiClient::authenticate(). Args: access_token: JSON string with token data Raises: ValueError: For invalid token JSON """ await self.auth_provider.set_token(access_token) # Parse token to extract user profile (like PHP version) try: token_data = json.loads(access_token) if "profile" in token_data: self._user_profile = token_data["profile"] except json.JSONDecodeError: # If token parsing fails, profile will remain None pass async def get_access_token(self) -> str | None: """ Get current access token (may be refreshed). Maps to PHP ApiClient::getAccessToken(). Returns: Current access token JSON string or None """ token_data = await self.auth_provider.get_token() if token_data: return json.dumps(token_data) return None def income(self) -> IncomeAPI: """ Get Income API instance. Maps to PHP ApiClient::income(). Returns: IncomeAPI instance for creating/cancelling receipts """ return IncomeAPI(self.http_client) def receipt(self) -> ReceiptAPI: """ Get Receipt API instance. Maps to PHP ApiClient::receipt(). Returns: ReceiptAPI instance for accessing receipt data Raises: ValueError: If user is not authenticated (no profile data) """ if not self._user_profile or "inn" not in self._user_profile: raise ValueError("User profile not available. Please authenticate first.") return ReceiptAPI( http_client=self.http_client, base_endpoint=self.base_url, user_inn=self._user_profile["inn"], ) def payment_type(self) -> PaymentTypeAPI: """ Get PaymentType API instance. Maps to PHP ApiClient::paymentType(). Returns: PaymentTypeAPI instance for managing payment methods """ return PaymentTypeAPI(self.http_client) def tax(self) -> TaxAPI: """ Get Tax API instance. Maps to PHP ApiClient::tax(). Returns: TaxAPI instance for tax information and history """ return TaxAPI(self.http_client) def user(self) -> UserAPI: """ Get User API instance. Maps to PHP ApiClient::user(). Returns: UserAPI instance for user information """ return UserAPI(self.http_client)