Skip to content

Error handling

ineepy provides a structured exception hierarchy for error handling.

Exception hierarchy

IneepyError (base exception)
├── HTTPError
│   ├── ValidationError (400/422)
│   ├── UnauthorizedError (401)
│   ├── ForbiddenError (403)
│   ├── NotFoundError (404)
│   ├── ConflictError (409)
│   └── PreconditionFailedError (412)
├── JobFailedError
└── TimeoutError

Common exceptions

Exception HTTP Status When Recovery
ValidationError 400/422 Invalid input Check parameters
UnauthorizedError 401 Invalid credentials Re-authenticate
ForbiddenError 403 Insufficient scope Use admin scope if needed
NotFoundError 404 Resource not found Check ID/path
ConflictError 409 Resource conflict Check existing state
PreconditionFailedError 412 ETag mismatch Fetch fresh and retry
JobFailedError Async job failed Check job.error
TimeoutError Job didn't complete in time Increase timeout or resubmit

Basic error handling

try:
    bad_token = get_token(
        email='user@ineep.org.br',
        password='wrongpassword',
        save_token=False,
    )
except UnauthorizedError:
    print('Invalid credentials')
except IneepyError as e:
    print(f'Error: {e}')

Handling specific errors

        try:
            client.namespaces.delete(namespace_id='raw.errtest')
        except ConflictError:
            print('Namespace is not empty. Delete tables first.')

ETag mismatch (optimistic locking)

When updating a resource, you must provide the current ETag. If the resource was modified by another client, you'll get PreconditionFailedError:

        try:
            user, etag = client.users.get('me')

            # Simulate a stale ETag by using a wrong value
            stale_etag = '"00000000-0000-0000-0000-000000000000"'
            updated, _ = client.users.update(
                'me',
                etag=stale_etag,
                username='newusername',
            )
        except PreconditionFailedError:
            print('ETag mismatch: another client modified the resource')
            # Fetch fresh and retry
            user, fresh_etag = client.users.get('me')
            updated, _ = client.users.update(
                'me',
                etag=fresh_etag,
                username=user.username,
            )
            print('Retry successful')

Async job errors

Some operations submit async jobs that may fail:

        try:
            job = client.data.upload(
                table_id='raw.errtest.events',
                data=b'[{"id": 1}, {"id": 2}]',
                format='json',
            )
            print(f'Upload succeeded: {job.status}')
        except JobFailedError as e:
            print(f'Job {e.job_id} failed: {e.error}')
        except TimeoutError as e:
            print(
                f'Job {e.job_id} timed out, '
                f'increase the timeout parameter'
            )

Error details

All HTTP errors include status code and detail:

        try:
            # Request a user that doesn't exist
            user, etag = client.users.get(999999)
        except HTTPError as e:
            print(f'Status: {e.status_code}')
            print(f'Detail: {e.detail}')

Retry patterns

Simple retry with exponential backoff

        def retry_with_backoff(func, max_attempts=3):
            for attempt in range(max_attempts):
                try:
                    return func()
                except IneepyError as e:
                    if attempt == max_attempts - 1:
                        raise
                    wait_time = 2 ** attempt
                    print(
                        f'Attempt {attempt + 1} failed, '
                        f'retrying in {wait_time}s'
                    )
                    time.sleep(wait_time)

        def my_operation():
            with Ineepy(token=token) as c:
                return c.users.get('me')

        try:
            result = retry_with_backoff(my_operation)
        except IneepyError:
            print('All retries failed')

Retry with exponential backoff (async)

async def retry_with_backoff_async(func, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            return await func()
        except IneepyError:
            if attempt == max_attempts - 1:
                raise
            wait_time = 2 ** attempt
            print(
                f'Attempt {attempt + 1} failed, '
                f'retrying in {wait_time}s'
            )
            await asyncio.sleep(wait_time)


async def my_async_operation():
    async with AsyncIneepy(token=token) as client:
        return await client.users.get('me')


async def main():
    try:
        result = await retry_with_backoff_async(my_async_operation)
    except IneepyError:
        print('All retries failed')


asyncio.run(main())