BOB Docs
APIDesign

Tools

It's not bad to be lazy if it brings significant benefits to your work. Therefore, we’ve created a set of tools designed to not only accelerate your workflow but also help you organize it in a clear and efficient manner.

Good practice is to create a single location for reusable components throughout the application. We recommend creating a library in the root directory, enabling you to turn it into a package in the future.

Commands

Django Commons is one of my favorite features of the framework. As such, we’ve tailored commands for easier use.

The entire solution is written asynchronously, so the async_command will be of particular interest. It allows you to run any part of the system as a separate process. For example, you can create a dedicated container to execute background tasks. A great example of its implementation is buildapp in the _initializer application.

Models

Models include Django ORM structures used throughout the application. Every model in the app should inherit from the Base class.

Other models have been created to simplify work:

  1. CountryCode - Adds a country_code field for ISO 3166-1 alpha-2 country codes.
  2. ExternalId - Provides a unique external_id field for models, avoiding internal ID exposure.
  3. GUID - Replaces the default integer primary key with a UUID field.
  4. LanguageCode - Adds a language_code field for ISO 639-1 language codes.
  5. Ordered - Provides an order field for custom model ordering.
  6. OwnedByAnonymous - Links a record to an anonymous user.
  7. OwnedByOrganization - Links a model instance to a specific organization.
  8. OwnedByUser - Links a model instance to a specific user.

Permissions

To manage permissions, we’ve introduced helpers:

BoBPermission - An abstract base class for implementing custom permission logic in views, designed to be used with a decorator. For example:

class IsInGroup(BoBPermission):
    def __init__(self, group_name: str):
        self._group_name = str(group_name)
 
    async def validate(self, custom_user: CustomUser) -> bool | IsNotMemberOfGroup:
        if not await permission_service.check_user_belongs_to_group(custom_user.id, self._group_name):
            raise IsNotMemberOfGroup(
                f"User {custom_user.id} is not a member of {self._group_name}.",
                f"This functionality is only for {self._group_name} group members."
            )
        return True

Permissions are validated using the validate_permissions decorator:

@invitation_router.post('', response={201: InvitationCreatedDTO, 400: ApiExceptionOut},
                        tags=["organization"], summary="Invites user to organization by e-mail.")
@validate_permissions([IsInGroup(SystemGroupNames.MEMBER), IsInGroup(GroupNameManageableByUsers.USERS_MANAGER)])
async def invite_user(request, body: EmailDTO):
    """
    Allows inviting a user to an organization by e-mail.
    """
    ...

Repository

We use the repository pattern to separate data management from the business layer. Each repository manages a single model type, meaning every model type requiring operations will have its own repository.

Overview

AsyncBaseRepository is a generic base class that simplifies asynchronous repository patterns in Django. It supports:

  • Async querying of models
  • Eager loading (select_related, prefetch_related)
  • Pagination
  • CRUD operations

Key Features:

  • Generic Design: Works with any Django model via the model attribute.
  • Asynchronous ORM: Non-blocking database operations.
  • Optimized Queries: Built-in eager loading for performance.
  • CRUD Operations: Async methods for saving, updating, deleting, and retrieving data.

Example Usage:

from myapp.models import MyModel
 
class MyModelRepository(AsyncBaseRepository[MyModel]):
    model = MyModel
 
repo = MyModelRepository()
 
# Fetch a single instance
instance = await repo.get(pk=1)
 
# Fetch multiple instances with filters
instances, pagination = await repo.find(filters={"is_active": True}, pagination=Pagination(page=1, page_size=10))
 
# Save a new instance
await repo.save(instance)
 
# Update records
await repo.update(values_to_update={"is_active": False}, filters={"is_active": True})

Tests

The solution includes tools to speed up testing.

AsyncClient

AsyncClientBoB is a custom asynchronous client wrapper for testing API endpoints. It manages headers, cookies, and response validation.

Key Features:

  • User Data: Manages user-related attributes like email, UID, and organization info.
  • HTTP Methods: Supports async GET, POST, PATCH, and DELETE requests with response validation.

Fixtures

Fixtures support efficient testing of user flows and components (e2e and unit tests). Features include:

  • Database management
  • Reusable user authentication
  • Mock RabbitMQ publishers

ResponseChecker

Facilitates response validation during e2e testing, ensuring robust and detailed checks.

Exceptions

The ExceptionRegistry centralizes custom exceptions, ensuring consistency and maintainability. Example exceptions include:

  • PERMISSION_DENIED - Unauthorized access.
  • ALREADY_EXISTS - Duplicate resource creation.
  • TOKEN_EXPIRED - Expired token errors.

Logger

The CustomJsonFormatter enhances logging with JSON formatting and fields for compatibility with Uvicorn.

Utils

A collection of utilities for generating identifiers, secure passwords, and handling cookies. Examples:

  • generate_external_id() - Creates unique identifiers.
  • set_server_side_cookie() - Sets secure cookies in responses.
  • to_bool() - Converts various input types to boolean values.

On this page