Skip to main content

Abstract CRUD repository components

Project description

AbstractRepo - Python Repository Pattern Implementation

PyPI package version number Coverage Status Actions Status License

The Abstract Repository library provides a flexible abstraction layer between your application code and data storage systems. It implements the repository pattern with support for CRUD operations, filtering using specifications, ordering, pagination, and exception handling. This design allows you to easily switch between different persistence mechanisms while maintaining clean separation of concerns in your application architecture.

Core Concepts

  • CRUD Operations: Standard Create, Read, Update, Delete functionality
  • Specifications Pattern: Flexible filtering mechanism based on business rules
  • Ordering Options: Customizable sorting with control over NULL values placement
  • Pagination Support: Limit and offset-based page navigation
  • Strong Typing: Uses Python's typing module for robustness
  • Extensibility: Designed for easy extension to various database technologies
  • In-Memory Implementation: Built-in list-based repository for testing/development

Installation

pip install abstractrepo

Core Components

1. Repository Interface

import abc
from pydantic import BaseModel
from abstractrepo.repo import CrudRepositoryInterface


class User(BaseModel):
    id: int
    username: str
    password: str
    display_name: str

class UserCreateForm(BaseModel):
    username: str
    password: str
    display_name: str

class UserUpdateForm(BaseModel):
    display_name: str


class UserRepositoryInterface(CrudRepositoryInterface[User, int, UserCreateForm, UserUpdateForm], abc.ABC):  # TModel, TIdValue, TCreateSchema, TUpdateSchema
    pass


class UserRepository(UserRepositoryInterface):
    # Implement abstract methods
    ...

Key Methods:

  • get_collection(): Retrieve items with filtering/sorting/pagination
  • count(): Get filtered item count
  • get_item(): Get single item by ID
  • exists(): Check if item exists
  • create(): Add new item
  • update(): Modify existing item
  • delete(): Remove item

2. List-Based Implementation

import abc
from typing import Optional, List
from abstractrepo.repo import CrudRepositoryInterface, ListBasedCrudRepository
from abstractrepo.specification import SpecificationInterface, AttributeSpecification, Operator
from abstractrepo.exceptions import ItemNotFoundException, UniqueViolationException


class UserRepositoryInterface(CrudRepositoryInterface[User, int, UserCreateForm, UserUpdateForm], abc.ABC):
    pass


class ListBasedUserRepository(
    ListBasedCrudRepository[User, int, UserCreateForm, UserUpdateForm],
    UserRepositoryInterface,
):
    _next_id: int

    def __init__(self, items: Optional[List[User]] = None):
        super().__init__(items)
        self._next_id = 0

    def get_by_username(self, username: str) -> User:
        items = self.get_collection(AttributeSpecification('username', username))
        if len(items) == 0:
            raise ItemNotFoundException(User)

        return items[0]

    @property
    def model_class(self) -> Type[User]:
        return User

    def _create_model(self, form: UserCreateForm, new_id: int) -> User:
        if self._username_exists(form.username):
            raise UniqueViolationException(User, 'create', form)

        return User(
            id=new_id,
            username=form.username,
            password=form.password,
            display_name=form.display_name,
        )

    def _update_model(self, model: User, form: UserUpdateForm) -> User:
        model.display_name = form.display_name
        return model

    def _username_exists(self, username: str) -> bool:
        return self.count(AttributeSpecification('username', username)) > 0

    def _generate_id(self) -> int:
        self._next_id += 1
        return self._next_id

    def _get_id_filter_specification(self, item_id: int) -> SpecificationInterface[User, bool]:
        return AttributeSpecification('id', item_id, Operator.E)

3. Specifications

Filtering with Specifications:

from abstractrepo.specification import AttributeSpecification, AndSpecification, OrSpecification, Operator

# Single attribute
active_users = AttributeSpecification("is_active", True)

# Complex filters
premium_filter = AndSpecification(
    AttributeSpecification("plan", "premium"),
    OrSpecification(
        AttributeSpecification("age", 30, Operator.GTE),
        AttributeSpecification("join_date", "2023-01-01", Operator.GT)
    )
)

Supported Operators:

from abstractrepo.specification import Operator

Operator.E          # Equal
Operator.NE         # Not Equal
Operator.GT         # Greater Than
Operator.LT         # Less Than
Operator.GTE        # Greater Than or Equal
Operator.LTE        # Less Than or Equal
Operator.LIKE       # Case-Sensitive Pattern Match
Operator.ILIKE      # Case-Insensitive Pattern Match
Operator.IN         # In List
Operator.NOT_IN     # Not In List

4. Ordering

from abstractrepo.order import OrderOptionsBuilder, OrderOptions, OrderOption, OrderDirection, NonesOrder

# Single field ordering
ordering = OrderOptions(
    OrderOption("name", OrderDirection.ASC, NonesOrder.LAST)
)

# Multi-field ordering
ordering = OrderOptionsBuilder() \
    .add("priority", OrderDirection.DESC) \
    .add("created_at", OrderDirection.ASC, NonesOrder.LAST) \
    .build()

5. Pagination

from abstractrepo.paging import PagingOptions, PageResolver

# Manual paging
paging = PagingOptions(limit=10, offset=20)

# Page-based resolver
resolver = PageResolver(page_size=25)
page3 = resolver.get_page(3)

6. Exceptions

from abstractrepo.exceptions import (
    ItemNotFoundException,
    UniqueViolationException,
    RelationViolationException,
)

try:
    repo.get_item(999)
except ItemNotFoundException as e:
    print(f"Error: {e}")

Complete Example

from abstractrepo.repo import ListBasedCrudRepository
from abstractrepo.specification import AttributeSpecification, Operator
from abstractrepo.order import OrderOptions, OrderOption, OrderDirection
from abstractrepo.paging import PagingOptions

class User:
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email

      
class UserRepository(ListBasedCrudRepository[User, int, dict, dict]):
    # Implementation of abstract methods
    ...


# Initialize repository
repo = UserRepository()

# Create users
repo.create({"name": "Alice", "email": "alice@example.com"})
repo.create({"name": "Bob", "email": "bob@example.com"})

# Query with specifications
b_users = repo.get_collection(
    filter_spec=AttributeSpecification("name", "B%", Operator.LIKE),
    order_options=OrderOptions(OrderOption("email", OrderDirection.ASC)),
    paging_options=PagingOptions(limit=5)
)

# Get count
active_count = repo.count(filter_spec=AttributeSpecification("is_active", True))

API Reference

Repository Methods

Method Parameters Returns Description
get_collection filter_spec, order_options, paging_options List[TModel] Get filtered/sorted/paged collection
count filter_spec int Count filtered items
get_item item_id TModel Get single item by ID
exists item_id bool Check item existence
create form TModel Create new item
update item_id, form TModel Update existing item
delete item_id TModel Delete item

Specification Types

Class Description
AttributeSpecification Filter by model attribute
AndSpecification Logical AND combination
OrSpecification Logical OR combination
NotSpecification Logical negation

Ordering Options

OrderOption(
    attribute: str,
    direction: OrderDirection = OrderDirection.ASC,
    nones: NonesOrder = NonesOrder.FIRST,
)

Pagination Options

PagingOptions(
    limit: Optional[int] = None,
    offset: Optional[int] = None,
)

Best Practices

  1. Type Safety: Leverage Python's typing system for robust implementations
  2. Specification Composition: Combine simple specs for complex queries
  3. Null Handling: Explicitly define null ordering behavior
  4. Pagination: Use PageResolver for consistent page-based navigation
  5. Error Handling: Catch repository-specific exceptions

Dependencies

  • Python 3.7+
  • No external dependencies

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

abstractrepo-1.2.0.tar.gz (12.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

abstractrepo-1.2.0-py3-none-any.whl (10.2 kB view details)

Uploaded Python 3

File details

Details for the file abstractrepo-1.2.0.tar.gz.

File metadata

  • Download URL: abstractrepo-1.2.0.tar.gz
  • Upload date:
  • Size: 12.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for abstractrepo-1.2.0.tar.gz
Algorithm Hash digest
SHA256 f80d21dae70ecead6e39d7719aa52112f22c73c142ccc191951dedd00cda5752
MD5 f9f9ea0441a810ca9863febfba19bbb5
BLAKE2b-256 89ff73d3ea074ba290051f73ad461944c79fe66ed04ca793957b1ed1673ecbf5

See more details on using hashes here.

File details

Details for the file abstractrepo-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: abstractrepo-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for abstractrepo-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db20a71b65d012f5d2ceb5ef72c951fdf90c7ffd86a18a35bee49a8f406fc103
MD5 8593261fe8722b515e8c6af20b51da7f
BLAKE2b-256 977cb8a4bb0205fd92df20677b104c1cffcf54ef997e9592eae09d548d137f9f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page