#What's the purpose of lib/service.py in the examples?

1 messages · Page 1 of 1 (latest)

spiral vector
#

Looking at litestar-pg-redis-docker and litestar-fullstack examples, I try to figure out what's the purpose of Service? Seems like Service delegates everything to the underlying Repository, not adding anything?
In my app I'll need to have a batch script with mix of python / custom sqlalchemy queries logic that will be run from cli or as a query job. Is Service a good place for this script?

steep spade
#

yhea we need to refactor this away

#

so basically we had the logic in service and than we introduced the repositories into the core

#

so this is now pretty redundant

#

the entire example is slated for a refactor

#

@lapis ravine - are you looking into this?

#

if not, we are open to PRs basically

lapis ravine
#

Service extends Repository . In this architecture

  • Repository is to subclass and add new functionaility to reposiotory.
  • Where services are design to override
    For example if you want something to do before insert , after insert / before update/ after update . Service is a good place. .
    It would look a bit complicated at first but very useful.

Here is my plugin architecutre in my spritlog

#
class Service(SQLAlchemyAsyncRepositoryService[Backlog]):
    repository_type = Repository
    plugins: set[BacklogPlugin] = set()

    def __init__(self, **repo_kwargs: Any) -> None:
        self.repository: Repository = self.repository_type(**repo_kwargs)
        self.model_type = self.repository.model_type

        super().__init__(**repo_kwargs)

    async def to_model(self, data: Backlog | dict[str, Any], operation: str | None = None) -> Backlog:
        if isinstance(data, Backlog):
            slug = await self.repository.get_available_backlog_slug(backlog=data)
            if isinstance(slug, str):
                data.slug = slug
            data.due_date = await self.repository._get_due_date(data.beg_date, data.est_days)

        return await super().to_model(data, operation)

    async def create(self, data: Backlog | dict[str, Any]) -> Backlog:
        # Call the before_create hook for each registered plugin
        for plugin in self.plugins:
            data = await plugin.before_create(data=data)

        obj = await super().create(data)
        # Call the after_create hook for each
        for plugin in self.plugins:
            after = await plugin.after_create(data=obj)
            obj = await super().update(obj.id, after)

        return obj

    async def update(self, item_id: Any, data: Backlog | dict[str, Any]) -> Backlog:
        # Call the before_update hook for each registered plugin
        for plugin in self.plugins:
            data = await plugin.before_update(item_id=item_id, data=data)

        obj: Backlog = await super().update(item_id, data)

        # Call the after_update hook for each registered plugin
        for plugin in self.plugins:
            await plugin.after_update(data=obj)

        return obj
#

here i overwritten to_model which is called before update or insert

#

it creates slug , and calculates due_date

lapis ravine
#

then those services are injected into dependancies like this



logger = log.get_logger()

if TYPE_CHECKING:
    from collections.abc import AsyncGenerator

    from sqlalchemy.ext.asyncio import AsyncSession


async def provides_service(db_session: AsyncSession) -> AsyncGenerator[Service, None]:
    plugins = []
    for _, name, _ in pkgutil.iter_modules([app.plugins.__path__[0]]):
        module = __import__(f"{app.plugins.__name__}.{name}", fromlist=["*"])
        for obj_name in dir(module):
            obj = getattr(module, obj_name)
            if isinstance(obj, type) and issubclass(obj, BacklogPlugin) and obj is not BacklogPlugin:
                plugins.append(obj())
    async with Service.new(
        session=db_session,
        statement=select(Backlog).order_by(Backlog.due_date).options(joinedload(Backlog.project)),
    ) as service:
        service.plugins = set(plugins)
        try:
            yield service
        finally:
            ...

#

that way we can use services in controllers , instead of repositories. : making CRUD easily.

#
    @get()
    async def filter(self, service: "Service", filters: list["FilterTypes"] = validation_skip) -> Sequence[Model]:
        return await service.list(*filters)

    @post()
    async def create(self, data: Model, current_user: User, service: "Service") -> Model:
        if not data.owner_id:
            data.owner_id = current_user.id
        if not data.assignee_id:
            data.assignee_id = current_user.id
        return await service.create(data)

lapis ravine
#

oh but its lib need to be refactored and cleaned up

#

i have in mind to build django-like generators ,
also copier based starter templates.

spiral vector
west steppe
#

That's the main benefit of this pattern, imo

#

is that it helps absstract the logic from the controller so that you can re-use it in other places if you need to

lapis ravine
sterile peak
#

The way I think of it is that the service layer is where the business logic lives. This means that the boilerplate around using the framework and database remains fairly static, and mostly doesn't change from one project to the next.