Skip to content

Conversation

@Rendez
Copy link

@Rendez Rendez commented Jan 15, 2026

An idiomatic way to access the response status code, to at least be able to handle the wrapped (ApiError) request errors by error code.

At the moment, I'm getting around this in the most elegant way possible:

import logging

import requests
from thecompaniesapi import ApiError, Client, generated as models

logger = logging.getLogger(__name__)


class TheCompanyApiException(Exception):
    def __init__(self, message: str, status_code: int | None = None) -> None:
        super().__init__(message)
        self.status_code = status_code

    @classmethod
    def from_api_error(cls, e: ApiError, message: str) -> "TheCompanyApiException":
        status_code = None
        cause = e.__cause__
        if isinstance(cause, requests.HTTPError) and cause.response is not None:
            status_code = cause.response.status_code
        elif isinstance(cause, requests.exceptions.RetryError):
            # Retry exhausted - try to extract status from message
            # e.g., "too many 429 error responses"
            import re

            if match := re.search(r"(\d{3})", str(cause)):
                status_code = int(match.group(1))
        exc = cls(message, status_code)
        exc.__cause__ = e  # preserve the chain for debugging
        return exc


class TheCompanyApiService:
    def __init__(self, *, api_token: str, simple_mode: bool = True) -> None:
        """
        Service for interacting with The Companies API.

        :param api_token str: API token for authentication.
        :param simple_mode bool: Whether to use simplified responses (free of cost). Defaults to `True`.
        :raises: ValueError if `api_token` is not provided.
        """
        self._client = Client(api_token=api_token)
        self._simple_mode = simple_mode

    def fetch_company_by_email(self, email: str) -> models.CompanyV2:
        """
        Fetch company information by email address.
        The model's `CompanyV2.meta.mysqlId` and `CompanyV2.domain.domain` are identifiers,
        `domain` is the PK field in third-party's DB.

        :param email str: Email address to look up.
        :raises: TheCompanyApiException on failure.
        """
        try:
            body = self._client.fetchCompanyByEmail(email=email, simplified=self._simple_mode)
        except ApiError as e:
            exc = TheCompanyApiException.from_api_error(e, f"Failed to fetch company by email: {email}")
            logger.exception("TheCompaniesAPI error", exc_info=exc)
            raise exc

        return models.CompanyV2(**body)

    def search_companies_by_name(self, name: str) -> list[models.CompanyV2]:
        """
        Search for companies by name.

        :param name str: Company name to search for.
        :raises: TheCompanyApiException on failure.
        """
        try:
            body = self._client.searchCompaniesByName(
                name=name, exactWordsMatch=False, size=5, simplified=self._simple_mode
            )
        except ApiError as e:
            exc = TheCompanyApiException.from_api_error(e, f"Failed to search companies by name: {name}")
            logger.exception("TheCompaniesAPI error", exc_info=exc)
            raise exc

        return [models.CompanyV2(**company) for company in body["companies"]]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant