Skip to content

Commit

Permalink
feat: add methods for finding or creating records in model
Browse files Browse the repository at this point in the history
This commit introduces two class methods, `find_or_create_by` and `find_or_initialize_by`, to the `BaseModel` class.

- `find_or_create_by` allows the retrieval of an existing record or the creation of a new one with the provided attributes.
- `find_or_initialize_by` retrieves an existing record or initializes a new instance without saving it.

These enhancements streamline record management within the model, making it easier to handle common data access patterns while promoting cleaner code practices.

Generated-by: aiautocommit
  • Loading branch information
iloveitaly committed Nov 28, 2024
1 parent 170e7ea commit f1fd2f2
Showing 1 changed file with 46 additions and 0 deletions.
46 changes: 46 additions & 0 deletions activemodel/base_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import typing as t
from contextlib import contextmanager

import pydash
import sqlalchemy as sa
Expand Down Expand Up @@ -42,9 +43,19 @@ class BaseModel(SQLModel):

@classmethod
def __init_subclass__(cls, **kwargs):
"Setup automatic sqlalchemy lifecycle events for the class"

super().__init_subclass__(**kwargs)

def event_wrapper(method_name: str):
"""
This does smart heavy lifting for us to make sqlalchemy lifecycle events nicer to work with:
* Passes the target first to the lifecycle method, so it feels like an instance method
* Allows as little as a single positional argument, so methods can be simple
* Removes the need for decorators or anything fancy on the subclass
"""

def wrapper(mapper: Mapper, connection: Connection, target: BaseModel):
if hasattr(cls, method_name):
method = getattr(cls, method_name)
Expand Down Expand Up @@ -136,6 +147,41 @@ def count(cls) -> int:
"""
return get_session().exec(sm.select(sm.func.count()).select_from(cls)).one()

# TODO throw an error if this field is set on the model
def is_new(self):
return not self._sa_instance_state.has_identity

@classmethod
def find_or_create_by(cls, **kwargs):
"""
Find record or create it with the passed args if it doesn't exist.
"""

result = cls.get(**kwargs)

if result:
return result

new_model = cls(**kwargs)
new_model.save()

return new_model

@classmethod
def find_or_initialize_by(cls, **kwargs):
"""
Unfortunately, unlike ruby, python does not have a great lambda story. This makes writing convenience methods
like this a bit more difficult.
"""

result = cls.get(**kwargs)

if result:
return result

new_model = cls(**kwargs)
return new_model

# TODO what's super dangerous here is you pass a kwarg which does not map to a specific
# field it will result in `True`, which will return all records, and not give you any typing
# errors. Dangerous when iterating on structure quickly
Expand Down

0 comments on commit f1fd2f2

Please sign in to comment.