From 3bb0933efa914645c7cbe8e0e3261e06c816f8ea Mon Sep 17 00:00:00 2001 From: Vianney Mixtur <59029806+VianneyMI@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:43:09 +0200 Subject: [PATCH] 72 expressions improvements (#117) * Renamed statement as expression * Renamed expression as operand * Renamed resolve as express * expression -> operand * follow up * Fixed tests * Added mypy.ini and updated gitignore * Replaced old dict typehints by Expression * Follow up * Replaced dict typehint by Expression in operators * Follow up for search operators * FIXME : Fix lookup, join, right join and associated tests following collection removal in pipeline * Removed right joins * Removed old expressions module * Fixed test --- .gitignore | 1 + docs/index.md | 5 +- mypy.ini | 9 + readme.md | 5 +- src/monggregate/__init__.py | 3 +- src/monggregate/_run.py | 16 - src/monggregate/base.py | 32 +- src/monggregate/dollar.py | 100 ++-- src/monggregate/expressions.py | 505 ------------------ src/monggregate/operators/accumulators/avg.py | 15 +- .../operators/accumulators/count.py | 6 +- .../operators/accumulators/first.py | 15 +- .../operators/accumulators/last.py | 15 +- src/monggregate/operators/accumulators/max.py | 15 +- src/monggregate/operators/accumulators/min.py | 15 +- .../operators/accumulators/push.py | 15 +- src/monggregate/operators/accumulators/sum.py | 15 +- src/monggregate/operators/arithmetic/add.py | 11 +- .../operators/arithmetic/divide.py | 5 +- .../operators/arithmetic/multiply.py | 11 +- src/monggregate/operators/arithmetic/pow.py | 5 +- .../operators/arithmetic/subtract.py | 5 +- src/monggregate/operators/array/array.py | 2 +- .../operators/array/array_to_object.py | 15 +- src/monggregate/operators/array/filter.py | 18 +- src/monggregate/operators/array/first.py | 11 +- src/monggregate/operators/array/in_.py | 9 +- src/monggregate/operators/array/is_array.py | 11 +- src/monggregate/operators/array/last.py | 11 +- src/monggregate/operators/array/max_n.py | 16 +- src/monggregate/operators/array/min_n.py | 16 +- src/monggregate/operators/array/size.py | 11 +- src/monggregate/operators/array/sort_array.py | 16 +- src/monggregate/operators/boolean/and_.py | 11 +- src/monggregate/operators/boolean/not_.py | 15 +- src/monggregate/operators/boolean/or_.py | 11 +- src/monggregate/operators/comparison/cmp.py | 9 +- src/monggregate/operators/comparison/eq.py | 9 +- src/monggregate/operators/comparison/gt.py | 9 +- src/monggregate/operators/comparison/gte.py | 9 +- src/monggregate/operators/comparison/lt.py | 9 +- src/monggregate/operators/comparison/lte.py | 9 +- src/monggregate/operators/comparison/ne.py | 9 +- src/monggregate/operators/conditional/cond.py | 8 +- .../operators/conditional/if_null.py | 13 +- .../operators/conditional/switch.py | 5 +- src/monggregate/operators/date/millisecond.py | 15 +- .../operators/objects/merge_objects.py | 15 +- .../operators/objects/object_to_array.py | 18 +- src/monggregate/operators/strings/concat.py | 11 +- .../operators/strings/date_from_string.py | 6 +- .../operators/strings/date_to_string.py | 6 +- src/monggregate/operators/type_/type_.py | 14 +- src/monggregate/pipeline.py | 155 ++---- src/monggregate/search/collectors/facet.py | 22 +- src/monggregate/search/commons/count.py | 10 +- src/monggregate/search/commons/fuzzy.py | 6 +- src/monggregate/search/commons/highlight.py | 1 + .../search/operators/autocomplete.py | 5 +- src/monggregate/search/operators/compound.py | 8 +- src/monggregate/search/operators/equals.py | 5 +- src/monggregate/search/operators/exists.py | 5 +- .../search/operators/more_like_this.py | 10 +- src/monggregate/search/operators/range.py | 10 +- src/monggregate/search/operators/regex.py | 5 +- src/monggregate/search/operators/text.py | 6 +- src/monggregate/search/operators/wildcard.py | 6 +- src/monggregate/stages/bucket.py | 6 +- src/monggregate/stages/bucket_auto.py | 6 +- src/monggregate/stages/count.py | 5 +- src/monggregate/stages/group.py | 6 +- src/monggregate/stages/limit.py | 6 +- src/monggregate/stages/lookup.py | 6 +- src/monggregate/stages/match.py | 32 +- src/monggregate/stages/out.py | 6 +- src/monggregate/stages/project.py | 6 +- src/monggregate/stages/replace_root.py | 6 +- src/monggregate/stages/sample.py | 6 +- src/monggregate/stages/search/base.py | 6 +- src/monggregate/stages/search/search.py | 8 +- src/monggregate/stages/search/search_meta.py | 7 +- src/monggregate/stages/set.py | 5 +- src/monggregate/stages/skip.py | 5 +- src/monggregate/stages/sort.py | 6 +- src/monggregate/stages/sort_by_count.py | 6 +- src/monggregate/stages/stage.py | 16 +- src/monggregate/stages/union_with.py | 8 +- src/monggregate/stages/unset.py | 5 +- src/monggregate/stages/unwind.py | 6 +- src/monggregate/stages/vector_search.py | 8 +- tests/test_dollar.py | 6 +- tests/test_group.py | 12 +- tests/test_join_alias.py | 27 - tests/test_operators_accumulators.py | 32 +- tests/test_operators_array.py | 46 +- tests/test_operators_boolean.py | 20 +- tests/test_operators_comparison.py | 14 +- tests/test_operators_objects.py | 8 +- tests/test_search_operators.py | 16 +- tests/test_stages.py | 52 +- tests/tests_docs/test_readme.py | 12 +- 101 files changed, 644 insertions(+), 1203 deletions(-) create mode 100644 mypy.ini delete mode 100644 src/monggregate/_run.py delete mode 100644 src/monggregate/expressions.py diff --git a/.gitignore b/.gitignore index f776a28..2fce7c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ venv .venv +.coverage .env notes.md **/*~ diff --git a/docs/index.md b/docs/index.md index c234e28..1904f2b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -127,7 +127,7 @@ import os from dotenv import load_dotenv import pymongo -from monggregate import Pipeline, S, Expression +from monggregate import Pipeline, S # Creating connexion string securely load_dotenv(verbose=True) @@ -142,8 +142,7 @@ client = pymongo.MongoClient(MONGODB_URI) db = client["sample_mflix"] # Using expressions -comments_count = Expression.field("comments").size() - +comments_count = S.size(S.comments) # Creating the pipeline pipeline = Pipeline() diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..499a1ae --- /dev/null +++ b/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +show_column_numbers = True +implicit_optional = False +disallow_untyped_defs = True +disallow_untyped_calls = True +follow_imports = silent +ignore_missing_imports = True +plugins = pydantic.mypy +explicit_package_bases = True diff --git a/readme.md b/readme.md index c234e28..1904f2b 100644 --- a/readme.md +++ b/readme.md @@ -127,7 +127,7 @@ import os from dotenv import load_dotenv import pymongo -from monggregate import Pipeline, S, Expression +from monggregate import Pipeline, S # Creating connexion string securely load_dotenv(verbose=True) @@ -142,8 +142,7 @@ client = pymongo.MongoClient(MONGODB_URI) db = client["sample_mflix"] # Using expressions -comments_count = Expression.field("comments").size() - +comments_count = S.size(S.comments) # Creating the pipeline pipeline = Pipeline() diff --git a/src/monggregate/__init__.py b/src/monggregate/__init__.py index 1bef43c..a8fa57f 100644 --- a/src/monggregate/__init__.py +++ b/src/monggregate/__init__.py @@ -1,10 +1,9 @@ """App Package""" -from monggregate.expressions import Expression from monggregate.dollar import S, SS from monggregate.pipeline import Pipeline -__all__ = ["Expression", "S", "SS", "Pipeline"] +__all__ = ["Pipeline", "S", "SS"] __version__ = "0.21.0" __author__ = "Vianney Mixtur" diff --git a/src/monggregate/_run.py b/src/monggregate/_run.py deleted file mode 100644 index 9c38806..0000000 --- a/src/monggregate/_run.py +++ /dev/null @@ -1,16 +0,0 @@ -from types import NoneType -try: - from pymongo.database import Database - from pymongo.command_cursor import CommandCursor - PYMONGO = True -except ImportError: - Database = NoneType - PYMONGO = False -try: - from motor.motor_asyncio import AsyncIOMotorDatabase - from motor.motor_tornado import MotorDatabase - MOTOR = True -except ImportError: - AsyncIOMotorDatabase = NoneType - MotorDatabase = NoneType - MOTOR = False \ No newline at end of file diff --git a/src/monggregate/base.py b/src/monggregate/base.py index 0fc0b41..52ad892 100644 --- a/src/monggregate/base.py +++ b/src/monggregate/base.py @@ -8,6 +8,7 @@ #---------------------------- from abc import ABC, abstractmethod from typing import Any, TypeGuard +from typing_extensions import Self # 3rd Party imports @@ -15,7 +16,7 @@ try: import pydantic.v1 as pyd except ModuleNotFoundError: - import pydantic as pyd + import pydantic as pyd # type: ignore[no-redef] from humps.main import camelize @@ -24,32 +25,39 @@ class Singleton: """Singleton metaclass""" _instance = None - def __new__(cls, *args, **kwargs): + def __new__(cls, *args:Any, **kwargs:Any)->Self: if not isinstance(cls._instance, cls): cls._instance = object.__new__(cls, *args, **kwargs) return cls._instance + +Expression = dict[str, Any] class BaseModel(pyd.BaseModel, ABC): """Mongreggate base class""" + def to_expression(self)->Expression|list[Expression]: + """Converts an instance of a class inheriting from BaseModel to an expression""" + + return self.express(self) + @classmethod - def resolve(cls, obj:Any)->dict|list[dict]: + def express(cls, obj:Any)->Expression|list[Expression]: """Resolves an expression encapsulated in an object from a class inheriting from BaseModel""" - return resolve(obj) + return express(obj) @property @abstractmethod - def statement(self)->dict: + def expression(self)->Expression: """Stage statement absctract method""" # this is a lazy attribute # what is currently in generate statement should go in here - def __call__(self)->dict: + def __call__(self)->Expression|list[Expression]: """Makes an instance of any class inheriting from this class callable""" - return self.resolve(self.statement) + return self.to_expression() class Config(pyd.BaseConfig): """Base configuration for classes inheriting from this""" @@ -65,25 +73,25 @@ def isbasemodel(instance:Any)->TypeGuard[BaseModel]: return isinstance(instance, BaseModel) -def resolve(obj:Any)->dict|list[dict]: +def express(obj:Any)->dict|list[dict]: """Resolves an expression encapsulated in an object from a class inheriting from BaseModel""" if isbasemodel(obj): - output:dict|list = obj.statement + output:dict|list = obj.expression elif isinstance(obj, list) and any(map(isbasemodel, obj)): output = [] for element in obj: if isinstance(element, BaseModel): - output.append(element.statement) + output.append(element.expression) else: output.append(element) elif isinstance(obj, dict): output = {} for key, value in obj.items(): if isinstance(value, BaseModel): - output[key] = value.statement + output[key] = value.expression else: - output[key] = resolve(value) + output[key] = express(value) else: output = obj diff --git a/src/monggregate/dollar.py b/src/monggregate/dollar.py index 3d0208f..6df091d 100644 --- a/src/monggregate/dollar.py +++ b/src/monggregate/dollar.py @@ -131,10 +131,10 @@ def field(self, name:str)->str: # Accumulators # ------------------------------- @classmethod - def avg(cls, expression:Any)->accumulators.Avg: + def avg(cls, operand:Any)->accumulators.Avg: """Returns the $avg operator""" - return accumulators.avg(expression) + return accumulators.avg(operand) @classmethod def count(cls)->accumulators.Count: @@ -143,40 +143,40 @@ def count(cls)->accumulators.Count: return accumulators.count() @classmethod - def first(cls, expression:Any)->accumulators.First: + def first(cls, operand:Any)->accumulators.First: """Returns the $first operator""" - return accumulators.first(expression) + return accumulators.first(operand) @classmethod - def last(cls, expression:Any)->accumulators.Last: + def last(cls, operand:Any)->accumulators.Last: """Returns the $last operator""" - return accumulators.last(expression) + return accumulators.last(operand) @classmethod - def max(cls, expression:Any)->accumulators.Max: + def max(cls, operand:Any)->accumulators.Max: """Returns the $max operator""" - return accumulators.max(expression) + return accumulators.max(operand) @classmethod - def min(cls, expression:Any)->accumulators.Min: + def min(cls, operand:Any)->accumulators.Min: """Returns the $min operator""" - return accumulators.min(expression) + return accumulators.min(operand) @classmethod - def push(cls, expression:Any)->accumulators.Push: + def push(cls, operand:Any)->accumulators.Push: """Returns the $push operator""" - return accumulators.push(expression) + return accumulators.push(operand) @classmethod - def sum(cls, expression:Any)->accumulators.Sum: + def sum(cls, operand:Any)->accumulators.Sum: """Returns the $sum operator""" - return accumulators.sum(expression) + return accumulators.sum(operand) #-------------------------------- # Arithmetic @@ -188,10 +188,10 @@ def add(cls, *args:Any)->arithmetic.Add: return arithmetic.add(*args) # @classmethod - # def ceil(cls, expression:Any)->arithmetic.Ceil: + # def ceil(cls, operand:Any)->arithmetic.Ceil: # """Returns the $ceil operator""" - # return arithmetic.ceil(expression) + # return arithmetic.ceil(operand) @classmethod def divide(cls, *args:Any)->arithmetic.Divide: @@ -200,23 +200,23 @@ def divide(cls, *args:Any)->arithmetic.Divide: return arithmetic.divide(*args) # @classmethod - # def exp(cls, expression:Any)->arithmetic.Exp: + # def exp(cls, operand:Any)->arithmetic.Exp: # """Returns the $exp operator""" - # return arithmetic.exp(expression) + # return arithmetic.exp(operand) # @classmethod - # def floor(cls, expression:Any)->arithmetic.Floor: + # def floor(cls, operand:Any)->arithmetic.Floor: # """Returns the $floor operator""" - # return arithmetic.floor(expression) + # return arithmetic.floor(operand) # @classmethod - # def ln(cls, expression:Any)->arithmetic.Ln: + # def ln(cls, operand:Any)->arithmetic.Ln: # """Returns the $ln operator""" - # return arithmetic.ln(expression) + # return arithmetic.ln(operand) # @classmethod @@ -227,10 +227,10 @@ def divide(cls, *args:Any)->arithmetic.Divide: # @classmethod - # def log10(cls, expression:Any)->arithmetic.Log10: + # def log10(cls, operand:Any)->arithmetic.Log10: # """Returns the $log10 operator""" - # return arithmetic.log10(expression) + # return arithmetic.log10(operand) # @classmethod @@ -257,17 +257,17 @@ def pow(cls, *args:Any)->arithmetic.Pow: # Array # ------------------------------- @classmethod - def array_to_object(cls, expression:Any)->array.ArrayToObject: + def array_to_object(cls, operand:Any)->array.ArrayToObject: """Returns the $arrayToObject operator""" - return array.array_to_object(expression) + return array.array_to_object(operand) # TODO : Workout aliases @classmethod - def filter(cls, expression:Any,*, let:str, query:Any, limit:int|None=None)->array.Filter: + def filter(cls, operand:Any,*, let:str, query:Any, limit:int|None=None)->array.Filter: """Returns the $filter operator""" - return array.filter(expression, let, query, limit) + return array.filter(operand, let, query, limit) @classmethod def in_(cls, left:Any, right:Any)->array.In: @@ -276,36 +276,36 @@ def in_(cls, left:Any, right:Any)->array.In: return array.in_(left, right) @classmethod - def is_array(cls, expression:Any)->array.IsArray: + def is_array(cls, operand:Any)->array.IsArray: """Returns the $isArray operator""" - return array.is_array(expression) + return array.is_array(operand) @classmethod - def max_n(cls, expression:Any, n:int=1)->array.MaxN: + def max_n(cls, operand:Any, n:int=1)->array.MaxN: """Returns the $max operator""" - return array.max_n(expression, n) + return array.max_n(operand, n) @classmethod - def min_n(cls, expression:Any, n:int=1)->array.MinN: + def min_n(cls, operand:Any, n:int=1)->array.MinN: """Returns the $min operator""" - return array.min_n(expression, n) + return array.min_n(operand, n) @classmethod - def size(cls, expression:Any)->array.Size: + def size(cls, operand:Any)->array.Size: """Returns the $size operator""" - return array.size(expression) + return array.size(operand) # TODO : Check if the type of the sort_spec is correct # or can it be an expression that needs to evaluate to a dict[str, 1,-1] @classmethod - def sort_array(cls, expression:Any, sort_spec:dict[str, Literal[1,-1]])->array.SortArray: + def sort_array(cls, operand:Any, sort_spec:dict[str, Literal[1,-1]])->array.SortArray: """Returns the $sort operator""" - return array.sort_array(expression, sort_spec) + return array.sort_array(operand, sort_spec) #-------------------------------- # Comparison @@ -362,10 +362,10 @@ def cond(cls, if_:Any, then:Any, else_:Any)->conditional.Cond: return conditional.cond(if_, then, else_) @classmethod - def if_null(cls, expression:Any, replacement:Any)->conditional.IfNull: + def if_null(cls, operand:Any, replacement:Any)->conditional.IfNull: """Returns the $ifNull operator""" - return conditional.if_null(expression, replacement) + return conditional.if_null(operand, replacement) @classmethod def switch(cls, branches:dict[Any, Any], default:Any)->conditional.Switch: @@ -377,10 +377,10 @@ def switch(cls, branches:dict[Any, Any], default:Any)->conditional.Switch: # Date # ------------------------------- @classmethod - def millisecond(cls, expression:Any, timezone:Any)->date.Millisecond: + def millisecond(cls, operand:Any, timezone:Any)->date.Millisecond: """Returns the $millisecond operator""" - return date.millisecond(expression, timezone) + return date.millisecond(operand, timezone) #-------------------------------- @@ -409,14 +409,14 @@ def date_from_string( @classmethod def date_to_string( cls, - expression:Any, + operand:Any, format:Any=None, timezone:Any=None, on_null:Any=None )->strings.DateToString: """Returns the $dateToString operator""" - return strings.date_to_string(expression, format, timezone, on_null) + return strings.date_to_string(operand, format, timezone, on_null) #-------------------------------- # Objects @@ -428,10 +428,10 @@ def merge_objects(cls, *args:Any)->objects.MergeObjects: return objects.merge_objects(*args) @classmethod - def object_to_array(cls, expression:Any)->objects.ObjectToArray: + def object_to_array(cls, operand:Any)->objects.ObjectToArray: """Returns the $objectToArray operator""" - return objects.object_to_array(expression) + return objects.object_to_array(operand) #-------------------------------- # Boolean @@ -449,19 +449,19 @@ def or_(cls, *args:Any)->boolean.Or: return boolean.or_(*args) @classmethod - def not_(cls, expression:Any)->boolean.Not: + def not_(cls, operand:Any)->boolean.Not: """Returns the $not operator""" - return boolean.not_(expression) + return boolean.not_(operand) #-------------------------------- # Type # ------------------------------- @classmethod - def type_(cls, expression:Any)->type_.Type_: + def type_(cls, operand:Any)->type_.Type_: """Returns the $type operator""" - return type_.type_(expression) + return type_.type_(operand) class DollarDollar(Singleton): diff --git a/src/monggregate/expressions.py b/src/monggregate/expressions.py deleted file mode 100644 index c4ff884..0000000 --- a/src/monggregate/expressions.py +++ /dev/null @@ -1,505 +0,0 @@ -"""Expressions Module""" - -# Standard Library imports -#---------------------------- -from typing import Any, Literal -from typing_extensions import Self - -# 3rd Party imports -# --------------------------- -from monggregate.base import BaseModel, pyd - -# Local imports -# ---------------------------- -from monggregate.fields import FieldPath, Variable -from monggregate.operators import( - accumulators, - arithmetic, - array, - boolean, - comparison, - conditional, - date, - objects, - strings, - type_, -) - -class Expression(BaseModel): - """ - MongoDB expression interface. - - Expressions can include field paths, literals, systems variables, expression objects of the form {field1: Expression} - and expression operators of the form {operator:[arg, .. argN]} or {operator:arg}. - - Expressions can be nested. - """ - - content : Any - - @property - def statement(self)->Any: - return self.resolve(self.content) - - #---------------------------------------------------- - # Expression Internal Methods - #---------------------------------------------------- - @classmethod - def constant(cls, value:int | float | str | bool | None)->Self: - """Creates a constant expression.""" - - return cls(content=value) - - @classmethod - def field(cls, name:str)->Self: - """Creates a field expression.""" - - if not name.startswith("$"): - name = f"${name}" - - return cls(content=FieldPath(name)) - - - @classmethod - def variable(cls, name:str)->Self: - """Creates a variable expression.""" - - while not variable.startswith("$$"): - variable = "$" + variable - - return cls(content=Variable(name)) - - #--------------------------------------------------- - # Accumulators (Aggregation) Operators - #--------------------------------------------------- - def average(self)->Self: - """Creates an $avg expression""" - - - return self.__class__(content=accumulators.Avg(expression=self)) - - - def count(self)->Self: - """Creates a $count expression""" - - - return self.__class__(content=accumulators.Count()) - - - def first(self)->Self: - """Creates a $first expression""" - - - return self.__class__(content=accumulators.First(expression=self)) - - - def last(self)->Self: - """Creates a $last expression""" - - - return self.__class__(content=accumulators.Last(expression=self)) - - - def max(self)->Self: - """Creates a $max expression""" - - - return self.__class__(content=accumulators.Max(expression=self)) - - - def min(self)->Self: - """Creates a $min expression""" - - - return self.__class__(content=accumulators.Min(expression=self)) - - - def push(self)->Self: - """Creates a $push expression""" - - - return self.__class__(content=accumulators.Push(expression=self)) - - - def sum(self)->Self: - """Creates a $sum expression""" - - return self.__class__(content=accumulators.Sum(expression=self)) - - - - #--------------------------------------------------- - # Arithmetic Operators - #--------------------------------------------------- - def __add__(self, other:Self)->Self: - """ - Creates a $add expression. - - Overloads python addition operator (+). - """ - - - return self.__class__(content=arithmetic.Add(left=self, right=other)) - - - def __sub__(self, other:Self)->Self: - """ - Creates a $subtract expression. - - Overloads python subtraction operator (-). - """ - - - return self.__class__(content=arithmetic.Subtract(left=self, right=other)) - - - def __mul__(self, other:Self)->Self: - """ - Creates a $multiply expression. - - Overloads python multiplication operator (*). - """ - - - return self.__class__(content=arithmetic.Multiply(left=self, right=other)) - - - def __div__(self, other:Self)->Self: - """ - Creates a $divide expression. - - Overloads python division operator (/). - """ - - - return self.__class__(content=arithmetic.Divide(left=self, right=other)) - - - def __pow__(self, other:Self)->Self: - """ - Creates a $pow expression. - - Overloads python power operator (**). - """ - - - return self.__class__(content=arithmetic.Pow(left=self, right=other)) - - - def __radd__(self, other:Self)->Self: - """ - Creates a $add expression. - - Overloads python addition operator (+). - """ - - - return self.__class__(content=arithmetic.Add(left=other, right=self)) - - - def __rsub__(self, other:Self)->Self: - """ - Creates a $subtract expression. - - Overloads python subtraction operator (-). - """ - - - return self.__class__(content=arithmetic.Subtract(left=other, right=self)) - - - def __rmul__(self, other:Self)->Self: - """ - Creates a $multiply expression. - - Overloads python multiplication operator (*). - """ - - - return self.__class__(content=arithmetic.Multiply(left=other, right=self)) - - def __rdiv__(self, other:Self)->Self: - """ - Creates a $divide expression. - - Overloads python division operator (/). - """ - - - return self.__class__(content=arithmetic.Divide(left=other, right=self)) - - - def __rpow__(self, other:Self)->Self: - """ - Creates a $pow expression. - - Overloads python power operator (**). - """ - - - return self.__class__(content=arithmetic.Pow(left=other, right=self)) - - #--------------------------------------------------- - # Array Operators - #--------------------------------------------------- - def array_to_object(self)->Self: - """Creates a $arrayToObject expression""" - - - return self.__class__(content=array.ArrayToObject(expression=self)) - - - def in_(self, right:Self)->Self: - """Creates a $in operator""" - - - return self.__class__(content=array.In(left=self, right=right)) - - - def __contains__(self, right:Self)->Self: - """Creates a $in expression""" - - return self.__class__(content=array.In(left=self, right=right)) - - def filter(self, query:Self, let:str|None=None, limit:int|None=None)->Self: - """"Creates a $filter expression""" - - - return self.__class__(content=array.Filter( - expression=self, - query=query, - let=let, - limit=limit - )) - - - def is_array(self)->Self: - """Creates a $isArray expression""" - - - return self.__class__(content=array.IsArray(expression=self)) - - - def max_n(self, limit:int=1)->Self: - """Creates a $maxN expression""" - - - return self.__class__(content=array.MaxN(expression=self, limit=limit)) - - - def min_n(self, limit:int=1)->Self: - """Creates a $maxN expression""" - - - return self.__class__(content=array.MinN(expression=self, limit=limit)) - - - def size(self)->Self: - """Creates a $size expression""" - - - return self.__class__(content=array.Size(expression=self)) - - - def sort_array(self, by:dict[str, Literal[1, -1]])->Self: - """Creates a $sortArray expression""" - - - return self.__class__(content=array.SortArray(expression=self, by=by)) - - - #--------------------------------------------------- - # Boolean (Logical) Operators - #--------------------------------------------------- - def __and__(self, other:Self)->Self: - """ - Creates an And operator expression. - - Overloads python bitwise AND operator ($). - """ - - - return self.__class__(content=boolean.And(expressions=[self, other])) - - - def __or__(self, other:Self)->Self: - """ - Creates an Or operator expression. - - Overloads python bitwise OR operator (|). - """ - - - return self.__class__(content=boolean.Or(expressions=[self, other])) - - - def __invert__(self)->Self: - """ - Creates an Not operator expression. - - Overloads the python bitwise NOT operator (~). - """ - - - return self.__class__(content=boolean.Not(expression=self)) - - - #--------------------------------------------------- - # Comparison Operators - #--------------------------------------------------- - def __eq__(self, other:Self)->Self: - """ - Creates a $eq expression. - - Overloads python Equal to operator (==). - """ - - - return self.__class__(content=comparison.Eq(left=self, right=other)) - - - def __lt__(self, other:Self)->Self: - """ - Creates a $lt expression. - - Overloads python Less than operator (<). - """ - - - return self.__class__(content=comparison.Lt(left=self, right=other)) - - - def __le__(self, other:Self)->Self: - """ - Creates a $le expression. - - Overloads python Less than or equal to operator (<=). - """ - - - return self.__class__(content=comparison.Lte(left=self, right=other)) - - - def __gt__(self, other:Self)->Self: - """ - Creates a $gt expression. - - Overloads python Greater than operator (>). - """ - - - return self.__class__(content=comparison.Gt(left=self, right=other)) - - - def __ge__(self, other:Self)->Self: - """ - Creates a $gte expression. - - Overloads python Greather than or equal to operator (>=). - """ - - - return self.__class__(content=comparison.Gte(left=self, right=other)) - - - def __ne__(self, other:Self)->Self: - """ - Creates a $lt expression. - - Overloads python Not equal to operator (!=). - """ - - - return self.__class__(content=comparison.Ne(left=self, right=other)) - - - def compare(self, other:Self)->Self: - """Creates a $cmp expression.""" - - - return self.__class__(content=comparison.Cmp(left=self, right=other)) - - #--------------------------------------------------- - # Conditional Operators - #--------------------------------------------------- - def cond(self, then_:Self, else_:Self)->Self: - """Creates a $cond expression""" - - - return self.__class__(content=conditional.Cond(if_=self, then_=then_, else_=else_)) - - def if_null(self, output:Self)->Self: - """Creates a $ifNull expression""" - - - return self.__class__(content=conditional.IfNull(expression=self, output=output)) - - - def switch(self, branches:list[Any], default:Self)->Self: - """Creates a $switch expression""" - - - return self.__class__(content=conditional.Switch(expression=self, branches=branches, default=default)) - - #--------------------------------------------------- - # Date Operators - #--------------------------------------------------- - def millisecond(self)->Self: - """Creates a $millisecond expression""" - - - return self.__class__(content=date.Millisecond(expression=self)) - - #--------------------------------------------------- - # Objects Operators - #--------------------------------------------------- - def merge_objects(self)->Self: - """Creates a $mergeObjects operator""" - - - return self.__class__(content=objects.MergeObjects(expression=self)) - - - def object_to_array(self)->Self: - """Creates a $objectToArray operator""" - - - return self.__class__(content=objects.ObjectToArray(expression=self)) - - - #--------------------------------------------------- - # String Operators - #--------------------------------------------------- - def concat(self, *args:Self)->Self: - """Creates a $concat operator""" - - - return self.__class__(content=strings.Concat(expressions=[self, *args])) - - def date_from_string(self)->Self: - """Creates a $dateFromString operator""" - - - return self.__class__(content=strings.DateFromString(expression=self)) - - - def date_to_string(self)->Self: - """Creates a $dateToString operator""" - - - return self.__class__(content=strings.DateToString(expression=self)) - - #--------------------------------------------------- - # Type Operators - #--------------------------------------------------- - def type_(self)->Self: - """Creates a $type operator""" - - - return self.__class__(content=type_.Type_(expression=self)) - - -if __name__ == "__main__": - #result = Expression.field("left") & Expression.field("right") - result = Expression.field("related_comments").size() - print(result()) \ No newline at end of file diff --git a/src/monggregate/operators/accumulators/avg.py b/src/monggregate/operators/accumulators/avg.py index dd581df..a7c8f13 100644 --- a/src/monggregate/operators/accumulators/avg.py +++ b/src/monggregate/operators/accumulators/avg.py @@ -71,6 +71,7 @@ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Average(Accumulator): @@ -79,7 +80,7 @@ class Average(Accumulator): Attributes ---------- - expression : Any valid expression + operand : Any valid expression Online MongoDB documentation: ----------------------------- @@ -102,20 +103,20 @@ class Average(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#mongodb-group-grp.-avg) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$avg" : self.expression + return self.express({ + "$avg" : self.operand }) Avg = Average -def average(expression:Any)->Average: +def average(operand:Any)->Average: """Returns a $avg operator""" - return Average(expression=expression) + return Average(operand=operand) avg = average diff --git a/src/monggregate/operators/accumulators/count.py b/src/monggregate/operators/accumulators/count.py index 1822f2a..a26c91b 100644 --- a/src/monggregate/operators/accumulators/count.py +++ b/src/monggregate/operators/accumulators/count.py @@ -44,7 +44,7 @@ """ - +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Count(Accumulator): @@ -70,9 +70,9 @@ class Count(Accumulator): @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$count" : {} }) diff --git a/src/monggregate/operators/accumulators/first.py b/src/monggregate/operators/accumulators/first.py index 35c816b..b10c240 100644 --- a/src/monggregate/operators/accumulators/first.py +++ b/src/monggregate/operators/accumulators/first.py @@ -60,6 +60,7 @@ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class First(Accumulator): @@ -68,7 +69,7 @@ class First(Accumulator): Attributes ------------------------ - - expression, Expression : Any valid expression + - operand, Any : Any valid expression Online MongoDB documentation: ------------------------------ @@ -88,17 +89,17 @@ class First(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#mongodb-group-grp.-first) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$first" : self.expression + return self.express({ + "$first" : self.operand }) -def first(expression:Any)->First: +def first(operand:Any)->First: """Returns a $first operator""" - return First(expression=expression) + return First(operand=operand) diff --git a/src/monggregate/operators/accumulators/last.py b/src/monggregate/operators/accumulators/last.py index ab67140..a8fb8c0 100644 --- a/src/monggregate/operators/accumulators/last.py +++ b/src/monggregate/operators/accumulators/last.py @@ -52,6 +52,7 @@ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Last(Accumulator): @@ -60,7 +61,7 @@ class Last(Accumulator): Attributes ------------------------ - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation ---------------------------- @@ -80,18 +81,18 @@ class Last(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/#mongodb-group-grp.-last) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$last" : self.expression + return self.express({ + "$last" : self.operand }) -def last(expression:Any)->Last: +def last(operand:Any)->Last: """Returns a $last operator""" - return Last(expression=expression) + return Last(operand=operand) diff --git a/src/monggregate/operators/accumulators/max.py b/src/monggregate/operators/accumulators/max.py index b72431f..d9d6d00 100644 --- a/src/monggregate/operators/accumulators/max.py +++ b/src/monggregate/operators/accumulators/max.py @@ -77,6 +77,7 @@ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Max(Accumulator): @@ -85,7 +86,7 @@ class Max(Accumulator): Attributes --------------------- - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation ---------------------------- @@ -109,18 +110,18 @@ class Max(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#mongodb-group-grp.-max) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$max" : self.expression + return self.express({ + "$max" : self.operand }) -def max(expression:Any)->Max: +def max(operand:Any)->Max: """Returns a $last operator""" - return Max(expression=expression) + return Max(operand=operand) diff --git a/src/monggregate/operators/accumulators/min.py b/src/monggregate/operators/accumulators/min.py index 908a01c..4d6d462 100644 --- a/src/monggregate/operators/accumulators/min.py +++ b/src/monggregate/operators/accumulators/min.py @@ -77,6 +77,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Min(Accumulator): @@ -85,7 +86,7 @@ class Min(Accumulator): Attributes ---------------------- - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation @@ -109,18 +110,18 @@ class Min(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#mongodb-group-grp.-min) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$min" : self.expression + return self.express({ + "$min" : self.operand }) -def min(expression:Any)->Min: +def min(operand:Any)->Min: """Returns a $min operator""" - return Min(expression=expression) + return Min(operand=operand) diff --git a/src/monggregate/operators/accumulators/push.py b/src/monggregate/operators/accumulators/push.py index 2bc9c33..2aa4f44 100644 --- a/src/monggregate/operators/accumulators/push.py +++ b/src/monggregate/operators/accumulators/push.py @@ -29,6 +29,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Push(Accumulator): @@ -37,7 +38,7 @@ class Push(Accumulator): Attributes ------------------- - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation ---------------------------- @@ -52,18 +53,18 @@ class Push(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#mongodb-group-grp.-push) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$push" : self.expression + return self.express({ + "$push" : self.operand }) -def push(expression:Any)->Push: +def push(operand:Any)->Push: """Returns a $push operator""" - return Push(expression=expression) + return Push(operand=operand) diff --git a/src/monggregate/operators/accumulators/sum.py b/src/monggregate/operators/accumulators/sum.py index 60d4531..c2429a6 100644 --- a/src/monggregate/operators/accumulators/sum.py +++ b/src/monggregate/operators/accumulators/sum.py @@ -86,6 +86,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.accumulators.accumulator import Accumulator class Sum(Accumulator): @@ -95,7 +96,7 @@ class Sum(Accumulator): Attributes ----------------------- - operands, list[Expression] : Any valid list of expressions - - operand, Expression : Any valid expression + - operand, Any :Any valid expression Online MongoDB documentation ---------------------------- @@ -119,22 +120,22 @@ class Sum(Accumulator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#mongodb-group-grp.-sum) """ - expression : Any + operand : Any @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ - "$sum" : self.expression + return self.express({ + "$sum" : self.operand }) def sum(*args:Any)->Sum: """Returns a $sum operator""" if len(args)>1: - output = Sum(expression=list(args)) + output = Sum(operand=list(args)) else: - output = Sum(expression=args[0]) + output = Sum(operand=args[0]) return output diff --git a/src/monggregate/operators/arithmetic/add.py b/src/monggregate/operators/arithmetic/add.py index caf4248..e4334ec 100644 --- a/src/monggregate/operators/arithmetic/add.py +++ b/src/monggregate/operators/arithmetic/add.py @@ -21,6 +21,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator class Add(ArithmeticOperator): @@ -50,17 +51,17 @@ class Add(ArithmeticOperator): """ - expressions : list[Any] + operands : list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$add" : self.expressions + def expression(self) -> Expression: + return self.express({ + "$add" : self.operands }) def add(*args:Any)->Add: """Returns a $add operator""" return Add( - expressions=list(args) + operands=list(args) ) \ No newline at end of file diff --git a/src/monggregate/operators/arithmetic/divide.py b/src/monggregate/operators/arithmetic/divide.py index 562b956..488c961 100644 --- a/src/monggregate/operators/arithmetic/divide.py +++ b/src/monggregate/operators/arithmetic/divide.py @@ -23,6 +23,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator class Divide(ArithmeticOperator): @@ -57,8 +58,8 @@ class Divide(ArithmeticOperator): denominator : Any @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$divide" : [self.numerator, self.denominator] }) diff --git a/src/monggregate/operators/arithmetic/multiply.py b/src/monggregate/operators/arithmetic/multiply.py index deba343..ccfb2f0 100644 --- a/src/monggregate/operators/arithmetic/multiply.py +++ b/src/monggregate/operators/arithmetic/multiply.py @@ -22,6 +22,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator class Multiply(ArithmeticOperator): @@ -49,17 +50,17 @@ class Multiply(ArithmeticOperator): """ - expressions : list[Any] + operands : list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$multiply" : self.expressions + def expression(self) -> Expression: + return self.express({ + "$multiply" : self.operands }) def multiply(*args:Any)->Multiply: """Returns a $multiply operator""" return Multiply( - expressions=list(args) + operands=list(args) ) \ No newline at end of file diff --git a/src/monggregate/operators/arithmetic/pow.py b/src/monggregate/operators/arithmetic/pow.py index 94d8113..982ee6d 100644 --- a/src/monggregate/operators/arithmetic/pow.py +++ b/src/monggregate/operators/arithmetic/pow.py @@ -40,6 +40,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator class Pow(ArithmeticOperator): @@ -73,8 +74,8 @@ class Pow(ArithmeticOperator): exponent : Any @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$pow" : [self.number, self.exponent] }) diff --git a/src/monggregate/operators/arithmetic/subtract.py b/src/monggregate/operators/arithmetic/subtract.py index a44ce4f..f5a036d 100644 --- a/src/monggregate/operators/arithmetic/subtract.py +++ b/src/monggregate/operators/arithmetic/subtract.py @@ -37,6 +37,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator class Subtract(ArithmeticOperator): @@ -73,8 +74,8 @@ class Subtract(ArithmeticOperator): right: Any @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$substract" : [self.left, self.right] }) diff --git a/src/monggregate/operators/array/array.py b/src/monggregate/operators/array/array.py index 64d9780..4bed895 100644 --- a/src/monggregate/operators/array/array.py +++ b/src/monggregate/operators/array/array.py @@ -49,7 +49,7 @@ class ArrayOperator(Operator, ABC): class ArrayOnlyOperator(ArrayOperator, ABC): """Base class for array operators that work directly on the input array without any other parameters""" - expression : Any + operand : Any # Type aliases # ----------------------------------------- diff --git a/src/monggregate/operators/array/array_to_object.py b/src/monggregate/operators/array/array_to_object.py index 3c6b851..31f8a7a 100644 --- a/src/monggregate/operators/array/array_to_object.py +++ b/src/monggregate/operators/array/array_to_object.py @@ -49,6 +49,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOperator class ArrayToObject(ArrayOperator): @@ -57,7 +58,7 @@ class ArrayToObject(ArrayOperator): Attributes ---------------------- - - expression, Expression : Any valid expression that resolves to an array of two-element arrays + - operand, Any:Any valid expression that resolves to an array of two-element arrays or array if documents that contains "k" and "v" fields. Online MongoDB documentation @@ -88,16 +89,16 @@ class ArrayToObject(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/#mongodb-expression-exp.-arrayToObject) """ - expression : Any + operand : Any @property - def statement(self) -> dict: - return self.resolve({ - "$arrayToObject" : self.expression + def expression(self) -> Expression: + return self.express({ + "$arrayToObject" : self.operand }) -def array_to_object(expression:Any)->ArrayToObject: +def array_to_object(operand:Any)->ArrayToObject: """Returns a $arrayToObject operator""" - return ArrayToObject(expression=expression) + return ArrayToObject(operand=operand) diff --git a/src/monggregate/operators/array/filter.py b/src/monggregate/operators/array/filter.py index 0e06113..a57acfe 100644 --- a/src/monggregate/operators/array/filter.py +++ b/src/monggregate/operators/array/filter.py @@ -43,7 +43,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.array.array import ArrayOperator class Filter(ArrayOperator): @@ -54,8 +54,8 @@ class Filter(ArrayOperator): Attributes -------------------------------- - - expression / input, Expression : An expression that resolves to an array - - query / cond, Expression : An expressions that resolves to a boolean value used to determine + - expression / input, Any : An expression that resolves to an array + - query / cond, Any :An expressions that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. @@ -104,27 +104,27 @@ class Filter(ArrayOperator): """ - expression : Any = pyd.Field(alias="input") + operand : Any = pyd.Field(alias="input") query : Any = pyd.Field(alias="cond") let : str | None = pyd.Field("this", alias="as") limit : int | None = pyd.Field(None, ge=1) # NOTE : limit can actually be an expression but constraints are invalid with any type @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$filter":{ - "input" : self.expression, + "input" : self.operand, "cond" : self.query, "as" : self.let, "limit" : self.limit } }) -def filter(expression:Any, let:str, query:Any, limit:int|None=None)->Filter: +def filter(operand:Any, let:str, query:Any, limit:int|None=None)->Filter: """Returns a $filter operator""" return Filter( - expression = expression, + operand=operand, query = query, let = let, limit = limit diff --git a/src/monggregate/operators/array/first.py b/src/monggregate/operators/array/first.py index 9ccd5b6..c5aba61 100644 --- a/src/monggregate/operators/array/first.py +++ b/src/monggregate/operators/array/first.py @@ -98,6 +98,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOnlyOperator class First(ArrayOnlyOperator): @@ -106,7 +107,7 @@ class First(ArrayOnlyOperator): Attributes ------------------- - - expression, Expression : Any valid expressions as long as it resolves to an array, null or missing value. + - operand, Any:Any valid expressions as long as it resolves to an array, null or missing value. Online MongoDB documentation ---------------------------- @@ -116,14 +117,14 @@ class First(ArrayOnlyOperator): """ @property - def statement(self) -> dict: - return self.resolve({ - "$first":self.expression + def expression(self) -> Expression: + return self.express({ + "$first":self.operand }) def first(array:Any)->First: """Returns a $first operator""" return First( - expression = array + operand = array ) diff --git a/src/monggregate/operators/array/in_.py b/src/monggregate/operators/array/in_.py index eedd251..23123cf 100644 --- a/src/monggregate/operators/array/in_.py +++ b/src/monggregate/operators/array/in_.py @@ -31,6 +31,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOperator class In(ArrayOperator): @@ -40,8 +41,8 @@ class In(ArrayOperator): Attributes ------------------------------ - - left, Expression : Any valid expression (to be looked for in right) - - right, Expression : Any valid expression that resolves to an array (containing left or not). + - left, Any :Any valid expression (to be looked for in right) + - right, Any :Any valid expression that resolves to an array (containing left or not). Online MongoDB documentation ---------------------------- @@ -65,8 +66,8 @@ class In(ArrayOperator): @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$in":[self.left, self.right] }) diff --git a/src/monggregate/operators/array/is_array.py b/src/monggregate/operators/array/is_array.py index de42f10..23115f6 100644 --- a/src/monggregate/operators/array/is_array.py +++ b/src/monggregate/operators/array/is_array.py @@ -26,6 +26,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOnlyOperator class IsArray(ArrayOnlyOperator): @@ -34,7 +35,7 @@ class IsArray(ArrayOnlyOperator): Attributes ------------------------- - - expression : Any valid expression + - operand : Any valid expression Online MongoDB documentation ---------------------------- @@ -47,14 +48,14 @@ class IsArray(ArrayOnlyOperator): """ @property - def statement(self) -> dict: - return self.resolve({ - "$isArray":self.expression + def expression(self) -> Expression: + return self.express({ + "$isArray":self.operand }) def is_array(array:Any)->IsArray: """Returns a $isArray operator""" return IsArray( - expression = array + operand = array ) diff --git a/src/monggregate/operators/array/last.py b/src/monggregate/operators/array/last.py index 1c721bd..f210dd9 100644 --- a/src/monggregate/operators/array/last.py +++ b/src/monggregate/operators/array/last.py @@ -102,6 +102,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOnlyOperator class Last(ArrayOnlyOperator): @@ -111,7 +112,7 @@ class Last(ArrayOnlyOperator): Attributes ----------------------------- - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation ---------------------------- @@ -126,14 +127,14 @@ class Last(ArrayOnlyOperator): """ @property - def statement(self) -> dict: - return self.resolve({ - "$last":self.expression + def expression(self) -> Expression: + return self.express({ + "$last":self.operand }) def last(array:Any)->Last: """Returns a $last operator""" return Last( - expression = array + operand = array ) diff --git a/src/monggregate/operators/array/max_n.py b/src/monggregate/operators/array/max_n.py index 0680789..9154fce 100644 --- a/src/monggregate/operators/array/max_n.py +++ b/src/monggregate/operators/array/max_n.py @@ -45,7 +45,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.array.array import ArrayOperator class MaxN(ArrayOperator): @@ -55,7 +55,7 @@ class MaxN(ArrayOperator): Attributes -------------------------- - - expression, Expression : Any valid expression that resolves to an array + - operand, Any:Any valid expression that resolves to an array - limit / n , int : An expression that resolves to a positive integer. The integer specifies the number of array elements taht $maxN returns. @@ -66,22 +66,22 @@ class MaxN(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN-array-element/#mongodb-expression-exp.-maxN) """ - expression : Any = pyd.Field(alias="input") + operand : Any = pyd.Field(alias="input") limit : Any = pyd.Field(1, alias="n") @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$maxN" : { "n" : self.limit, - "input" : self.expression + "input" : self.operand } }) -def max_n(expression:Any, limit:Any=1)->MaxN: +def max_n(operand:Any, limit:Any=1)->MaxN: """Returns a $maxN operator""" return MaxN( - expression = expression, + operand=operand, limit = limit ) diff --git a/src/monggregate/operators/array/min_n.py b/src/monggregate/operators/array/min_n.py index 2496c0a..bfa4ede 100644 --- a/src/monggregate/operators/array/min_n.py +++ b/src/monggregate/operators/array/min_n.py @@ -45,7 +45,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.array.array import ArrayOperator class MinN(ArrayOperator): @@ -54,7 +54,7 @@ class MinN(ArrayOperator): Attributes -------------------------- - - expression, Expression : Any valid expression that resolves to an array + - operand, Any:Any valid expression that resolves to an array - limit / n , int : An expression that resolves to a positive integer. The integer specifies the number of array elements taht $maxN returns. @@ -65,22 +65,22 @@ class MinN(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN-array-element/#mongodb-expression-exp.-minN) """ - expression : Any = pyd.Field(alias="input") + operand : Any = pyd.Field(alias="input") limit : Any = pyd.Field(1, alias="n") @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$minN" : { "n" : self.limit, - "input" : self.expression + "input" : self.operand } }) -def min_n(expression:Any, limit:Any=1)->MinN: +def min_n(operand:Any, limit:Any=1)->MinN: """Returns a $minN operator""" return MinN( - expression = expression, + operand = operand, limit = limit ) diff --git a/src/monggregate/operators/array/size.py b/src/monggregate/operators/array/size.py index 2973a39..34d0ce0 100644 --- a/src/monggregate/operators/array/size.py +++ b/src/monggregate/operators/array/size.py @@ -28,6 +28,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOnlyOperator class Size(ArrayOnlyOperator): @@ -37,7 +38,7 @@ class Size(ArrayOnlyOperator): Attributes -------------------- - - expression : Any valid expression that resolves to an array + - operand : Any valid expression that resolves to an array Online MongoDB documentation ---------------------------- @@ -54,14 +55,14 @@ class Size(ArrayOnlyOperator): """ @property - def statement(self) -> dict: - return self.resolve({ - "$size":self.expression + def expression(self) -> Expression: + return self.express({ + "$size":self.operand }) def size(array:Any)->Size: """Returns a $size operator""" return Size( - expression = array + operand = array ) diff --git a/src/monggregate/operators/array/sort_array.py b/src/monggregate/operators/array/sort_array.py index f0aecf0..a08e562 100644 --- a/src/monggregate/operators/array/sort_array.py +++ b/src/monggregate/operators/array/sort_array.py @@ -83,7 +83,7 @@ from typing import Any, Literal -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.array.array import ArrayOperator class SortArray(ArrayOperator): @@ -93,7 +93,7 @@ class SortArray(ArrayOperator): Attributes -------------------------- - - expression, Expression : Any valid expression that resolves to an array + - operand, Any:Any valid expression that resolves to an array - by, dict[str, Literal[1, -1]] : document indicating a sort order Online MongoDB documentation @@ -119,22 +119,22 @@ class SortArray(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#mongodb-expression-exp.-sortArray) """ - expression : Any = pyd.Field(alias="input") + operand : Any = pyd.Field(alias="input") by : dict[str, Literal[1, -1]] = pyd.Field(1, alias="sort_by") @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$sortArray":{ - "input" : self.expression, + "input" : self.operand, "sortBy" : self.by } }) -def sort_array(expression:Any, sort_by:dict[str, Literal[1, -1]])->SortArray: +def sort_array(operand:Any, sort_by:dict[str, Literal[1, -1]])->SortArray: """Returns a $first operator""" return SortArray( - expression = expression, + operand = operand, sort_by = sort_by ) diff --git a/src/monggregate/operators/boolean/and_.py b/src/monggregate/operators/boolean/and_.py index f0f7d09..927d43c 100644 --- a/src/monggregate/operators/boolean/and_.py +++ b/src/monggregate/operators/boolean/and_.py @@ -50,6 +50,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.boolean.boolean import BooleanOperator class And(BooleanOperator): @@ -75,18 +76,18 @@ class And(BooleanOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/#mongodb-expression-exp.-and) """ - expressions : list[Any] + operands : list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$and" : self.expressions + def expression(self) -> Expression: + return self.express({ + "$and" : self.operands }) def and_(*args:Any)->And: """Returns a $and operator""" return And( - expressions=list(args) + operands=list(args) ) diff --git a/src/monggregate/operators/boolean/not_.py b/src/monggregate/operators/boolean/not_.py index 11d05bd..3f5e4b0 100644 --- a/src/monggregate/operators/boolean/not_.py +++ b/src/monggregate/operators/boolean/not_.py @@ -27,6 +27,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.boolean.boolean import BooleanOperator class Not(BooleanOperator): @@ -35,7 +36,7 @@ class Not(BooleanOperator): Attributes ------------------- - - expression, Expression : Any valid expression + - operand, Any:Any valid expression Online MongoDB documentation ---------------------------- @@ -51,17 +52,17 @@ class Not(BooleanOperator): """ - expression : Any + operand : Any @property - def statement(self) -> dict: - return self.resolve({ - "$not" : [self.expression] + def expression(self) -> Expression: + return self.express({ + "$not" : [self.operand] }) -def not_(expression:Any)->Not: +def not_(operand:Any)->Not: """Returns a $not operator""" return Not( - expression=expression + operand=operand ) diff --git a/src/monggregate/operators/boolean/or_.py b/src/monggregate/operators/boolean/or_.py index 971c76d..b301c6c 100644 --- a/src/monggregate/operators/boolean/or_.py +++ b/src/monggregate/operators/boolean/or_.py @@ -26,6 +26,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.boolean.boolean import BooleanOperator class Or(BooleanOperator): @@ -52,17 +53,17 @@ class Or(BooleanOperator): """ - expressions : list[Any] + operands : list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$or" : self.expressions + def expression(self) -> Expression: + return self.express({ + "$or" : self.operands }) def or_(*args:Any)->Or: """Returns a $or operator""" return Or( - expressions=list(args) + operands=list(args) ) diff --git a/src/monggregate/operators/comparison/cmp.py b/src/monggregate/operators/comparison/cmp.py index 63dbeb4..e98793a 100644 --- a/src/monggregate/operators/comparison/cmp.py +++ b/src/monggregate/operators/comparison/cmp.py @@ -28,6 +28,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class Compare(Comparator): @@ -38,8 +39,8 @@ class Compare(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -63,9 +64,9 @@ class Compare(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$cmp":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/eq.py b/src/monggregate/operators/comparison/eq.py index acc59db..45ce9fb 100644 --- a/src/monggregate/operators/comparison/eq.py +++ b/src/monggregate/operators/comparison/eq.py @@ -27,6 +27,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class Equal(Comparator): @@ -36,8 +37,8 @@ class Equal(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -61,9 +62,9 @@ class Equal(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$eq":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/gt.py b/src/monggregate/operators/comparison/gt.py index 88c388c..ea767ba 100644 --- a/src/monggregate/operators/comparison/gt.py +++ b/src/monggregate/operators/comparison/gt.py @@ -25,6 +25,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class GreatherThan(Comparator): @@ -34,8 +35,8 @@ class GreatherThan(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -57,9 +58,9 @@ class GreatherThan(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$gt":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/gte.py b/src/monggregate/operators/comparison/gte.py index 496ad2c..3d296da 100644 --- a/src/monggregate/operators/comparison/gte.py +++ b/src/monggregate/operators/comparison/gte.py @@ -23,6 +23,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class GreatherThanOrEqual(Comparator): @@ -33,8 +34,8 @@ class GreatherThanOrEqual(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -56,9 +57,9 @@ class GreatherThanOrEqual(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$gte":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/lt.py b/src/monggregate/operators/comparison/lt.py index c35f7d3..2e20d07 100644 --- a/src/monggregate/operators/comparison/lt.py +++ b/src/monggregate/operators/comparison/lt.py @@ -23,6 +23,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class LowerThan(Comparator): @@ -33,8 +34,8 @@ class LowerThan(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -56,9 +57,9 @@ class LowerThan(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$lt":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/lte.py b/src/monggregate/operators/comparison/lte.py index 78bf580..04cbe20 100644 --- a/src/monggregate/operators/comparison/lte.py +++ b/src/monggregate/operators/comparison/lte.py @@ -23,6 +23,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class LowerThanOrEqual(Comparator): @@ -33,8 +34,8 @@ class LowerThanOrEqual(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -57,9 +58,9 @@ class LowerThanOrEqual(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$lte":[self.left, self.right] }) diff --git a/src/monggregate/operators/comparison/ne.py b/src/monggregate/operators/comparison/ne.py index caae1bd..7c834a4 100644 --- a/src/monggregate/operators/comparison/ne.py +++ b/src/monggregate/operators/comparison/ne.py @@ -26,6 +26,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.comparison.comparator import Comparator class NotEqual(Comparator): @@ -35,8 +36,8 @@ class NotEqual(Comparator): Attributes ------------------- - - left, Expression : Left operand. Can be any valid expression. - - right, Expression : Right operand. Can be any valid expression. + - left, Any :Left operand. Can be any valid expression. + - right, Any :Right operand. Can be any valid expression. Online MongoDB documentation ---------------------------- @@ -58,9 +59,9 @@ class NotEqual(Comparator): """ @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "$ne":[self.left, self.right] }) diff --git a/src/monggregate/operators/conditional/cond.py b/src/monggregate/operators/conditional/cond.py index 5716578..111f811 100644 --- a/src/monggregate/operators/conditional/cond.py +++ b/src/monggregate/operators/conditional/cond.py @@ -32,7 +32,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.conditional.conditional import ConditionalOperator class Cond(ConditionalOperator): @@ -79,7 +79,7 @@ class Cond(ConditionalOperator): """ # Syntax 2 - expression : Any|None + operand : Any|None # NOTE: below trailing underscores and aliases might not be needed as true/false are not protected in python # (but True and False are) true_ : Any|None = pyd.Field(alias="true") @@ -139,8 +139,8 @@ def _validate_else(cls, v:Any, values:dict)->Any: @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$cond" : { "if" : self.if_, "then" : self.then_, diff --git a/src/monggregate/operators/conditional/if_null.py b/src/monggregate/operators/conditional/if_null.py index e7c10f6..1a6dc6e 100644 --- a/src/monggregate/operators/conditional/if_null.py +++ b/src/monggregate/operators/conditional/if_null.py @@ -44,6 +44,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.conditional.conditional import ConditionalOperator class IfNull(ConditionalOperator): @@ -72,19 +73,19 @@ class IfNull(ConditionalOperator): """ - expression : Any # NOTE : Maybe diverge from Mongo and do not allow multiple expressions + operand : Any # NOTE : Maybe diverge from Mongo and do not allow multiple expressions output : Any @property - def statement(self) -> dict: - return self.resolve({ - "$ifNull" : [self.expression, self.output] + def expression(self) -> Expression: + return self.express({ + "$ifNull" : [self.operand, self.output] }) -def if_null(expression:Any, output:Any)->IfNull: +def if_null(operand:Any, output:Any)->IfNull: """Returns an $if_null operator""" return IfNull( - expression=expression, + operand=operand, output=output ) diff --git a/src/monggregate/operators/conditional/switch.py b/src/monggregate/operators/conditional/switch.py index b1b478a..9b98923 100644 --- a/src/monggregate/operators/conditional/switch.py +++ b/src/monggregate/operators/conditional/switch.py @@ -52,6 +52,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.conditional.conditional import ConditionalOperator # TODO : Define branch @@ -100,8 +101,8 @@ class Switch(ConditionalOperator): default : Any|None @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$switch" : { "branches" : self.branches, "default" : self.default diff --git a/src/monggregate/operators/date/millisecond.py b/src/monggregate/operators/date/millisecond.py index 9be008e..b780b35 100644 --- a/src/monggregate/operators/date/millisecond.py +++ b/src/monggregate/operators/date/millisecond.py @@ -119,6 +119,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.date.date import DateOperator class Millisecond(DateOperator): @@ -169,28 +170,28 @@ class Millisecond(DateOperator): """ - expression : Any + operand : Any timezone : Any | None @property - def statement(self) -> dict: + def expression(self) -> Expression: if self.timezone: inner = { - "date" : self.expression, + "date" : self.operand, "timezone" : self.timezone } else: - inner = self.expression + inner = self.operand - return self.resolve({ + return self.express({ "$millisecond" : inner }) -def millisecond(expression:Any, timezone:Any)->Millisecond: +def millisecond(operand:Any, timezone:Any)->Millisecond: """Returns an $millisecond operator""" return Millisecond( - expression=expression, + operand=operand, timezone=timezone ) \ No newline at end of file diff --git a/src/monggregate/operators/objects/merge_objects.py b/src/monggregate/operators/objects/merge_objects.py index 3ce9c0b..704ebc6 100644 --- a/src/monggregate/operators/objects/merge_objects.py +++ b/src/monggregate/operators/objects/merge_objects.py @@ -42,6 +42,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.array.array import ArrayOperator class MergeObjects(ArrayOperator): @@ -51,7 +52,7 @@ class MergeObjects(ArrayOperator): Attribute --------------------- - - expression, Expression : Any valid expression or list of expression + - operand, Any:Any valid expression or list of expression Online MongoDB documentation ---------------------------- @@ -66,16 +67,16 @@ class MergeObjects(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#mongodb-expression-exp.-mergeObjects) """ - expression : Any | list[Any] + operand : Any | list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$mergeObjects" : self.expression + def expression(self) -> Expression: + return self.express({ + "$mergeObjects" : self.operand }) -def merge_objects(expression:Any)->MergeObjects: +def merge_objects(operand:Any)->MergeObjects: """Returns a $mergeObjects operator""" - return MergeObjects(expression=expression) + return MergeObjects(operand=operand) diff --git a/src/monggregate/operators/objects/object_to_array.py b/src/monggregate/operators/objects/object_to_array.py index b85ded6..f82dde3 100644 --- a/src/monggregate/operators/objects/object_to_array.py +++ b/src/monggregate/operators/objects/object_to_array.py @@ -27,9 +27,11 @@ """ -from monggregate.operators.array.array import ArrayOperator from typing import Any +from monggregate.base import Expression +from monggregate.operators.array.array import ArrayOperator + class ObjectToArray(ArrayOperator): """ Abstraction of MongoDB $arrayToObject operator which converts a @@ -37,7 +39,7 @@ class ObjectToArray(ArrayOperator): Attributes ------------------- - - expression, Expression : Any valid expression that resolves to an object + - operand, Any:Any valid expression that resolves to an object Online MongoDB documentation ---------------------------- @@ -60,15 +62,15 @@ class ObjectToArray(ArrayOperator): [Source](https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/#mongodb-expression-exp.-objectToArray) """ - expression : Any + operand : Any @property - def statement(self) -> dict: - return self.resolve({ - "$objectToArray" : self.expression + def expression(self) -> Expression: + return self.express({ + "$objectToArray" : self.operand }) -def object_to_array(expression:Any)->ObjectToArray: +def object_to_array(operand:Any)->ObjectToArray: """Returns a $objectToArray operator""" - return ObjectToArray(expression=expression) + return ObjectToArray(operand=operand) diff --git a/src/monggregate/operators/strings/concat.py b/src/monggregate/operators/strings/concat.py index 04d1371..5e3e99b 100644 --- a/src/monggregate/operators/strings/concat.py +++ b/src/monggregate/operators/strings/concat.py @@ -22,6 +22,7 @@ """ from typing import Any +from monggregate.base import Expression from monggregate.operators.strings.string import StringOperator class Concat(StringOperator): @@ -49,17 +50,17 @@ class Concat(StringOperator): """ - expressions : list[Any] + operands : list[Any] @property - def statement(self) -> dict: - return self.resolve({ - "$concat" : self.expressions + def expression(self) -> Expression: + return self.express({ + "$concat" : self.operands }) def concat(*args:Any)->Concat: """Returns an $concat operator""" return Concat( - expressions=list(args) + operands=list(args) ) \ No newline at end of file diff --git a/src/monggregate/operators/strings/date_from_string.py b/src/monggregate/operators/strings/date_from_string.py index 9208249..7e919ac 100644 --- a/src/monggregate/operators/strings/date_from_string.py +++ b/src/monggregate/operators/strings/date_from_string.py @@ -76,7 +76,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.strings.string import StringOperator class DateFromString(StringOperator): @@ -118,8 +118,8 @@ class DateFromString(StringOperator): on_null : Any @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$dateFromString" : self.dict(by_alias=True, exclude_none=True) }) diff --git a/src/monggregate/operators/strings/date_to_string.py b/src/monggregate/operators/strings/date_to_string.py index 76b77a3..948674b 100644 --- a/src/monggregate/operators/strings/date_to_string.py +++ b/src/monggregate/operators/strings/date_to_string.py @@ -83,7 +83,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.operators.strings.string import StringOperator class DateToString(StringOperator): @@ -126,8 +126,8 @@ class DateToString(StringOperator): on_null : Any @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$dateToString" : self.dict(by_alias=True, exclude_none=True) }) diff --git a/src/monggregate/operators/type_/type_.py b/src/monggregate/operators/type_/type_.py index d9526e4..c293c29 100644 --- a/src/monggregate/operators/type_/type_.py +++ b/src/monggregate/operators/type_/type_.py @@ -87,7 +87,7 @@ """ from typing import Any -from monggregate.base import BaseModel +from monggregate.base import BaseModel, Expression class Type_(BaseModel): """ @@ -111,19 +111,19 @@ class Type_(BaseModel): [Source](https://docs.mongodb.com/manual/reference/operator/aggregation/type/#mongodb-expression-exp.-type) """ - expression:Any + operand : Any @property - def statement(self)->dict: + def expression(self)->Expression: - return self.resolve({ - "$type":self.expression + return self.express({ + "$type":self.operand }) -def type_(expression:Any)->Type_: +def type_(operand:Any)->Type_: """Returns a $type operator""" return Type_( - expression=expression + operand = operand ) diff --git a/src/monggregate/pipeline.py b/src/monggregate/pipeline.py index 539989f..ea42a80 100644 --- a/src/monggregate/pipeline.py +++ b/src/monggregate/pipeline.py @@ -5,9 +5,8 @@ from typing_extensions import Self -from monggregate import _run -from monggregate.base import BaseModel +from monggregate.base import BaseModel, Expression from monggregate.stages import ( AnyStage, BucketAuto, @@ -22,6 +21,7 @@ Project, ReplaceRoot, Sample, + Stage, Search, SearchMeta, SearchStageMap, @@ -46,23 +46,16 @@ class Pipeline(BaseModel): # pylint: disable=too-many-public-methods """ - MongoDB aggregation pipeline abstraction + MongoDB aggregation pipeline abstraction. Attributes ----------------------- - - collection, str : reference collection for the pipeline. - This is the collection where the aggregation will be done. - However some stages in the pipeline might work with additional - collections (e.g. lookup stage) + stages : list[Stage] + the list of Stages that the pipeline is made of. + Similarly to the pipeline itself. This package constructs + abstraction for MongoDB aggregation framework pipeline stages. - - stages, list[Stage] : the list of Stages that the pipeline is made of. - Similarly to the pipeline itself. This package constructs - abstraction for MongoDB aggregation framework pipeline stages. - - on_call, OnCallEnum : pipeline instances are callable. This defines the behavior of the instance - when called. See OnCallEnum above. Defaults to export - - - -db, Database : pymongo database instance. Can be optionally provided to make a pipeline instance self sufficient Usage ----------------------- @@ -83,39 +76,29 @@ class Pipeline(BaseModel): # pylint: disable=too-many-public-methods >>> db["listingsAndReviews"].aggregate(pipeline=pipeline()) # pipeline() here is actually equivalent to pipeline.export() - Alternatively, your pipeline can be self sufficient and executes itself directly using the following approach: - - >>> pipeline = Pipeline( - _db=db, - collection="listingsAndReviews", - on_call="run" - ) - - >>> pipeline.match( - query = { "room_type": "Entire home/apt"} - ).sort_by_count( - by = "bed_type" - ) - - >>> pipeline() # pipeline() there is actually equivalent to pipeline.run() - """ - # instance of pymongo or motor database to allow pipeline to directly runnable - _db : _run.Database | _run.AsyncIOMotorDatabase | _run.MotorDatabase | None = None - # name of the collection to run the pipeline on - collection : str | None =None - # list of stages that compose the pipeline - stages : list[AnyStage] = [] + + stages : list[AnyStage|Expression] = [] @property - def statement(self)->list[dict]: + def expression(self)->list[Expression]: """Returns the pipeline statement""" - return [stage.statement for stage in self.stages] + # TODO : Add test on this case + # https://github.com/VianneyMI/monggregate/issues/106 + stages_expressions = [] + + for stage in self.stages: + if isinstance(stage, Stage): + stages_expressions.append(stage.expression) + else: + stages_expressions.append(stage) + + return stages_expressions @@ -123,55 +106,6 @@ def statement(self)->list[dict]: # ------------------------------------------------ # Pipeline Internal Methods #------------------------------------------------- - if _run.MOTOR: - async def __call__(self)->list[dict[str, Any]]: - """Makes a pipeline instance callable and executes the entire pipeline when called""" - - return await self.run() - - - async def run(self)->list[dict[str, Any]]: - """Executes the entire pipeline""" - - if self._db is None: - raise ValueError("db is not defined. Please indicate which database to run the pipeline on") - - if not self.collection: - raise ValueError("collection is not defined. Please indicate which collection to run the pipeline on") - - # TODO : Allow to yield results as they come in - cursor = self._db[self.collection].aggregate(pipeline=self.export()) - array = await cursor.to_list(length=None) - - return array - - elif _run.PYMONGO: - def __call__(self)->list[dict[str, Any]]: - """Makes a pipeline instance callable and executes the entire pipeline when called""" - - return self.run() - - - def run(self)->list[dict[str, Any]]: - """Executes the entire pipeline""" - - if self._db is None: - raise ValueError("_db is not defined. Please indicate which database to run the pipeline on") - - if not self.collection: - raise ValueError("collection is not defined. Please indicate which collection to run the pipeline on") - - stages = self.export() - array = list(self._db[self.collection].aggregate(pipeline=stages)) - return array - else: - def __call__(self)->None: - self.run() - - def run(self)->None: - raise NotImplementedError("No database driver found. Please install pymongo or motor") - - def export(self)->list[dict]: """ Exports current pipeline to pymongo format. @@ -181,17 +115,8 @@ def export(self)->list[dict]: """ - stages = [] - for stage in self.stages: - stages.append(stage()) - - return stages - + return self.expression - def to_statements(self)->list[dict]: - """Alias for export method""" - - return self.export() # -------------------------------------------------- # Pipeline List Methods @@ -202,8 +127,6 @@ def __add__(self, other:Self)->Self: raise TypeError(f"unsupported operand type(s) for +: 'Pipeline' and '{type(other)}'") return Pipeline( - _db=self._db, - collection=self.collection, stages=self.stages + other.stages ) @@ -322,7 +245,7 @@ def bucket(self, *, boundaries:list, by:Any=None, group_by:Any=None, default:Any ) return self - def bucket_auto(self, *, by:Any=None, group_by:Any=None, buckets:int, output:dict=None, granularity:GranularityEnum|None=None)->Self: + def bucket_auto(self, *, by:Any=None, group_by:Any=None, buckets:int, output:dict|None=None, granularity:GranularityEnum|None=None)->Self: """ Adds a bucket_auto stage to the current pipeline. This stage aggregates documents into buckets automatically computed to statisfy the number of buckets desired @@ -513,8 +436,8 @@ def lookup(self, *, \ on:str|None=None, left_on:str|None=None, local_field:str|None=None, - right_on:str=None, - foreign_field:str=None)->Self: + right_on:str|None=None, + foreign_field:str|None=None)->Self: """ Adds a lookup stage to the current pipeline. Performs a left outer join to a collection in the same database to filter in documents from the "joined" collection for processing. The @@ -617,8 +540,6 @@ def join( if how == "left": self.__left_join(right=other, on=on, left_on=left_on, right_on=right_on) - elif how == "right": - self.__right_join(left=other, on=on, left_on=left_on, right_on=right_on) elif how == "inner": self.__inner_join(right=other, on=on, left_on=left_on, right_on=right_on) @@ -645,8 +566,8 @@ def __join_common(self, right:str, on:str|None, left_on:str|None, right_on:str|N self.stages.append( ReplaceRoot( document=MergeObjects( - expression=[ROOT, "$"+join_field] - ).statement + operand=[ROOT, "$"+join_field] + ).expression ) ) self.stages.append( @@ -659,17 +580,6 @@ def __left_join(self, right:str, on:str|None, left_on:str|None, right_on:str|Non self.__join_common(right=right, on=on, left_on=left_on, right_on=right_on) - def __right_join(self, left:str, on:str|None, left_on:str, right_on:str|None) -> None: - """Implements SQL right join""" - - warn("This stage will override the collection attribute of the pipeline with left and may lead to strange behaviors if not anticipated.") - # TODO : Warns that this will override current pipeline collection by left - # TODO : Append collection name in foreign collection documents field names to avoid collision and override of field when promoting sub-documents - # Ex : {"a":1, "b":2, "c":{"a":3, "d":0}} after promoting "c" would become {"a":3, "b":2, "d":0} and we want to prevent this - - right = self.collection - self.collection = left - self.__join_common(right=right, on=on, left_on=left_on, right_on=right_on) def __inner_join(self, right:str, on:str|None, left_on:str|None, right_on:str|None) -> None: """Implements SQL inner join""" @@ -684,7 +594,7 @@ def __inner_join(self, right:str, on:str|None, left_on:str|None, right_on:str|No self.stages.insert(-3, filter_no_match) - def match(self, query:dict={}, expression:Any=None, **kwargs:Any)->Self: + def match(self, query:dict={}, expr:Expression=None, **kwargs:Any)->Self: """ Adds a match stage to the current pipeline. Filters the documents to pass only the documents that match the specified condition(s) to the next pipeline stage. @@ -693,7 +603,7 @@ def match(self, query:dict={}, expression:Any=None, **kwargs:Any)->Self: ------------------- - query, dict : a simple MQL query use to filter the documents. - - expression, Expression : an aggregation expression used to filter the documents + - operand, Any:an aggregation expression used to filter the documents NOTE : Use query if you're using a MQL query and expression if you're using aggregation expressions. @@ -711,10 +621,7 @@ def match(self, query:dict={}, expression:Any=None, **kwargs:Any)->Self: query = query | kwargs self.stages.append( - Match( - query=query, - expression=expression - ) + Match(query=query, expr=expr) ) return self @@ -1425,7 +1332,7 @@ def unwind(self, \ return self - def unset(self, field:str=None, fields:list[str]|None=None)->Self: + def unset(self, field:str|None=None, fields:list[str]|None=None)->Self: """ Adds an unset stage to the current pipeline. Removes/excludes fields from documents. diff --git a/src/monggregate/search/collectors/facet.py b/src/monggregate/search/collectors/facet.py index bd41845..f096ebb 100644 --- a/src/monggregate/search/collectors/facet.py +++ b/src/monggregate/search/collectors/facet.py @@ -169,7 +169,7 @@ from typing import Any, Callable, Literal from typing_extensions import Self -from monggregate.base import BaseModel, pyd +from monggregate.base import BaseModel, pyd, Expression from monggregate.fields import FieldName from monggregate.search.collectors.collector import SearchCollector from monggregate.search.operators import( @@ -274,9 +274,9 @@ class StringFacet(FacetDefinition): num_buckets : int = 10 @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({self.name : self.dict(by_alias=True, exclude={"name"})}) + return self.express({self.name : self.dict(by_alias=True, exclude={"name"})}) class NumericFacet(FacetDefinition): @@ -299,9 +299,9 @@ class NumericFacet(FacetDefinition): default : str|None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({self.name : self.dict(by_alias=True, exclude={"name"})}) + return self.express({self.name : self.dict(by_alias=True, exclude={"name"})}) class DateFacet(FacetDefinition): @@ -321,9 +321,9 @@ class DateFacet(FacetDefinition): default : str|None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({self.name : self.dict(by_alias=True, exclude={"name"})}) + return self.express({self.name : self.dict(by_alias=True, exclude={"name"})}) AnyFacet = StringFacet|NumericFacet|DateFacet Facets = list[AnyFacet] @@ -371,7 +371,7 @@ def validate_facets(cls, facets:Facets)->Facets: @property - def statement(self) -> dict: + def expression(self) -> Expression: if not self.facets: raise ValueError("No facets were defined") @@ -384,12 +384,12 @@ def statement(self) -> dict: } for facet in self.facets: - _statement["facet"]["facets"].update(facet.statement) + _statement["facet"]["facets"].update(facet.expression) if self.operator: - _statement["facet"].update({"operator":self.operator.statement}) + _statement["facet"].update({"operator":self.operator.expression}) - return self.resolve(_statement) + return self.express(_statement) #--------------------------------------------------------- # Constructors diff --git a/src/monggregate/search/commons/count.py b/src/monggregate/search/commons/count.py index afb5cdb..9d3a423 100644 --- a/src/monggregate/search/commons/count.py +++ b/src/monggregate/search/commons/count.py @@ -6,7 +6,7 @@ from typing import Literal -from monggregate.base import BaseModel, pyd +from monggregate.base import BaseModel, pyd, Expression class CountOptions(BaseModel): """Class representing the count options in a $search query. @@ -41,9 +41,9 @@ def validate_type(cls, value:str)->str: return value @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve(self.dict(by_alias=True)) + return self.express(self.dict(by_alias=True)) class CountResults(BaseModel): """Class defining the count results.""" @@ -52,6 +52,6 @@ class CountResults(BaseModel): total : int|None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve(self.dict(by_alias=True)) \ No newline at end of file + return self.express(self.dict(by_alias=True)) \ No newline at end of file diff --git a/src/monggregate/search/commons/fuzzy.py b/src/monggregate/search/commons/fuzzy.py index 7d52e67..47039f6 100644 --- a/src/monggregate/search/commons/fuzzy.py +++ b/src/monggregate/search/commons/fuzzy.py @@ -1,6 +1,6 @@ """Module defining an interface to define the fuzzy search parameters.""" -from monggregate.base import BaseModel, pyd +from monggregate.base import BaseModel, pyd, Expression class FuzzyOptions(BaseModel): """Class defining the fuzzy search parameters.""" @@ -10,7 +10,7 @@ class FuzzyOptions(BaseModel): prefix_length : int = pyd.Field(0, alias="prefixLength") @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve(self.dict(by_alias=True)) + return self.express(self.dict(by_alias=True)) \ No newline at end of file diff --git a/src/monggregate/search/commons/highlight.py b/src/monggregate/search/commons/highlight.py index 1c26d9c..dfb5ab8 100644 --- a/src/monggregate/search/commons/highlight.py +++ b/src/monggregate/search/commons/highlight.py @@ -8,6 +8,7 @@ from monggregate.base import BaseModel, pyd +# TODO : Check if those are missing an expression property class HighlightOptions(BaseModel): """Class defining the highlighting parameters.""" diff --git a/src/monggregate/search/operators/autocomplete.py b/src/monggregate/search/operators/autocomplete.py index c28187f..1e9e592 100644 --- a/src/monggregate/search/operators/autocomplete.py +++ b/src/monggregate/search/operators/autocomplete.py @@ -129,6 +129,7 @@ """ +from monggregate.base import Expression from monggregate.utils import StrEnum from monggregate.search.operators.operator import SearchOperator from monggregate.search.commons import FuzzyOptions @@ -171,9 +172,9 @@ class Autocomplete(SearchOperator): score : dict|None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "autocomplete":{ "query": self.query, "path": self.path, diff --git a/src/monggregate/search/operators/compound.py b/src/monggregate/search/operators/compound.py index 802ea47..514acb0 100644 --- a/src/monggregate/search/operators/compound.py +++ b/src/monggregate/search/operators/compound.py @@ -61,7 +61,7 @@ from typing_extensions import Self -from monggregate.base import pyd +from monggregate.base import Expression from monggregate.search.operators.operator import SearchOperator, OperatorLiteral from monggregate.search.operators.clause import ( Clause, @@ -111,7 +111,7 @@ class Compound(SearchOperator): minimum_should_match : int = 0 @property - def statement(self) -> dict: + def expression(self) -> Expression: clauses = {} if self.must: @@ -124,7 +124,7 @@ def statement(self) -> dict: if self.filter: clauses["filter"] = self.filter - return self.resolve({ + return self.express({ "compound":clauses }) @@ -217,7 +217,7 @@ def equals( path=path, value=value, score=score - ).statement + ).expression self._register_clause(type, _equals) diff --git a/src/monggregate/search/operators/equals.py b/src/monggregate/search/operators/equals.py index 7b1af16..6afee42 100644 --- a/src/monggregate/search/operators/equals.py +++ b/src/monggregate/search/operators/equals.py @@ -69,6 +69,7 @@ """ from datetime import datetime +from monggregate.base import Expression from monggregate.search.operators.operator import SearchOperator class Equals(SearchOperator, smart_union=True): @@ -96,9 +97,9 @@ class Equals(SearchOperator, smart_union=True): score : dict|None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "equals":{ "path": self.path, "value": self.value, diff --git a/src/monggregate/search/operators/exists.py b/src/monggregate/search/operators/exists.py index 2b075ff..09bbedb 100644 --- a/src/monggregate/search/operators/exists.py +++ b/src/monggregate/search/operators/exists.py @@ -28,6 +28,7 @@ """ +from monggregate.base import Expression from monggregate.search.operators.operator import SearchOperator class Exists(SearchOperator): @@ -51,9 +52,9 @@ class Exists(SearchOperator): @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "exists" : { "path":self.path } diff --git a/src/monggregate/search/operators/more_like_this.py b/src/monggregate/search/operators/more_like_this.py index fae3eef..14c48b8 100644 --- a/src/monggregate/search/operators/more_like_this.py +++ b/src/monggregate/search/operators/more_like_this.py @@ -83,8 +83,8 @@ You can't use the moreLikeThis operator inside the embeddedDocument operator to query documents in an array. """ - -from monggregate.base import pyd +from typing import Any +from monggregate.base import pyd, Expression from monggregate.search.operators.operator import SearchOperator @@ -105,7 +105,7 @@ class MoreLikeThis(SearchOperator): like : dict | list[dict] @pyd.validator("like", pre=True, always=True) - def validate_like(cls, v): + def validate_like(cls, v:dict[str, Any])->dict[str, Any]|list[dict[str, Any]]: if isinstance(v, list): if len(v)==0: raise ValueError("The 'like' field must be a non-empty list of BSON documents.") @@ -113,9 +113,9 @@ def validate_like(cls, v): return v @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "moreLikeThis" : { "like" : self.like } diff --git a/src/monggregate/search/operators/range.py b/src/monggregate/search/operators/range.py index 3ec8573..c50644b 100644 --- a/src/monggregate/search/operators/range.py +++ b/src/monggregate/search/operators/range.py @@ -68,7 +68,7 @@ from datetime import datetime -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.search.operators.operator import SearchOperator class Range(SearchOperator, smart_union=True): @@ -97,20 +97,20 @@ class Range(SearchOperator, smart_union=True): score : dict|None @pyd.validator("gte", pre=True, always=True) - def at_least_one_lower(cls, value, values): + def at_least_one_lower(cls, value:int | float | datetime | None, values:dict)->int | float | datetime | None: if value is None and values.get("gt") is None: raise ValueError("at least one of gte or gt must be specified") return value @pyd.validator("lte", pre=True, always=True) - def at_least_one_upper(cls, value, values): + def at_least_one_upper(cls, value:int | float | datetime | None, values:dict)->int | float | datetime | None: if value is None and values.get("lt") is None: raise ValueError("at least one of lte or lt must be specified") return value @property - def statement(self) -> dict: + def expression(self) -> Expression: params = { "path": self.path, @@ -127,5 +127,5 @@ def statement(self) -> dict: else: params["lte"] = self.lte - return self.resolve({"range":params}) + return self.express({"range":params}) \ No newline at end of file diff --git a/src/monggregate/search/operators/regex.py b/src/monggregate/search/operators/regex.py index 00bd4aa..c2abd1f 100644 --- a/src/monggregate/search/operators/regex.py +++ b/src/monggregate/search/operators/regex.py @@ -53,6 +53,7 @@ """ +from monggregate.base import Expression from monggregate.search.operators.operator import SearchOperator class Regex(SearchOperator): @@ -81,9 +82,9 @@ class Regex(SearchOperator): score : dict | None = None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "regex":{ "query": self.query, "path": self.path, diff --git a/src/monggregate/search/operators/text.py b/src/monggregate/search/operators/text.py index e9d3c0e..38312dc 100644 --- a/src/monggregate/search/operators/text.py +++ b/src/monggregate/search/operators/text.py @@ -30,7 +30,7 @@ """ -from monggregate.base import pyd +from monggregate.base import Expression from monggregate.search.operators.operator import SearchOperator from monggregate.search.commons.fuzzy import FuzzyOptions @@ -72,9 +72,9 @@ class Text(SearchOperator): synonyms : str | None = None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "text" : self.dict(exclude_none=True, by_alias=True) }) \ No newline at end of file diff --git a/src/monggregate/search/operators/wildcard.py b/src/monggregate/search/operators/wildcard.py index 21c9537..7c9bf14 100644 --- a/src/monggregate/search/operators/wildcard.py +++ b/src/monggregate/search/operators/wildcard.py @@ -65,7 +65,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.search.operators.operator import SearchOperator class Wildcard(SearchOperator): @@ -92,8 +92,8 @@ class Wildcard(SearchOperator): score : dict | None = None @property - def statement(self) -> dict: + def expression(self) -> Expression: - return self.resolve({ + return self.express({ "wildcard":self.dict(exclude_none=True, by_alias=True) }) \ No newline at end of file diff --git a/src/monggregate/stages/bucket.py b/src/monggregate/stages/bucket.py index 6ed453c..717314f 100644 --- a/src/monggregate/stages/bucket.py +++ b/src/monggregate/stages/bucket.py @@ -51,7 +51,7 @@ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.fields import FieldName @@ -116,11 +116,11 @@ class Bucket(Stage): _validate_by = pyd.validator("by", pre=True, always=True, allow_reuse=True)(validate_field_path) # re-used pyd.validators @property - def statement(self) -> dict: + def expression(self) -> Expression: # Generates statement #-------------------------------------- - return self.resolve({ + return self.express({ "$bucket" : { "groupBy" : self.by, "boundaries" :self.boundaries, diff --git a/src/monggregate/stages/bucket_auto.py b/src/monggregate/stages/bucket_auto.py index 83f9ce8..656e665 100644 --- a/src/monggregate/stages/bucket_auto.py +++ b/src/monggregate/stages/bucket_auto.py @@ -81,7 +81,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.fields import FieldName from monggregate.operators.accumulators.accumulator import AccumulatorExpression @@ -164,11 +164,11 @@ class BucketAuto(Stage): # Output #----------------------------------------------------------------------------- @property - def statement(self) -> dict: + def expression(self) -> Expression: # NOTE : maybe it would be better to use _to_unique_list here # or to further validate by. - return self.resolve({ + return self.express({ "$bucketAuto" : { "groupBy" : self.by, "buckets" : self.buckets, diff --git a/src/monggregate/stages/count.py b/src/monggregate/stages/count.py index 89dcc2c..5eb68b5 100644 --- a/src/monggregate/stages/count.py +++ b/src/monggregate/stages/count.py @@ -34,6 +34,7 @@ """ +from monggregate.base import Expression from monggregate.stages.stage import Stage from monggregate.fields import FieldName @@ -63,7 +64,7 @@ class Count(Stage): name: FieldName @property - def statement(self) -> dict: - return self.resolve({ + def expression(self) -> Expression: + return self.express({ "$count": self.name }) diff --git a/src/monggregate/stages/group.py b/src/monggregate/stages/group.py index 4c51ad4..e812b63 100644 --- a/src/monggregate/stages/group.py +++ b/src/monggregate/stages/group.py @@ -64,7 +64,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import validate_field_path, validate_field_paths @@ -117,10 +117,10 @@ def validate_query(cls, query:dict, values:dict[str,Any]) -> dict: return query @property - def statement(self) -> dict[str, dict]: + def expression(self) -> Expression: """Generates set stage statement from arguments""" - return self.resolve({ + return self.express({ "$group":self.query }) diff --git a/src/monggregate/stages/limit.py b/src/monggregate/stages/limit.py index 047c713..218984b 100644 --- a/src/monggregate/stages/limit.py +++ b/src/monggregate/stages/limit.py @@ -51,7 +51,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage class Limit(Stage): @@ -79,8 +79,8 @@ class Limit(Stage): value : int = pyd.Field(gt=0) @property - def statement(self)->dict: + def expression(self)->Expression: - return self.resolve({ + return self.express({ "$limit" : self.value }) diff --git a/src/monggregate/stages/lookup.py b/src/monggregate/stages/lookup.py index ac88c9a..8f99a72 100644 --- a/src/monggregate/stages/lookup.py +++ b/src/monggregate/stages/lookup.py @@ -245,7 +245,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import StrEnum @@ -376,7 +376,7 @@ def set_type(cls, value:str, values:dict)->str: return type_ @property - def statement(self)->dict: + def expression(self)->Expression: """Generates statement from attributes""" @@ -412,4 +412,4 @@ def statement(self)->dict: } } - return self.resolve(statement) + return self.express(statement) diff --git a/src/monggregate/stages/match.py b/src/monggregate/stages/match.py index a35fe51..48f5bbe 100644 --- a/src/monggregate/stages/match.py +++ b/src/monggregate/stages/match.py @@ -49,10 +49,10 @@ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.operators.operator import Operator -from monggregate.expressions import Expression +#from monggregate.expressions import Expression class Match(Stage): """ @@ -62,7 +62,7 @@ class Match(Stage): ----------- - query, dict : a simple MQL query use to filter the documents. - - expression, Expression : an aggregation expression used to filter the documents + - operand, Any:an aggregation expression used to filter the documents NOTE : Use query if you're using a MQL query and expression if you're using aggregation expressions. @@ -79,27 +79,29 @@ class Match(Stage): """ query : dict = {} #| None - expression : Any | None = None + expr : Expression | None = None - @pyd.validator("expression", pre=True, always=True) - def validate_expression(cls, expression)-> Any: + @pyd.validator("expr", pre=True, always=True) + def validate_operand(cls, expr:Any)-> Any: - c1 = isinstance(expression, dict) # expression is "expressed/resolved" already - c2 = isinstance(expression, Expression) # expression is an expression object - c3 = isinstance(expression, Operator) # expression is an operator object + c1 = isinstance(expr, dict) # expression is "expressed/resolved" already + c2 = isinstance(expr, Operator) # expression is an operator object - if expression and not (c1 or c2 or c3): + if expr and not (c1 or c2 ): raise ValueError("The expression argument must be a valid expression, operator or a dict.") - return expression + if isinstance(expr, dict) and "$expr" not in expr: + expr = {"$expr":expr} + + return expr @property - def statement(self) -> dict: + def expression(self) -> Expression: - if self.expression: - _statement = self.resolve({"$match":{"$expr":self.expression}}) + if self.expr: + _statement = self.express({"$match":{"$expr":self.expr}}) else: - _statement = self.resolve({"$match":self.query}) + _statement = self.express({"$match":self.query}) return _statement diff --git a/src/monggregate/stages/out.py b/src/monggregate/stages/out.py index 477ea17..5080264 100644 --- a/src/monggregate/stages/out.py +++ b/src/monggregate/stages/out.py @@ -112,7 +112,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage class Out(Stage): @@ -140,7 +140,7 @@ class Out(Stage): collection : str = pyd.Field(...,alias="coll") @property - def statement(self)->dict: + def expression(self)->Expression: """Generates statement from attributes""" @@ -158,4 +158,4 @@ def statement(self)->dict: "$out" : self.collection } - return self.resolve(statement) + return self.express(statement) diff --git a/src/monggregate/stages/project.py b/src/monggregate/stages/project.py index 005bb69..6d15f8a 100644 --- a/src/monggregate/stages/project.py +++ b/src/monggregate/stages/project.py @@ -125,7 +125,7 @@ # NOTE : Would be nice and useful to have something keywords arguments based to generate the projection # (on top[on the side] of the below) -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import to_unique_list @@ -257,7 +257,7 @@ def _to_projection(projection:dict, projection_args:list[str]|dict, include:bool @property - def statement(self)->dict[str, dict]: + def expression(self)->Expression: """Generates statement from other attributes""" - return self.resolve({"$project":self.projection}) + return self.express({"$project":self.projection}) diff --git a/src/monggregate/stages/replace_root.py b/src/monggregate/stages/replace_root.py index 3b70356..f87bae2 100644 --- a/src/monggregate/stages/replace_root.py +++ b/src/monggregate/stages/replace_root.py @@ -65,7 +65,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import validate_field_path @@ -103,7 +103,7 @@ class ReplaceRoot(Stage): _validates_path_to_new_root = pyd.validator("path_to_new_root", allow_reuse=True, pre=True, always=True)(validate_field_path) @property - def statement(self)->dict: + def expression(self)->Expression: """Generate statements from argument""" if self.path_to_new_root: @@ -112,4 +112,4 @@ def statement(self)->dict: expression = self.document - return self.resolve({"$replaceRoot":{"newRoot":expression}}) + return self.express({"$replaceRoot":{"newRoot":expression}}) diff --git a/src/monggregate/stages/sample.py b/src/monggregate/stages/sample.py index e2fdb5f..a0d10ad 100644 --- a/src/monggregate/stages/sample.py +++ b/src/monggregate/stages/sample.py @@ -49,7 +49,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage class Sample(Stage): @@ -72,10 +72,10 @@ class Sample(Stage): value : int = pyd.Field(10, gt=0) @property - def statement(self)->dict: + def expression(self)->Expression: """Generate statement from arguments""" - return self.resolve({ + return self.express({ "$sample" : { "size" : self.value } diff --git a/src/monggregate/stages/search/base.py b/src/monggregate/stages/search/base.py index 68572eb..be34d3d 100644 --- a/src/monggregate/stages/search/base.py +++ b/src/monggregate/stages/search/base.py @@ -9,7 +9,7 @@ from typing import Self except ImportError: from typing_extensions import Self -from monggregate.base import pyd, BaseModel +from monggregate.base import pyd, BaseModel, Expression from monggregate.stages.stage import Stage from monggregate.search.collectors import Facet from monggregate.search.operators import ( @@ -63,7 +63,7 @@ class SearchConfig(BaseModel): score_details : bool = False @property - def statement(self): + def expression(self)->Expression: """Returns the statement of the stage""" raise NotImplementedError("statement property must be implemented in subclasses") @@ -141,7 +141,7 @@ def validate_operator(cls, value:dict, values:dict)->dict|None: return value @property - def statement(self): + def expression(self)->Expression: """Returns the statement of the stage""" raise NotImplementedError("statement property must be implemented in subclasses") diff --git a/src/monggregate/stages/search/search.py b/src/monggregate/stages/search/search.py index bdee47c..c0e31b3 100644 --- a/src/monggregate/stages/search/search.py +++ b/src/monggregate/stages/search/search.py @@ -72,6 +72,8 @@ from typing import Self except ImportError: from typing_extensions import Self + +from monggregate.base import Expression from monggregate.stages.search.base import SearchBase @@ -116,7 +118,7 @@ class Search(SearchBase): """ @property - def statement(self) -> dict[str, dict]: + def expression(self) -> Expression: config = { "index":self.index, @@ -128,13 +130,13 @@ def statement(self) -> dict[str, dict]: method = self.collector or self.operator - config.update(method.statement) + config.update(method.expression) _statement = { "$search":config } - return self.resolve(_statement) + return self.express(_statement) diff --git a/src/monggregate/stages/search/search_meta.py b/src/monggregate/stages/search/search_meta.py index 1032d26..cc84f5f 100644 --- a/src/monggregate/stages/search/search_meta.py +++ b/src/monggregate/stages/search/search_meta.py @@ -63,6 +63,7 @@ """ +from monggregate.base import Expression from monggregate.stages.search.base import SearchBase @@ -105,7 +106,7 @@ class SearchMeta(SearchBase): """ @property - def statement(self) -> dict[str, dict]: + def expression(self) -> Expression: config = { "index":self.index, @@ -118,11 +119,11 @@ def statement(self) -> dict[str, dict]: method = self.collector or self.operator - config.update(method.statement) + config.update(method.expression) _statement = { "$searchMeta":config } - return self.resolve(_statement) + return self.express(_statement) \ No newline at end of file diff --git a/src/monggregate/stages/set.py b/src/monggregate/stages/set.py index f73fc73..bb87889 100644 --- a/src/monggregate/stages/set.py +++ b/src/monggregate/stages/set.py @@ -34,6 +34,7 @@ """ +from monggregate.base import Expression from monggregate.stages.stage import Stage class Set(Stage): @@ -55,7 +56,7 @@ class Set(Stage): document : dict = {} #| None @property - def statement(self)->dict[str, dict]: + def expression(self)->Expression: """Generates set stage statement from arguments""" - return self.resolve({"$set":self.document}) + return self.express({"$set":self.document}) diff --git a/src/monggregate/stages/skip.py b/src/monggregate/stages/skip.py index c515ab4..589a00a 100644 --- a/src/monggregate/stages/skip.py +++ b/src/monggregate/stages/skip.py @@ -62,6 +62,7 @@ """ #from pydantic import pyd.Field +from monggregate.base import Expression from monggregate.stages.stage import Stage class Skip(Stage): @@ -89,9 +90,9 @@ class Skip(Stage): value : int # Add gt 0 constraint ? check behavior with 0 @property - def statement(self)->dict: + def expression(self)->Expression: """Generate statement from arguments""" - return self.resolve({ + return self.express({ "$skip" : self.value }) diff --git a/src/monggregate/stages/sort.py b/src/monggregate/stages/sort.py index 8653481..e05d1d4 100644 --- a/src/monggregate/stages/sort.py +++ b/src/monggregate/stages/sort.py @@ -95,7 +95,7 @@ """ from typing import Literal -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import to_unique_list @@ -266,7 +266,7 @@ def _to_query(query:dict, sort_args:list[str]|dict, direction:bool)->None: return query @property - def statement(self)->dict[str, dict]: + def expression(self)->Expression: """Generates statement from other attributes""" - return self.resolve({"$sort":self.query}) + return self.express({"$sort":self.query}) diff --git a/src/monggregate/stages/sort_by_count.py b/src/monggregate/stages/sort_by_count.py index 11e0fbb..e0a4315 100644 --- a/src/monggregate/stages/sort_by_count.py +++ b/src/monggregate/stages/sort_by_count.py @@ -47,7 +47,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import validate_field_path @@ -81,9 +81,9 @@ class SortByCount(Stage): @property - def statement(self)->dict: + def expression(self)->Expression: """Generates sort_by_count stage statement from SortByCount class keywords arguments""" - return self.resolve({ + return self.express({ "$sortByCount" : self.by }) diff --git a/src/monggregate/stages/stage.py b/src/monggregate/stages/stage.py index 009577b..210812d 100644 --- a/src/monggregate/stages/stage.py +++ b/src/monggregate/stages/stage.py @@ -2,17 +2,29 @@ # Standard Library imports #---------------------------- -from abc import ABC +from abc import ABC, abstractmethod + # Package imports # --------------------------- -from monggregate.base import BaseModel +from monggregate.base import BaseModel, Expression from monggregate.utils import StrEnum class Stage(BaseModel, ABC): """MongoDB pipeline stage interface base class""" + def to_expression(self)->Expression: + """Converts an instance of a class inheriting from BaseModel to an expression""" + + return self.express(self) + + + def __call__(self)->Expression: + """Makes an instance of any class inheriting from this class callable""" + + return self.to_expression() + class StageEnum(StrEnum): """Enumeration of the available stages""" diff --git a/src/monggregate/stages/union_with.py b/src/monggregate/stages/union_with.py index 0af7615..d3944cf 100644 --- a/src/monggregate/stages/union_with.py +++ b/src/monggregate/stages/union_with.py @@ -170,7 +170,7 @@ """ from typing import Any -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.base import BaseModel from monggregate.stages.stage import Stage @@ -204,12 +204,12 @@ def validate_pipeline(cls, pipeline: Any): output = pipeline if isinstance(pipeline, BaseModel): - output = pipeline.statement + output = pipeline.expression return output @property - def statement(self) -> dict[str, dict]: + def expression(self) -> Expression: """Generates $unionWith statement""" if self.pipeline: @@ -222,4 +222,4 @@ def statement(self) -> dict[str, dict]: else: statement = {"$unionWith": self.collection} - return self.resolve(statement) + return self.express(statement) diff --git a/src/monggregate/stages/unset.py b/src/monggregate/stages/unset.py index dc568d9..f695b11 100644 --- a/src/monggregate/stages/unset.py +++ b/src/monggregate/stages/unset.py @@ -47,6 +47,7 @@ """ +from monggregate.base import Expression from monggregate.stages.stage import Stage from monggregate.fields import FieldName @@ -71,7 +72,7 @@ class Unset(Stage): fields: list[FieldName] | None @property - def statement(self) -> dict: + def expression(self) -> Expression: if self.field: _statement = { @@ -82,4 +83,4 @@ def statement(self) -> dict: "$unset": self.fields } - return self.resolve(_statement) + return self.express(_statement) diff --git a/src/monggregate/stages/unwind.py b/src/monggregate/stages/unwind.py index 99d2973..6223258 100644 --- a/src/monggregate/stages/unwind.py +++ b/src/monggregate/stages/unwind.py @@ -66,7 +66,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage from monggregate.utils import validate_field_path @@ -102,7 +102,7 @@ class Unwind(Stage): _validates_path_to_array = pyd.validator("path_to_array", allow_reuse=True, pre=True, always=True)(validate_field_path) @property - def statement(self)->dict[str, dict]: + def expression(self)->Expression: """Generates set stage statement from arguments""" params = {"path":self.path_to_array} @@ -113,4 +113,4 @@ def statement(self)->dict[str, dict]: if self.always: params["preserveNullAndEmptyArrays"] = self.always - return self.resolve({"$unwind" : params}) + return self.express({"$unwind" : params}) diff --git a/src/monggregate/stages/vector_search.py b/src/monggregate/stages/vector_search.py index 5767421..e5f6880 100644 --- a/src/monggregate/stages/vector_search.py +++ b/src/monggregate/stages/vector_search.py @@ -148,7 +148,7 @@ """ -from monggregate.base import pyd +from monggregate.base import pyd, Expression from monggregate.stages.stage import Stage class VectorSearch(Stage): @@ -175,7 +175,7 @@ class VectorSearch(Stage): query_vector : list[float] @pyd.validator("num_candidates", pre=True, always=True) - def validate_num_candidates(cls, num_candidates:int, values:dict): + def validate_num_candidates(cls, num_candidates:int, values:dict)->int: """Validates that num_candidates is less than or equal to 10000""" limit:int = values.get("limit", 1) @@ -185,10 +185,10 @@ def validate_num_candidates(cls, num_candidates:int, values:dict): return num_candidates @property - def statement(self) -> dict[str, dict]: + def expression(self) -> Expression: """Generates set stage statement from arguments""" - return self.resolve({"$vectorSearch" : { + return self.express({"$vectorSearch" : { "index" : self.index, "path" : self.path, "queryVector" : self.query_vector, diff --git a/tests/test_dollar.py b/tests/test_dollar.py index 286d198..d46c5a2 100644 --- a/tests/test_dollar.py +++ b/tests/test_dollar.py @@ -10,7 +10,7 @@ def test_dollar_getattr()->None: assert S.age == "$age" assert S.address == "$address" - assert S.and_(True, True) == And(expressions=[True, True]) + assert S.and_(True, True) == And(operands=[True, True]) def test_singletons()->None: """Tests that Dollar and DollarDollar are singletons""" @@ -23,9 +23,9 @@ def test_singletons()->None: def test_simple_expressions()->None: """Tests some simple expressions""" - assert S.sum(1).statement == {"$sum": 1} + assert S.sum(1).expression == {"$sum": 1} - assert S.type_("number").statement == {"$type": "number"} + assert S.type_("number").expression == {"$type": "number"} #S.avg(S.multiply(S.price, S.quantity)).statement == {"$avg": [{"$multiply": ["$price", "$quantity"]}]} diff --git a/tests/test_group.py b/tests/test_group.py index 6ba4b9a..d06b113 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -15,7 +15,7 @@ def test_group_stage(): query = { "output":{"$sum":"income"} } - ).statement == { + ).expression == { "$group":{ "_id":["$name", "$age"], "output":{"$sum":"income"} @@ -37,7 +37,7 @@ def test_group_stage(): "output":{"$sum":"income"} } } - assert group.statement == expected_statement + assert group.expression == expected_statement #, list(diff(expected_statement, group.statement)) @@ -48,7 +48,7 @@ def test_group_stage(): query = { "output":{"$sum":"income"} } - ).statement == { + ).expression == { "$group":{ "_id":{"name":"$name", "age":"$age"}, "output":{"$sum":"income"} @@ -68,7 +68,7 @@ def test_group_in_pipeline(): query = { "output":{"$sum":"income"} } - ).statement == [ + ).expression == [ { "$group":{ "_id":["$name", "$age"], @@ -84,7 +84,7 @@ def test_group_in_pipeline(): query = { "output":{"$sum":"income"} } - ).statement == [ + ).expression == [ { "$group":{ "_id":["$age", "$name"], @@ -100,7 +100,7 @@ def test_group_in_pipeline(): query = { "output":{"$sum":"income"} } - ).statement == [ + ).expression == [ { "$group":{ "_id":{"name":"$name", "age":"$age"}, diff --git a/tests/test_join_alias.py b/tests/test_join_alias.py index acb0eb0..a761dac 100644 --- a/tests/test_join_alias.py +++ b/tests/test_join_alias.py @@ -27,32 +27,6 @@ def test_left_join()->None: "as" : "__right__" }} -def test_right_join()->None: - """Tests right join in pipeline class""" - - - pipeline = Pipeline(collection="left") - pipeline.join( - other = "right", - how = "right", - on = "zipcode" - ) - - assert len(pipeline.stages) == 4, pipeline - # we set the other collection as being the reference left collection - # in a right join. Hence following line. - assert pipeline.collection == "right" - assert isinstance(pipeline[0], Lookup) - assert pipeline[0]() == { - "$lookup":{ - "from" : "left", # in a right join, other becomes left, and the pipeline - # collection becomes the right collection. - # In a nutshell, right becomes left, and left becomes right - "localField" : "zipcode", - "foreignField" : "zipcode", - "as" : "__left__" - }} - def test_inner_join()->None: """Tests left join in pipeline class""" @@ -81,5 +55,4 @@ def test_inner_join()->None: if __name__ == "__main__": test_left_join() - test_right_join() test_inner_join() diff --git a/tests/test_operators_accumulators.py b/tests/test_operators_accumulators.py index 3eca9fa..6a181b9 100644 --- a/tests/test_operators_accumulators.py +++ b/tests/test_operators_accumulators.py @@ -23,7 +23,7 @@ def test_average(self)->None: """Tests the $avg operator class and mirror function""" average_op = Average( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -32,7 +32,7 @@ def test_average(self)->None: # Functional test # --------------------- - assert average_op.statement == average([1, 2, 3, 4]).statement == { + assert average_op.expression == average([1, 2, 3, 4]).expression == { "$avg" : [1, 2, 3, 4] } @@ -47,7 +47,7 @@ def test_count(self)->None: # Functional test # --------------------- - assert count_op.statement == count().statement == { + assert count_op.expression == count().expression == { "$count" : {} } @@ -58,7 +58,7 @@ def test_first(self)->None: first_op = First( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -67,7 +67,7 @@ def test_first(self)->None: # Functional test # --------------------- - assert first_op.statement == first([1, 2, 3, 4]).statement == { + assert first_op.expression == first([1, 2, 3, 4]).expression == { "$first" : [1, 2, 3, 4] } @@ -77,7 +77,7 @@ def test_last(self)->None: last_op = Last( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -86,7 +86,7 @@ def test_last(self)->None: # Functional test # --------------------- - assert last_op.statement == last([1, 2, 3, 4]).statement == { + assert last_op.expression == last([1, 2, 3, 4]).expression == { "$last" : [1, 2, 3, 4] } @@ -95,7 +95,7 @@ def test_max(self)->None: max_op = Max( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -104,7 +104,7 @@ def test_max(self)->None: # Functional test # --------------------- - assert max_op.statement == max([1, 2, 3, 4]).statement == { + assert max_op.expression == max([1, 2, 3, 4]).expression == { "$max" : [1, 2, 3, 4] } @@ -113,7 +113,7 @@ def test_min(self)->None: min_op = Min( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -122,7 +122,7 @@ def test_min(self)->None: # Functional test # --------------------- - assert min_op.statement == min([1, 2, 3, 4]).statement == { + assert min_op.expression == min([1, 2, 3, 4]).expression == { "$min" : [1, 2, 3, 4] } @@ -131,7 +131,7 @@ def test_push(self)->None: """Tests the $push operator class and mirror function""" push_op = Push( - expression = { + operand = { "item" : "$item", "quantity" : "$quantity" } @@ -143,10 +143,10 @@ def test_push(self)->None: # Functional test # --------------------- - assert push_op.statement == push({ + assert push_op.expression == push({ "item" : "$item", "quantity" : "$quantity" - }).statement == { + }).expression == { "$push" : { "item" : "$item", "quantity" : "$quantity" @@ -159,7 +159,7 @@ def test_sum(self)->None: sum_op = Sum( - expression = [1, 2, 3, {"$literal":4}] + operand = [1, 2, 3, {"$literal":4}] ) # Unit test @@ -168,6 +168,6 @@ def test_sum(self)->None: # Functional test # --------------------- - assert sum_op.statement == sum([1, 2, 3, {"$literal":4}]).statement == { + assert sum_op.expression == sum([1, 2, 3, {"$literal":4}]).expression == { "$sum" : [1, 2, 3, {"$literal":4}] } diff --git a/tests/test_operators_array.py b/tests/test_operators_array.py index 705ba3b..90d88aa 100644 --- a/tests/test_operators_array.py +++ b/tests/test_operators_array.py @@ -29,7 +29,7 @@ class TestArrayOperators: def test_array_to_object(self)->None: """Tests the $arrayToObject operator class and mirror function""" - array_to_object_op = ArrayToObject(expression="$dimensions") + array_to_object_op = ArrayToObject(operand="$dimensions") # Unit test # ----------------- @@ -37,7 +37,7 @@ def test_array_to_object(self)->None: # Functional test # ----------------- - assert array_to_object_op.statement == array_to_object("$dimensions").statement == { + assert array_to_object_op.expression == array_to_object("$dimensions").expression == { "$arrayToObject" :"$dimensions" } @@ -47,7 +47,7 @@ def test_filter(self)->None: filter_op = Filter( - expression = [1, 2, 3, 4], + operand = [1, 2, 3, 4], let = "num", query = greather_than("$$num", 2), ) @@ -58,11 +58,11 @@ def test_filter(self)->None: # Functional test # --------------------- - assert filter_op.statement == filter( + assert filter_op.expression == filter( [1, 2, 3, 4], "num", greather_than("$$num", 2) - ).statement == { + ).expression == { "$filter" : { "input" : [1, 2, 3, 4], "as" : "num", @@ -78,7 +78,7 @@ def test_first(self)->None: first_op = First( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -87,7 +87,7 @@ def test_first(self)->None: # Functional test # --------------------- - assert first_op.statement == first([1, 2, 3, 4]).statement == { + assert first_op.expression == first([1, 2, 3, 4]).expression == { "$first" : [1, 2, 3, 4] } @@ -105,14 +105,14 @@ def test_in(self)->None: # Functional test # ----------------------- - assert in_op.statement == in_(1, [1, 2, 3, 4]).statement == { + assert in_op.expression == in_(1, [1, 2, 3, 4]).expression == { "$in" : [1, [1, 2, 3, 4]] } def test_is_array(self)->None: """Tests the $isArray operator class and mirror function""" - is_array_op = IsArray(expression=[1, 2, 3,4]) + is_array_op = IsArray(operand=[1, 2, 3,4]) # Unit test # -------------------- @@ -120,7 +120,7 @@ def test_is_array(self)->None: # Functional test # -------------------- - assert is_array_op.statement == is_array([1, 2, 3, 4]).statement == { + assert is_array_op.expression == is_array([1, 2, 3, 4]).expression == { "$isArray" : [1, 2, 3, 4] } @@ -129,7 +129,7 @@ def test_last(self)->None: last_op = Last( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -138,7 +138,7 @@ def test_last(self)->None: # Functional test # --------------------- - assert last_op.statement == last([1, 2, 3, 4]).statement == { + assert last_op.expression == last([1, 2, 3, 4]).expression == { "$last" : [1, 2, 3, 4] } @@ -148,7 +148,7 @@ def test_max_n(self)->None: max_n_op = MaxN( limit = 1, - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -157,7 +157,7 @@ def test_max_n(self)->None: # Functional test # -------------------- - assert max_n_op.statement == max_n([1, 2, 3, 4], 1).statement == { + assert max_n_op.expression == max_n([1, 2, 3, 4], 1).expression == { "$maxN" : { "n" : 1, "input" : [1, 2, 3, 4] @@ -169,7 +169,7 @@ def test_min_n(self)->None: min_n_op = MinN( limit = 1, - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -178,7 +178,7 @@ def test_min_n(self)->None: # Functional test # -------------------- - assert min_n_op.statement == min_n([1, 2, 3, 4], 1).statement == { + assert min_n_op.expression == min_n([1, 2, 3, 4], 1).expression == { "$minN" : { "n" : 1, "input" : [1, 2, 3, 4] @@ -189,7 +189,7 @@ def test_size(self)->None: """Tests the $size operator class and mirror function""" size_op = Size( - expression = [1, 2, 3, 4] + operand = [1, 2, 3, 4] ) # Unit test @@ -198,7 +198,7 @@ def test_size(self)->None: # Functional test # --------------------- - assert size_op.statement == size([1, 2, 3, 4]).statement == { + assert size_op.expression == size([1, 2, 3, 4]).expression == { "$size" : [1, 2, 3, 4] } @@ -207,7 +207,7 @@ def test_sort_array(self)->None: """Tests the $sortArray operator class and mirror function""" sort_array_op = SortArray( - expression ="$team", + operand = "$team", sort_by = {"name":1} ) @@ -217,7 +217,7 @@ def test_sort_array(self)->None: # Functional test # ---------------- - assert sort_array_op.statement == sort_array("$team", {"name":1}).statement == { + assert sort_array_op.expression == sort_array("$team", {"name":1}).expression == { "$sortArray" : { "input" : "$team", "sortBy" : { @@ -225,3 +225,9 @@ def test_sort_array(self)->None: } } } + +if __name__ == "__main__": + tests = TestArrayOperators() + tests.test_filter() + tests.test_max_n() + \ No newline at end of file diff --git a/tests/test_operators_boolean.py b/tests/test_operators_boolean.py index a9b664b..88975dd 100644 --- a/tests/test_operators_boolean.py +++ b/tests/test_operators_boolean.py @@ -22,9 +22,9 @@ def test_and(self)->None: # Unit test # --------------------------- and_operator = And( - expressions=[ - First(expression=[1, 2, 3, 4]), - First(expression=[4, 5, 6, 7]) + operands=[ + First(operand=[1, 2, 3, 4]), + First(operand=[4, 5, 6, 7]) ] ) @@ -32,7 +32,7 @@ def test_and(self)->None: # Functional tests # -------------------------- - assert and_operator.statement == and_(First(expression=[1, 2, 3, 4]), First(expression=[4, 5, 6, 7])).statement == { + assert and_operator.expression == and_(First(operand=[1, 2, 3, 4]), First(operand=[4, 5, 6, 7])).expression == { "$and" : [ { "$first" : [1, 2, 3, 4] @@ -47,7 +47,7 @@ def test_and(self)->None: def test_not(self)->None: """Tests the $not operator class and mirror function""" - not_op = Not(expression=greather_than("$qty", 250)) + not_op = Not(operand=greather_than("$qty", 250)) # Unit test # -------------------- @@ -55,7 +55,7 @@ def test_not(self)->None: # Functional test # -------------------- - assert not_op.statement == not_(greather_than("$qty", 250)).statement == { + assert not_op.expression == not_(greather_than("$qty", 250)).expression == { "$not" : [{"$gt":["$qty", 250]}] } @@ -66,9 +66,9 @@ def test_or(self)->None: # Unit test # --------------------------- or_operator = Or( - expressions=[ - First(expression=[1, 2, 3, 4]), - First(expression=[4, 5, 6, 7]) + operands=[ + First(operand=[1, 2, 3, 4]), + First(operand=[4, 5, 6, 7]) ] ) @@ -76,7 +76,7 @@ def test_or(self)->None: # Functional tests # -------------------------- - assert or_operator.statement == or_(First(expression=[1, 2, 3, 4]), First(expression=[4, 5, 6, 7])).statement == { + assert or_operator.expression == or_(First(operand=[1, 2, 3, 4]), First(operand=[4, 5, 6, 7])).expression == { "$or" : [ { "$first" : [1, 2, 3, 4] diff --git a/tests/test_operators_comparison.py b/tests/test_operators_comparison.py index a71ced2..7273deb 100644 --- a/tests/test_operators_comparison.py +++ b/tests/test_operators_comparison.py @@ -32,7 +32,7 @@ def test_cmp(self)->None: # Functional test # ------------------- - assert cmp_op.statement == cmp("$qty", 250).statement == { + assert cmp_op.expression == cmp("$qty", 250).expression == { "$cmp" : ["$qty", 250] } @@ -50,7 +50,7 @@ def test_eq(self)->None: # Functional test # ------------------- - assert eq_op.statement == eq("$qty", 250).statement == { + assert eq_op.expression == eq("$qty", 250).expression == { "$eq" : ["$qty", 250] } @@ -68,7 +68,7 @@ def test_gt(self)->None: # Functional test # ------------------- - assert gt_op.statement == gt("$qty", 250).statement == { + assert gt_op.expression == gt("$qty", 250).expression == { "$gt" : ["$qty", 250] } @@ -86,7 +86,7 @@ def test_gte(self)->None: # Functional test # ------------------- - assert gte_op.statement == gte("$qty", 250).statement == { + assert gte_op.expression == gte("$qty", 250).expression == { "$gte" : ["$qty", 250] } @@ -104,7 +104,7 @@ def test_lt(self)->None: # Functional test # ------------------- - assert lt_op.statement == lt("$qty", 250).statement == { + assert lt_op.expression == lt("$qty", 250).expression == { "$lt" : ["$qty", 250] } @@ -122,7 +122,7 @@ def test_lte(self)->None: # Functional test # ------------------- - assert lte_op.statement == lte("$qty", 250).statement == { + assert lte_op.expression == lte("$qty", 250).expression == { "$lte" : ["$qty", 250] } @@ -140,6 +140,6 @@ def test_ne(self)->None: # Functional test # ------------------- - assert ne_op.statement == ne("$qty", 250).statement == { + assert ne_op.expression == ne("$qty", 250).expression == { "$ne" : ["$qty", 250] } diff --git a/tests/test_operators_objects.py b/tests/test_operators_objects.py index 9ff4154..be46598 100644 --- a/tests/test_operators_objects.py +++ b/tests/test_operators_objects.py @@ -17,7 +17,7 @@ def test_merge_objects(self)->None: """Tests the $mergeObjects operator""" merge_objects_op = MergeObjects( - expression = "$quantity" + operand = "$quantity" ) # Unit test @@ -26,7 +26,7 @@ def test_merge_objects(self)->None: # Functinal test # --------------- - assert merge_objects_op.statement == merge_objects("$quantity").statement == { + assert merge_objects_op.expression == merge_objects("$quantity").expression == { "$mergeObjects" : "$quantity" } @@ -34,7 +34,7 @@ def test_object_to_array(self)->None: """Tests the $mergeObjects operator""" object_to_array_op = ObjectToArray( - expression = "$dimensions" + operand = "$dimensions" ) # Unit test @@ -43,6 +43,6 @@ def test_object_to_array(self)->None: # Functinal test # --------------- - assert object_to_array_op.statement == object_to_array("$dimensions").statement == { + assert object_to_array_op.expression == object_to_array("$dimensions").expression == { "$objectToArray" : "$dimensions" } diff --git a/tests/test_search_operators.py b/tests/test_search_operators.py index 11ca8b3..cc2e2f9 100644 --- a/tests/test_search_operators.py +++ b/tests/test_search_operators.py @@ -40,7 +40,7 @@ def test_autocomplete(self)->None: # Functional test # --------------- - assert autocomplete_op.statement == { + assert autocomplete_op.expression == { "autocomplete": { "query": "m", "path": "title", @@ -79,7 +79,7 @@ def test_equals(self)->None: # Functional test # --------------- - assert equals_op.statement == { + assert equals_op.expression == { "equals": { "path": "title", "score": None, @@ -106,7 +106,7 @@ def test_exists(self)->None: # Functional test # --------------- - assert exists_op.statement == { + assert exists_op.expression == { "exists": { "path": "title", } @@ -127,7 +127,7 @@ def test_more_like_this(self)->None: # Functional test # --------------- - assert more_like_this_op.statement == { + assert more_like_this_op.expression == { "moreLikeThis": { "like": { "_id": "5a934e000102030405000000", @@ -160,7 +160,7 @@ def test_range(self)->None: # Functional test # --------------- - assert range_op.statement == { + assert range_op.expression == { "range": { "path": "price", "gte": 10, @@ -190,7 +190,7 @@ def test_regex(self)->None: # Functional test # --------------- - assert regex_op.statement == { + assert regex_op.expression == { "regex": { "path": "title", "query": "^test$", @@ -213,7 +213,7 @@ def test_text(self)->None: # Functional test # --------------- - assert text_op.statement == { + assert text_op.expression == { "text": { "query": "test", "path": "description" @@ -234,7 +234,7 @@ def test_wilcard(self)->None: # Functional test # --------------- - assert wilcard_op.statement == { + assert wilcard_op.expression == { "wildcard": { "allowAnalyzedField": False, "path": "title", diff --git a/tests/test_stages.py b/tests/test_stages.py index 0a610a0..7b04c87 100644 --- a/tests/test_stages.py +++ b/tests/test_stages.py @@ -61,7 +61,7 @@ def test_stage(self)->None: with pytest.raises(TypeError): # Checking that Stage cannot be instantiated - stage = Stage(statement={}) # pylint: disable=abstract-class-instantiated + stage = Stage(expression={}) # pylint: disable=abstract-class-instantiated def test_bucket_auto(self, state:State)->None: @@ -553,10 +553,10 @@ class TestStagesFunctional(TestStages): def test_bucket_auto_statement(self, state:State)->None: """Tests the BucketAuto class statement and its mirror function""" - assert state["bucket_auto"].statement == Pipeline().bucket_auto( + assert state["bucket_auto"].expression == Pipeline().bucket_auto( by = "test", buckets = 10 - )[-1].statement == { + )[-1].expression == { "$bucketAuto" : { "groupBy" : "$test", "buckets" : 10, @@ -571,9 +571,9 @@ def test_bucket_statement(self, state:State)->None: """ bucket = state["bucket"] - assert bucket.statement == Pipeline().bucket( + assert bucket.expression == Pipeline().bucket( by = "income", - boundaries = [25000, 40000, 60000, 100000])[-1].statement == { + boundaries = [25000, 40000, 60000, 100000])[-1].expression == { "$bucket" : { "groupBy" : "$income", "boundaries" : [25000, 40000, 60000, 100000], @@ -586,7 +586,7 @@ def test_count_statement(self, state:State)->None: """Tests the Count class statement and its mirror function""" count = state["count"] - assert count.statement == Pipeline().count(name="count")[0].statement == { + assert count.expression == Pipeline().count(name="count")[0].expression == { "$count" : "count" } @@ -594,11 +594,11 @@ def test_group_statement(self, state:State)->None: """Tests the Group class statement and its mirror function""" group = state["group"] - assert group.statement == Pipeline().group( + assert group.expression == Pipeline().group( query = { "_id" :"count" } - )[0].statement == { + )[0].expression == { "$group" : { "_id" :"count" } @@ -608,7 +608,7 @@ def test_limit_statement(self, state:State)->None: """Tests the Limit class statement and its mirror function""" limit = state["limit"] - assert limit.statement == Pipeline().limit(10)[0].statement == { + assert limit.expression == Pipeline().limit(10)[0].expression == { "$limit" : 10 } @@ -616,12 +616,12 @@ def test_lookup_statement(self, state:State)->None: """Tests the Limit class statement and its mirror function""" lookup = state["lookup"] - assert lookup.statement == Pipeline().lookup( + assert lookup.expression == Pipeline().lookup( right = "other_collection", left_on = "_id", right_on = "foreign_key", name = "matches" - )[0].statement == { + )[0].expression == { "$lookup" :{ "from" : "other_collection", "localField" : "_id", @@ -634,11 +634,11 @@ def test_match_statement(self, state:State)->None: """Tests the Match class and its mirror function""" match = state["match"] - assert match.statement == Pipeline().match( + assert match.expression == Pipeline().match( query = { "_id":"12345" } - )[0].statement == { + )[0].expression == { "$match" : { "_id" : "12345" } @@ -648,7 +648,7 @@ def test_out_satement(self, state:State)->None: """Tests the Out class and its mirror function""" out = state["out"] - assert out.statement == Pipeline().out("my_collection")[0].statement == { + assert out.expression == Pipeline().out("my_collection")[0].expression == { "$out" : "my_collection" } @@ -656,9 +656,9 @@ def test_project_statement(self, state:State)->None: """Tests the Project class and its mirror function""" project = state["project"] - assert project.statement == Pipeline().project( + assert project.expression == Pipeline().project( exclude = "_id" - )[0].statement == { + )[0].expression == { "$project" : { "_id" : 0 } @@ -668,9 +668,9 @@ def test_replace_root_statement(self, state:State)->None: """Tests the ReplaceRoot class and its mirror function""" replace_root = state["replace_root"] - assert replace_root.statement == Pipeline().replace_root( + assert replace_root.expression == Pipeline().replace_root( "myarray.mydocument" - )[0].statement == { + )[0].expression == { "$replaceRoot" : { "newRoot" : "$myarray.mydocument" } @@ -680,7 +680,7 @@ def test_sample_statement(self, state:State) -> None: """Tests the Sample class and its mirror function""" sample = state["sample"] - assert sample.statement == Pipeline().sample(3)[0].statement == { + assert sample.expression == Pipeline().sample(3)[0].expression == { "$sample" : { "size" : 3 } @@ -690,12 +690,12 @@ def test_set_statement(self, state:State) -> None: """Tests the Set class and its mirror function""" set = state["set"] - assert set.statement == Pipeline().set( + assert set.expression == Pipeline().set( { "field1":"value1", "fieldN":"valueN" } - )[0].statement == { + )[0].expression == { "$set" : { "field1":"value1", "fieldN":"valueN" @@ -706,7 +706,7 @@ def test_skip_statement(self, state:State)->None: """Tests the Skip class and its mirror function""" skip = state["skip"] - assert skip.statement == Pipeline().skip(10)[0].statement == { + assert skip.expression == Pipeline().skip(10)[0].expression == { "$skip" : 10 } @@ -715,9 +715,9 @@ def test_sort_by_count_statement(self, state:State)->None: """Tests the SortByCount class and its mirror function""" sort_by_count = state["sort_by_count"] - assert sort_by_count.statement == Pipeline().sort_by_count( + assert sort_by_count.expression == Pipeline().sort_by_count( by = "name" - )[0].statement == { + )[0].expression == { "$sortByCount" : "$name" } @@ -725,7 +725,7 @@ def test_sort_statement(self, state:State)->None: """Tests the Sort class and its mirror function""" sort = state["sort"] - assert sort.statement == Pipeline().sort(field1=1, fieldN=-1)[0].statement == { + assert sort.expression == Pipeline().sort(field1=1, fieldN=-1)[0].expression == { "$sort" : { "field1" : 1, "fieldN" : -1 @@ -736,7 +736,7 @@ def test_unwind_statement(self, state:State)->None: """Tests the Unwind class and its mirror function""" unwind = state["unwind"] - assert unwind.statement == Pipeline().unwind("xyz")[0].statement == { + assert unwind.expression == Pipeline().unwind("xyz")[0].expression == { "$unwind" : { "path" : "$xyz" } diff --git a/tests/tests_docs/test_readme.py b/tests/tests_docs/test_readme.py index 12eaccf..ad8ac3c 100644 --- a/tests/tests_docs/test_readme.py +++ b/tests/tests_docs/test_readme.py @@ -8,7 +8,7 @@ @pytest.mark.skip("Need to authorize the IP address of the machine running the tests.") @pytest.mark.e2e -def test_basic_pipeline_usage(): +def test_basic_pipeline_usage()->None: """Tests basic pipeline usage example. (First code snippet) @@ -53,7 +53,7 @@ def test_basic_pipeline_usage(): @pytest.mark.skip("Need to authorize the IP address of the machine running the tests.") @pytest.mark.e2e -def test_advanced_usage(): +def test_advanced_usage()->None: """Tests more advanced pipeline usage example. (Second code snippet) @@ -103,7 +103,7 @@ def test_advanced_usage(): @pytest.mark.skip("Need to authorize the IP address of the machine running the tests.") @pytest.mark.e2e -def test_even_more_advanced_usage(): +def test_even_more_advanced_usage()->None: """Tests even more advanced pipeline usage example. (Third code snippet) @@ -114,7 +114,7 @@ def test_even_more_advanced_usage(): from dotenv import load_dotenv import pymongo - from monggregate import Pipeline, S, Expression + from monggregate import Pipeline, S # Creating connexion string securely load_dotenv(verbose=True) @@ -129,7 +129,7 @@ def test_even_more_advanced_usage(): db = client["sample_mflix"] # Using expressions - comments_count = Expression.field("comments").size() + comments_count = S.size(S.comments) # Creating the pipeline @@ -142,7 +142,7 @@ def test_even_more_advanced_usage(): ).add_fields( comments_count=comments_count ).match( - expression=comments_count>2 + operand=comments_count>2 ).limit(1)