Skip to content

Commit

Permalink
Feature/sync (#386)
Browse files Browse the repository at this point in the history
* feat: sync version
  • Loading branch information
roman-right committed Oct 22, 2022
1 parent dffa9d0 commit 43dfb13
Show file tree
Hide file tree
Showing 139 changed files with 10,996 additions and 49 deletions.
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@

## Overview

[Beanie](https://github.com/roman-right/beanie) - is an Asynchronous Python
object-document mapper (ODM) for MongoDB, based
on [Motor](https://motor.readthedocs.io/en/stable/)
and [Pydantic](https://pydantic-docs.helpmanual.io/).
[Beanie](https://github.com/roman-right/beanie) - is a Python object-document mapper (ODM) for MongoDB that can be used, and in synchronous, and async contexts. Data models are based on [Pydantic](https://pydantic-docs.helpmanual.io/).

When using Beanie each database collection has a corresponding `Document` that
is used to interact with that collection. In addition to retrieving data,
Expand Down Expand Up @@ -57,7 +54,7 @@ class Product(Document):
category: Category # You can include pydantic models as well


# Beanie is fully asynchronous, so we will access it from an async function
# This is an asynchronous example, so we will access it from an async function
async def example():
# Beanie uses Motor async client under the hood
client = AsyncIOMotorClient("mongodb://user:pass@host:27017")
Expand Down
2 changes: 1 addition & 1 deletion beanie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from beanie.odm.views import View
from beanie.odm.union_doc import UnionDoc

__version__ = "1.12.1"
__version__ = "1.13.0"
__all__ = [
# ODM
"Document",
Expand Down
1 change: 0 additions & 1 deletion beanie/odm/utils/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ def _encode(
obj,
) -> Any:
""""""

if self.custom_encoders:
if type(obj) in self.custom_encoders:
return self.custom_encoders[type(obj)](obj)
Expand Down
50 changes: 50 additions & 0 deletions beanie/sync/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from beanie import (
WriteRules,
DeleteRules,
Insert,
Replace,
SaveChanges,
ValidateOnSave,
Delete,
Before,
After,
Update,
)
from beanie.sync.odm import Link
from beanie.sync.odm.actions import (
before_event,
after_event,
)
from beanie.sync.odm.bulk import BulkWriter
from beanie.sync.odm.documents import Document
from beanie.sync.odm.settings.timeseries import TimeSeriesConfig, Granularity
from beanie.sync.odm.union_doc import UnionDoc
from beanie.sync.odm.utils.general import init_beanie
from beanie.sync.odm.views import View

__all__ = [
# ODM
"Document",
"View",
"UnionDoc",
"init_beanie",
"TimeSeriesConfig",
"Granularity",
# Actions
"before_event",
"after_event",
"Insert",
"Replace",
"SaveChanges",
"ValidateOnSave",
"Delete",
"Before",
"After",
"Update",
# Bulk Write
"BulkWriter",
# Relations
"Link",
"WriteRules",
"DeleteRules",
]
45 changes: 45 additions & 0 deletions beanie/sync/odm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from beanie.sync.odm.actions import before_event, after_event
from beanie.odm.actions import (
Insert,
Replace,
SaveChanges,
ValidateOnSave,
Before,
After,
Delete,
Update,
)
from beanie.sync.odm.bulk import BulkWriter
from beanie.sync.odm.fields import (
Link,
)
from beanie.sync.odm.settings.timeseries import TimeSeriesConfig, Granularity
from beanie.sync.odm.utils.general import init_beanie
from beanie.sync.odm.documents import Document
from beanie.sync.odm.views import View
from beanie.sync.odm.union_doc import UnionDoc

__all__ = [
# ODM
"Document",
"View",
"UnionDoc",
"init_beanie",
"TimeSeriesConfig",
"Granularity",
# Actions
"before_event",
"after_event",
"Insert",
"Replace",
"SaveChanges",
"ValidateOnSave",
"Delete",
"Before",
"After",
"Update",
# Bulk Write
"BulkWriter",
# Relations
"Link",
]
210 changes: 210 additions & 0 deletions beanie/sync/odm/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import asyncio
import inspect
from functools import wraps
from typing import (
Callable,
List,
Union,
Dict,
TYPE_CHECKING,
Any,
Type,
Optional,
Tuple,
)

from beanie.odm.actions import EventTypes, ActionDirections

if TYPE_CHECKING:
from beanie.sync.odm.documents import Document


class ActionRegistry:
_actions: Dict[Type, Any] = {}

# TODO the real type is
# Dict[str, Dict[EventTypes,Dict[ActionDirections: List[Callable]]]]
# But mypy says it has syntax error inside. Fix it.

@classmethod
def clean_actions(cls, document_class: Type):
if not cls._actions.get(document_class) is None:
del cls._actions[document_class]

@classmethod
def add_action(
cls,
document_class: Type,
event_types: List[EventTypes],
action_direction: ActionDirections,
funct: Callable,
):
"""
Add action to the action registry
:param document_class: document class
:param event_types: List[EventTypes]
:param action_direction: ActionDirections - before or after
:param funct: Callable - function
"""
if cls._actions.get(document_class) is None:
cls._actions[document_class] = {
action_type: {
action_direction: []
for action_direction in ActionDirections
}
for action_type in EventTypes
}
for event_type in event_types:
cls._actions[document_class][event_type][action_direction].append(
funct
)

@classmethod
def get_action_list(
cls,
document_class: Type,
event_type: EventTypes,
action_direction: ActionDirections,
) -> List[Callable]:
"""
Get stored action list
:param document_class: Type - document class
:param event_type: EventTypes - type of needed event
:param action_direction: ActionDirections - before or after
:return: List[Callable] - list of stored methods
"""
if document_class not in cls._actions:
return []
return cls._actions[document_class][event_type][action_direction]

@classmethod
def run_actions(
cls,
instance: "Document",
event_type: EventTypes,
action_direction: ActionDirections,
exclude: List[Union[ActionDirections, str]],
):
"""
Run actions
:param instance: Document - object of the Document subclass
:param event_type: EventTypes - event types
:param action_direction: ActionDirections - before or after
"""
if action_direction in exclude:
return

document_class = instance.__class__
actions_list = cls.get_action_list(
document_class, event_type, action_direction
)
coros = []
for action in actions_list:
if action.__name__ in exclude:
continue

if inspect.iscoroutinefunction(action):
coros.append(action(instance))
elif inspect.isfunction(action):
action(instance)

asyncio.gather(*coros)


def register_action(
event_types: Tuple[Union[List[EventTypes], EventTypes]],
action_direction: ActionDirections,
):
"""
Decorator. Base registration method.
Used inside `before_event` and `after_event`
:param event_types: Union[List[EventTypes], EventTypes] - event types
:param action_direction: ActionDirections - before or after
:return:
"""
final_event_types = []
for event_type in event_types:
if isinstance(event_type, list):
final_event_types.extend(event_type)
else:
final_event_types.append(event_type)

def decorator(f):
f.has_action = True
f.event_types = final_event_types
f.action_direction = action_direction
return f

return decorator


def before_event(*args: Union[List[EventTypes], EventTypes]):
"""
Decorator. It adds action, which should run before mentioned one
or many events happen
:param args: Union[List[EventTypes], EventTypes] - event types
:return: None
"""

return register_action(
action_direction=ActionDirections.BEFORE,
event_types=args, # type: ignore
)


def after_event(*args: Union[List[EventTypes], EventTypes]):
"""
Decorator. It adds action, which should run after mentioned one
or many events happen
:param args: Union[List[EventTypes], EventTypes] - event types
:return: None
"""

return register_action(
action_direction=ActionDirections.AFTER,
event_types=args, # type: ignore
)


def wrap_with_actions(event_type: EventTypes):
"""
Helper function to wrap Document methods with
before and after event listeners
:param event_type: EventTypes - event types
:return: None
"""

def decorator(f: Callable):
@wraps(f)
def wrapper(
self,
*args,
skip_actions: Optional[List[Union[ActionDirections, str]]] = None,
**kwargs,
):
if skip_actions is None:
skip_actions = []

ActionRegistry.run_actions(
self,
event_type=event_type,
action_direction=ActionDirections.BEFORE,
exclude=skip_actions,
)

result = f(self, *args, skip_actions=skip_actions, **kwargs)

ActionRegistry.run_actions(
self,
event_type=event_type,
action_direction=ActionDirections.AFTER,
exclude=skip_actions,
)

return result

return wrapper

return decorator
Loading

0 comments on commit 43dfb13

Please sign in to comment.