Skip to content

Managers

The managers are a great tool that Edgy offers. Heavily inspired by Django, the managers allow you to build unique tailored queries ready to be used by your models. Unlike in Django Managers are instance and class aware. For every inheritance they are shallow copied and if used on an instance you have also an shallow copy you can customize.

Note: shallow copy means, deeply nested attributes or mutable attributes must be copied and not modified. As an alternative __copy__ can be overwritten to do this for you.

Edgy by default uses the the manager called query for direct queries which it makes it simple to understand. For related queries query_related is used which is by default a RedirectManager which redirects to query.

Let us see an example.

import edgy
from edgy import Database, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class User(edgy.Model):
    name: str = edgy.CharField(max_length=255)
    email: str = edgy.EmailField(max_length=70)
    is_active: bool = edgy.BooleanField(default=True)

    class Meta:
        registry = models
        unique_together = [("name", "email")]


# Using ipython that supports await
# Don't use this in production! Use Alembic or any tool to manage
# The migrations for you
await models.create_all()  # noqa

await User.query.create(name="Edgy", email="foo@bar.com")  # noqa

user = await User.query.get(id=1)  # noqa
# User(id=1)

When querying the User table, the query (manager) is the default and should be always presented when doing it so.

Inheritance

Managers can set the inherit flag to False, to prevent being used by subclasses. Things work like for fields. An usage would be injected managers though we have non yet.

Custom manager

It is also possible to have your own custom managers and to do it so, you should inherit the Manager class and override the get_queryset(). For further customization it is possible to use the BaseManager class which is more extensible.

For those familiar with Django managers, the concept is exactly the same. 😀

The managers must be type annotated ClassVar or an ImproperlyConfigured exception will be raised.

from typing import ClassVar

import edgy
from edgy import Manager, QuerySet


class InactiveManager(Manager):
    """
    Custom manager that will return only active users
    """

    def get_queryset(self) -> "QuerySet":
        queryset = super().get_queryset().filter(is_active=False)
        return queryset


class User(edgy.Model):
    # Add the new manager
    inactives: ClassVar[Manager] = InactiveManager()

Let us now create new manager and use it with our previous example.

from typing import ClassVar

import edgy
from edgy import Database, Manager, QuerySet, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class InactiveManager(Manager):
    """
    Custom manager that will return only active users
    """

    def get_queryset(self) -> "QuerySet":
        queryset = super().get_queryset().filter(is_active=False)
        return queryset


class User(edgy.Model):
    name: str = edgy.CharField(max_length=255)
    email: str = edgy.EmailField(max_length=70)
    is_active: bool = edgy.BooleanField(default=True)

    # Add the new manager
    inactives: ClassVar[Manager] = InactiveManager()

    class Meta:
        registry = models
        unique_together = [("name", "email")]


# Using ipython that supports await
# Don't use this in production! Use Alembic or any tool to manage
# The migrations for you
await models.create_all()  # noqa

# Create an inactive user
await User.query.create(name="Edgy", email="foo@bar.com", is_active=False)  # noqa

# You can also create a user using the new manager
await User.inactives.create(name="Another Edgy", email="bar@foo.com", is_active=False)  # noqa

# Querying using the new manager
user = await User.inactives.get(email="foo@bar.com")  # noqa
# User(id=1)

user = await User.inactives.get(email="bar@foo.com")  # noqa
# User(id=2)

# Create a user using the default manager
await User.query.create(name="Edgy", email="user@edgy.com")  # noqa

# Querying all inactives only
users = await User.inactives.all()  # noqa
# [User(id=1), User(id=2)]

# Querying them all
user = await User.query.all()  # noqa
# [User(id=1), User(id=2), User(id=3)]

These managers can be as complex as you like with as many filters as you desire. What you need is simply override the get_queryset() and add it to your models.

Override the default manager

Overriding the default manager is also possible by creating the custom manager and overriding the query manager. By default the queryis also used for related queries. This can be customized via setting an explicit query_related manager.

from typing import ClassVar

import edgy
from edgy import Database, Manager, QuerySet, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class InactiveManager(Manager):
    """
    Custom manager that will return only active users
    """

    def get_queryset(self) -> "QuerySet":
        queryset = super().get_queryset().filter(is_active=False)
        return queryset


class User(edgy.Model):
    name: str = edgy.CharField(max_length=255)
    email: str = edgy.EmailField(max_length=70)
    is_active: bool = edgy.BooleanField(default=True)

    # Add the new manager
    query: ClassVar[Manager] = InactiveManager()

    class Meta:
        registry = models
        unique_together = [("name", "email")]


# Using ipython that supports await
# Don't use this in production! Use Alembic or any tool to manage
# The migrations for you
await models.create_all()  # noqa

# Create an inactive user
await User.query.create(name="Edgy", email="foo@bar.com", is_active=False)  # noqa

# You can also create a user using the new manager
await User.query.create(name="Another Edgy", email="bar@foo.com", is_active=False)  # noqa

# Create a user using the default manager
await User.query.create(name="Edgy", email="user@edgy.com")  # noqa

# Querying them all
user = await User.query.all()  # noqa
# [User(id=1), User(id=2)]

Now with only overwriting the related manager:

from typing import ClassVar

import edgy
from edgy import Database, Manager, QuerySet, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class InactiveManager(Manager):
    """
    Custom manager that will return only active users
    """

    def get_queryset(self) -> "QuerySet":
        queryset = super().get_queryset().filter(is_active=False)
        return queryset


class Team(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class User(edgy.Model):
    name: str = edgy.CharField(max_length=255)
    email: str = edgy.EmailField(max_length=70)
    team = edgy.ForeignKey(Team, null=True, related_name="members")
    is_active: bool = edgy.BooleanField(default=True)

    # Add the new manager only for related queries
    query_related: ClassVar[Manager] = InactiveManager()

    class Meta:
        registry = models
        unique_together = [("name", "email")]


# Using ipython that supports await
# Don't use this in production! Use Alembic or any tool to manage
# The migrations for you
await models.create_all()  # noqa

# Create a Team using the default manager
team = await Team.query.create(name="Edgy team")  # noqa

# Create an inactive user
user1 = await User.query.create(name="Edgy", email="foo@bar.com", is_active=False, team=team)  # noqa

# You can also create a user using the new manager
user2 = await User.query_related.create(
    name="Another Edgy", email="bar@foo.com", is_active=False, team=team
)  # noqa

# Create a user using the default manager
user3 = await User.query.create(name="Edgy", email="user@edgy.com", team=team)  # noqa


# Querying them all
users = await User.query.all()  # noqa
assert [user1, user2, user3] == users
# now with team
users2 = await team.members.all()  # noqa
assert [user1, user2] == users2

Warning

Be careful when overriding the default manager as you might not get all the results from the .all() if you don't filter properly.