From 0604215be0842f965fa5080f5c8c4f67b8832688 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Thu, 10 Aug 2023 19:55:34 +0200 Subject: [PATCH 01/26] Kick start --- monggregate/expressions/content.py | 20 ++++++++ monggregate/expressions/expressions.py | 2 + monggregate/expressions/fields.py | 17 ++++++ monggregate/operators/dollar.py | 71 ++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 monggregate/operators/dollar.py diff --git a/monggregate/expressions/content.py b/monggregate/expressions/content.py index 93a3f78..bad1966 100644 --- a/monggregate/expressions/content.py +++ b/monggregate/expressions/content.py @@ -4,7 +4,27 @@ from monggregate.expressions.fields import FieldPath, Variable #from monggregate.operators.operator import Operator +# TODO : Distinguish between expressions evaluations (evaluated expressions) and lazy expressions (expressions that are not evaluated) Const = int | float | str | bool Consts = list[int] | list[float] | list[str] | list[bool] Content = dict[str, Any] | Variable | FieldPath | int | float | str | bool | list[int] | list[float] | list[str] | list[bool] # dict[str, Any] above represents Operator Expressions, Expression Objects and nested expressions + +# In all these cases, an expression is just something that dynamically populates and returns a new JSON/BSON data type element, which can be one of: + + # * a Number (including integer, long, float, double, decimal128) + # * a String (UTF-8) + # * a Boolean + # * a DateTime (UTC) + # * an Array + # * an Object + +# In a nutshell (Vianney's words): Expressions are lazily evaluated objects + + +# The previously stated generalisation about $match not supporting expressions is actually inaccurate. +# Version 3.6 of MongoDB introduced the $expr operator, which you can embed within a $match stage (or in MQL) to leverage aggregation expressions when filtering records. +# Essentially, this enables MongoDB's query runtime (which executes an aggregation's $match) to reuse expressions provided by MongoDB's aggregation runtime. + +# Inside a $expr operator, you can include any composite expression fashioned from $ operator functions, +# $ field paths and $$ variables. A few situations demand having to use $expr from inside a $match stage. Examples include: \ No newline at end of file diff --git a/monggregate/expressions/expressions.py b/monggregate/expressions/expressions.py index 674c3a7..d154724 100644 --- a/monggregate/expressions/expressions.py +++ b/monggregate/expressions/expressions.py @@ -4,6 +4,8 @@ # https://stackoverflow.com/questions/53638973/recursive-type-annotations # https://stackoverflow.com/questions/53845024/defining-a-recursive-type-hint-in-python +# TO HELP REFACTOR: https://www.practical-mongodb-aggregations.com/guides/expressions.html + # Standard Library imports #---------------------------- from typing import Any, Literal diff --git a/monggregate/expressions/fields.py b/monggregate/expressions/fields.py index 8925ced..bb48d32 100644 --- a/monggregate/expressions/fields.py +++ b/monggregate/expressions/fields.py @@ -25,3 +25,20 @@ class Variable(FieldPath): """Regex describing reference to a variable in expressions""" regex = re.compile(r"^\$\$") + +# Variables. Accessed as a string with a $$ prefix followed by the fixed name and falling into three sub-categories: + + # Context System Variables. + + # With values coming from the system environment rather than each input record an aggregation stage is processing. + # Examples: "$$NOW", "$$CLUSTER_TIME" + + # Marker Flag System Variables. + + # To indicate desired behaviour to pass back to the aggregation runtime. + # Examples: "$$ROOT", "$$REMOVE", "$$PRUNE" + + # Bind User Variables. + + # For storing values you declare with a $let operator (or with the let option of a $lookup stage, or as option of a $map or $filter stage). + # Examples: "$$product_name_var", "$$orderIdVal" \ No newline at end of file diff --git a/monggregate/operators/dollar.py b/monggregate/operators/dollar.py new file mode 100644 index 0000000..08188c4 --- /dev/null +++ b/monggregate/operators/dollar.py @@ -0,0 +1,71 @@ +"""WIP""" + +from typing import Any + +from monggregate.base import BaseModel + + # Avg, + # Count, + # First, + # Last, + # Max, + # Min, + # Push, + # Sum +from monggregate.operators import( + accumulators, + array, + comparison, + objects, + boolean +) +from monggregate.operators.array import( + ArrayToObject, + Filter, + #First, + In, + IsArray, + #Last, + MaxN, + MinN, + SortArray +) +from monggregate.operators.comparison import( + Cmp, + Eq, + Gt, + Gte, + Lt, + Lte, + Ne +) +from monggregate.operators.objects import( + MergeObjects, + ObjectToArray +) +from monggregate.operators.boolean import And, Or, Not, and_ + +# NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with +# it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance + +# TODO : Do not return statement directly but rather return a class that can be used to build the statement + +class Dollar(BaseModel): + """Base class for all $ functions""" + + # Operators + # ------------------------------ + + # Accumulators + # -------------------------- + @classmethod + def avg(cls, expression:Any)->str: + """Returns the $avg operator""" + + return accumulators.avg(expression) + + @classmethod + def and_(cls, *args:Any)->str: + """Returns the $and operator""" + + return boolean.and_(*args) From d6d769284394d3ae3ffd2430352ac91d066d6dd7 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 11 Aug 2023 10:00:21 +0200 Subject: [PATCH 02/26] Stored all operators in Dollar class --- monggregate/operators/dollar.py | 207 ++++++++++++++++++++++++++------ 1 file changed, 171 insertions(+), 36 deletions(-) diff --git a/monggregate/operators/dollar.py b/monggregate/operators/dollar.py index 08188c4..671b6b4 100644 --- a/monggregate/operators/dollar.py +++ b/monggregate/operators/dollar.py @@ -1,17 +1,9 @@ """WIP""" -from typing import Any +from typing import Any, Literal from monggregate.base import BaseModel - # Avg, - # Count, - # First, - # Last, - # Max, - # Min, - # Push, - # Sum from monggregate.operators import( accumulators, array, @@ -19,37 +11,12 @@ objects, boolean ) -from monggregate.operators.array import( - ArrayToObject, - Filter, - #First, - In, - IsArray, - #Last, - MaxN, - MinN, - SortArray -) -from monggregate.operators.comparison import( - Cmp, - Eq, - Gt, - Gte, - Lt, - Lte, - Ne -) -from monggregate.operators.objects import( - MergeObjects, - ObjectToArray -) -from monggregate.operators.boolean import And, Or, Not, and_ # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with # it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance # TODO : Do not return statement directly but rather return a class that can be used to build the statement - +# TODO : Fix return types class Dollar(BaseModel): """Base class for all $ functions""" @@ -63,9 +30,177 @@ def avg(cls, expression:Any)->str: """Returns the $avg operator""" return accumulators.avg(expression) + + @classmethod + def count(cls)->str: + """Returns the $count operator""" + + return accumulators.count() + + @classmethod + def first(cls, expression:Any)->str: + """Returns the $first operator""" + + return accumulators.first(expression) + + @classmethod + def last(cls, expression:Any)->str: + """Returns the $last operator""" + + return accumulators.last(expression) + + @classmethod + def max(cls, expression:Any)->str: + """Returns the $max operator""" + + return accumulators.max(expression) + + @classmethod + def min(cls, expression:Any)->str: + """Returns the $min operator""" + + return accumulators.min(expression) + + @classmethod + def push(cls, expression:Any)->str: + """Returns the $push operator""" + + return accumulators.push(expression) + + @classmethod + def sum(cls, expression:Any)->str: + """Returns the $sum operator""" + + return accumulators.sum(expression) + + # Array + # -------------------------- + @classmethod + def array_to_object(cls, expression:Any)->str: + """Returns the $arrayToObject operator""" + + return array.array_to_object(expression) + + # TODO : Workout aliases + @classmethod + def filter(cls, expression:Any,*, let:str, query:Any, limit:int|None=None)->str: + """Returns the $filter operator""" + + return array.filter(expression, let, query, limit) + + @classmethod + def in_(cls, left:Any, right:Any)->str: + """Returns the $in operator""" + + return array.in_(left, right) + + @classmethod + def is_array(cls, expression:Any)->str: + """Returns the $isArray operator""" + + return array.is_array(expression) + + @classmethod + def max_n(cls, expression:Any, n:int=1)->str: + """Returns the $max operator""" + + return array.max_n(expression, n) + + @classmethod + def min_n(cls, expression:Any, n:int=1)->str: + """Returns the $min operator""" + + return array.min_n(expression, n) + + @classmethod + def size(cls, expression:Any)->str: + """Returns the $size operator""" + return array.size(expression) + + # 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]])->str: + """Returns the $sort operator""" + + return array.sort_array(expression, sort_spec) + + # Comparison + # -------------------------- + @classmethod + def cmp(cls, left:Any, right:Any)->str: + """Returns the $cmp operator""" + + return comparison.cmp(left, right) + + @classmethod + def eq(cls, left:Any, right:Any)->str: + """Returns the $eq operator""" + + return comparison.eq(left, right) + + @classmethod + def gt(cls, left:Any, right:Any)->str: + """Returns the $gt operator""" + + return comparison.gt(left, right) + + @classmethod + def gte(cls, left:Any, right:Any)->str: + """Returns the $gte operator""" + + return comparison.gte(left, right) + + @classmethod + def lt(cls, left:Any, right:Any)->str: + """Returns the $lt operator""" + + return comparison.lt(left, right) + + @classmethod + def lte(cls, left:Any, right:Any)->str: + """Returns the $lte operator""" + + return comparison.lte(left, right) + + @classmethod + def ne(cls, left:Any, right:Any)->str: + """Returns the $ne operator""" + + return comparison.ne(left, right) + + # Objects + # -------------------------- + @classmethod + def merge_objects(cls, *args:Any)->str: + """Returns the $mergeObjects operator""" + + return objects.merge_objects(*args) + + @classmethod + def object_to_array(cls, expression:Any)->str: + """Returns the $objectToArray operator""" + + return objects.object_to_array(expression) + + # Boolean + # -------------------------- @classmethod def and_(cls, *args:Any)->str: """Returns the $and operator""" - + return boolean.and_(*args) + + @classmethod + def or_(cls, *args:Any)->str: + """Returns the $or operator""" + + return boolean.or_(*args) + + @classmethod + def not_(cls, expression:Any)->str: + """Returns the $not operator""" + + return boolean.not_(expression) + From c0f53567be385129e50bca2e54a7ecf41a383eca Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 11 Aug 2023 10:17:21 +0200 Subject: [PATCH 03/26] Return operators rather than statements in operators mirror functions --- monggregate/operators/accumulators/avg.py | 4 ++-- monggregate/operators/accumulators/count.py | 4 ++-- monggregate/operators/accumulators/first.py | 4 ++-- monggregate/operators/accumulators/last.py | 4 ++-- monggregate/operators/accumulators/max.py | 4 ++-- monggregate/operators/accumulators/min.py | 4 ++-- monggregate/operators/accumulators/push.py | 4 ++-- monggregate/operators/accumulators/sum.py | 6 +++--- monggregate/operators/array/array_to_object.py | 4 ++-- monggregate/operators/array/filter.py | 4 ++-- monggregate/operators/array/first.py | 4 ++-- monggregate/operators/array/in_.py | 4 ++-- monggregate/operators/array/is_array.py | 4 ++-- monggregate/operators/array/last.py | 4 ++-- monggregate/operators/array/max_n.py | 4 ++-- monggregate/operators/array/min_n.py | 4 ++-- monggregate/operators/array/size.py | 4 ++-- monggregate/operators/array/sort_array.py | 4 ++-- monggregate/operators/boolean/and_.py | 4 ++-- monggregate/operators/boolean/not_.py | 4 ++-- monggregate/operators/boolean/or_.py | 4 ++-- monggregate/operators/comparison/cmp.py | 4 ++-- monggregate/operators/comparison/eq.py | 4 ++-- monggregate/operators/comparison/gt.py | 4 ++-- monggregate/operators/comparison/gte.py | 4 ++-- monggregate/operators/comparison/lt.py | 4 ++-- monggregate/operators/comparison/lte.py | 4 ++-- monggregate/operators/comparison/ne.py | 4 ++-- monggregate/operators/objects/merge_objects.py | 4 ++-- monggregate/operators/objects/object_to_array.py | 4 ++-- 30 files changed, 61 insertions(+), 61 deletions(-) diff --git a/monggregate/operators/accumulators/avg.py b/monggregate/operators/accumulators/avg.py index 97afe0b..2c1156f 100644 --- a/monggregate/operators/accumulators/avg.py +++ b/monggregate/operators/accumulators/avg.py @@ -95,9 +95,9 @@ def statement(self) -> dict: Avg = Average -def average(expression:Any)->dict: +def average(expression:Any)->Average: """Creates a push statement""" - return Average(expression=expression).statement + return Average(expression=expression) avg = average diff --git a/monggregate/operators/accumulators/count.py b/monggregate/operators/accumulators/count.py index d3d880f..df7b371 100644 --- a/monggregate/operators/accumulators/count.py +++ b/monggregate/operators/accumulators/count.py @@ -62,8 +62,8 @@ def statement(self) -> dict: "$count" : {} }) -def count()->dict: +def count()->Count: """Creates a $count statement""" - return Count().statement + return Count() diff --git a/monggregate/operators/accumulators/first.py b/monggregate/operators/accumulators/first.py index 19fee72..64e1740 100644 --- a/monggregate/operators/accumulators/first.py +++ b/monggregate/operators/accumulators/first.py @@ -81,7 +81,7 @@ def statement(self) -> dict: "$first" : self.expression }) -def first(expression:Any)->dict: +def first(expression:Any)->First: """Creates a push statement""" - return First(expression=expression).statement + return First(expression=expression) diff --git a/monggregate/operators/accumulators/last.py b/monggregate/operators/accumulators/last.py index 72654f3..901ee37 100644 --- a/monggregate/operators/accumulators/last.py +++ b/monggregate/operators/accumulators/last.py @@ -75,7 +75,7 @@ def statement(self) -> dict: "$last" : self.expression }) -def last(expression:Any)->dict: +def last(expression:Any)->Last: """Creates a push statement""" - return Last(expression=expression).statement + return Last(expression=expression) diff --git a/monggregate/operators/accumulators/max.py b/monggregate/operators/accumulators/max.py index 5115d45..5f13a33 100644 --- a/monggregate/operators/accumulators/max.py +++ b/monggregate/operators/accumulators/max.py @@ -99,7 +99,7 @@ def statement(self) -> dict: "$max" : self.expression }) -def max(expression:Any)->dict: # pylint: disable=redefined-builtin +def max(expression:Any)->Max: """Creates a push statement""" - return Max(expression=expression).statement + return Max(expression=expression) diff --git a/monggregate/operators/accumulators/min.py b/monggregate/operators/accumulators/min.py index cc5c2ea..753f6df 100644 --- a/monggregate/operators/accumulators/min.py +++ b/monggregate/operators/accumulators/min.py @@ -99,7 +99,7 @@ def statement(self) -> dict: "$min" : self.expression }) -def min(expression:Any)->dict: +def min(expression:Any)->Min: """Creates a $min statement""" - return Min(expression=expression).statement + return Min(expression=expression) diff --git a/monggregate/operators/accumulators/push.py b/monggregate/operators/accumulators/push.py index eea216b..8a30a8a 100644 --- a/monggregate/operators/accumulators/push.py +++ b/monggregate/operators/accumulators/push.py @@ -52,7 +52,7 @@ def statement(self) -> dict: "$push" : self.expression }) -def push(expression:Any)->dict: +def push(expression:Any)->Push: """Creates a push statement""" - return Push(expression=expression).statement + return Push(expression=expression) diff --git a/monggregate/operators/accumulators/sum.py b/monggregate/operators/accumulators/sum.py index 915a32d..85c89c6 100644 --- a/monggregate/operators/accumulators/sum.py +++ b/monggregate/operators/accumulators/sum.py @@ -108,12 +108,12 @@ def statement(self) -> dict: "$sum" : self.expression }) -def sum(*args:Content)->dict: # pylint: disable=redefined-builtin +def sum(*args:Content)->Sum: """Creates a $sum statement""" if len(args)>1: - output = Sum(expression=list(args)).statement + output = Sum(expression=list(args)) else: - output = Sum(expression=args[0]).statement + output = Sum(expression=args[0]) return output diff --git a/monggregate/operators/array/array_to_object.py b/monggregate/operators/array/array_to_object.py index e254600..cc18f4a 100644 --- a/monggregate/operators/array/array_to_object.py +++ b/monggregate/operators/array/array_to_object.py @@ -72,7 +72,7 @@ def statement(self) -> dict: "$arrayToObject" : self.expression }) -def array_to_object(expression:Any)->dict: +def array_to_object(expression:Any)->ArrayToObject: """Returns an $arrayToObject statement""" - return ArrayToObject(expression=expression).statement + return ArrayToObject(expression=expression) diff --git a/monggregate/operators/array/filter.py b/monggregate/operators/array/filter.py index 4a14ece..3f0029d 100644 --- a/monggregate/operators/array/filter.py +++ b/monggregate/operators/array/filter.py @@ -84,7 +84,7 @@ def statement(self) -> dict: } }) -def filter(expression:Any, let:str, query:Any, limit:int|None=None)->dict: # pylint: disable=redefined-builtin +def filter(expression:Any, let:str, query:Any, limit:int|None=None)->Filter: """Returns a $filter statement""" return Filter( @@ -92,4 +92,4 @@ def filter(expression:Any, let:str, query:Any, limit:int|None=None)->dict: # pyl query = query, let = let, limit = limit - ).statement + ) diff --git a/monggregate/operators/array/first.py b/monggregate/operators/array/first.py index c626149..cb1fefe 100644 --- a/monggregate/operators/array/first.py +++ b/monggregate/operators/array/first.py @@ -116,9 +116,9 @@ def statement(self) -> dict: "$first":self.expression }) -def first(array:Any)->dict: +def first(array:Any)->First: """Returns a $first statement""" return First( expression = array - ).statement + ) diff --git a/monggregate/operators/array/in_.py b/monggregate/operators/array/in_.py index 08378ff..8ed48fe 100644 --- a/monggregate/operators/array/in_.py +++ b/monggregate/operators/array/in_.py @@ -54,10 +54,10 @@ def statement(self) -> dict: "$in":[self.left, self.right] }) -def in_(left:Any, right:Any)->dict: +def in_(left:Any, right:Any)->In: """Returns a $maxN statement""" return In( left = left, right = right - ).statement + ) diff --git a/monggregate/operators/array/is_array.py b/monggregate/operators/array/is_array.py index aca898e..76012f5 100644 --- a/monggregate/operators/array/is_array.py +++ b/monggregate/operators/array/is_array.py @@ -44,9 +44,9 @@ def statement(self) -> dict: "$isArray":self.expression }) -def is_array(array:Any)->dict: +def is_array(array:Any)->IsArray: """Returns a $isArray statement""" return IsArray( expression = array - ).statement + ) diff --git a/monggregate/operators/array/last.py b/monggregate/operators/array/last.py index 13acfad..b05666f 100644 --- a/monggregate/operators/array/last.py +++ b/monggregate/operators/array/last.py @@ -120,9 +120,9 @@ def statement(self) -> dict: "$last":self.expression }) -def last(array:Any)->dict: +def last(array:Any)->Last: """Returns a $last statement""" return Last( expression = array - ).statement + ) diff --git a/monggregate/operators/array/max_n.py b/monggregate/operators/array/max_n.py index df96dbe..f27a993 100644 --- a/monggregate/operators/array/max_n.py +++ b/monggregate/operators/array/max_n.py @@ -72,10 +72,10 @@ def statement(self) -> dict: } }) -def max_n(expression:Any, limit:Any=1)->dict: +def max_n(expression:Any, limit:Any=1)->MaxN: """Returns a $maxN statement""" return MaxN( expression = expression, limit = limit - ).statement + ) diff --git a/monggregate/operators/array/min_n.py b/monggregate/operators/array/min_n.py index 5449bc9..e392bf3 100644 --- a/monggregate/operators/array/min_n.py +++ b/monggregate/operators/array/min_n.py @@ -72,10 +72,10 @@ def statement(self) -> dict: } }) -def min_n(expression:Any, limit:Any=1)->dict: +def min_n(expression:Any, limit:Any=1)->MinN: """Returns a $minN statement""" return MinN( expression = expression, limit = limit - ).statement + ) diff --git a/monggregate/operators/array/size.py b/monggregate/operators/array/size.py index d43eaaf..64b4dbe 100644 --- a/monggregate/operators/array/size.py +++ b/monggregate/operators/array/size.py @@ -46,9 +46,9 @@ def statement(self) -> dict: "$size":self.expression }) -def size(array:Any)->dict: +def size(array:Any)->Size: """Returns a $size statement""" return Size( expression = array - ).statement + ) diff --git a/monggregate/operators/array/sort_array.py b/monggregate/operators/array/sort_array.py index 316f6e9..dd325a9 100644 --- a/monggregate/operators/array/sort_array.py +++ b/monggregate/operators/array/sort_array.py @@ -108,10 +108,10 @@ def statement(self) -> dict: } }) -def sort_array(expression:Any, sort_by:dict[str, Literal[1, -1]])->dict: +def sort_array(expression:Any, sort_by:dict[str, Literal[1, -1]])->SortArray: """Returns a $first statement""" return SortArray( expression = expression, sort_by = sort_by - ).statement + ) diff --git a/monggregate/operators/boolean/and_.py b/monggregate/operators/boolean/and_.py index ded177f..e5262ed 100644 --- a/monggregate/operators/boolean/and_.py +++ b/monggregate/operators/boolean/and_.py @@ -72,10 +72,10 @@ def statement(self) -> dict: "$and" : self.expressions }) -def and_(*args:Any)->dict: +def and_(*args:Any)->And: """Returns an $and statement""" return And( expressions=list(args) - ).statement + ) diff --git a/monggregate/operators/boolean/not_.py b/monggregate/operators/boolean/not_.py index 052291f..f4b8d6a 100644 --- a/monggregate/operators/boolean/not_.py +++ b/monggregate/operators/boolean/not_.py @@ -47,9 +47,9 @@ def statement(self) -> dict: "$not" : [self.expression] }) -def not_(expression:Any)->dict: +def not_(expression:Any)->Not: """Returns an $not statement""" return Not( expression=expression - ).statement + ) diff --git a/monggregate/operators/boolean/or_.py b/monggregate/operators/boolean/or_.py index 82ffc26..5e49b8b 100644 --- a/monggregate/operators/boolean/or_.py +++ b/monggregate/operators/boolean/or_.py @@ -48,9 +48,9 @@ def statement(self) -> dict: "$or" : self.expressions }) -def or_(*args:Any)->dict: +def or_(*args:Any)->Or: """Returns an $or statement""" return Or( expressions=list(args) - ).statement + ) diff --git a/monggregate/operators/comparison/cmp.py b/monggregate/operators/comparison/cmp.py index b21f3f1..191a9e6 100644 --- a/monggregate/operators/comparison/cmp.py +++ b/monggregate/operators/comparison/cmp.py @@ -50,12 +50,12 @@ def statement(self) -> dict: Cmp = Compare -def compare(left:Any, right:Any)->dict: +def compare(left:Any, right:Any)->Compare: """Returns a $cmp stament""" return Compare( left=left, right=right - ).statement + ) cmp = compare \ No newline at end of file diff --git a/monggregate/operators/comparison/eq.py b/monggregate/operators/comparison/eq.py index 6439061..21d5cc6 100644 --- a/monggregate/operators/comparison/eq.py +++ b/monggregate/operators/comparison/eq.py @@ -49,12 +49,12 @@ def statement(self) -> dict: Eq = Equal -def equal(left:Any, right:Any)->dict: +def equal(left:Any, right:Any)->Equal: """Creates an $eq statement""" return Equal( left=left, right=right - ).statement + ) eq = equal diff --git a/monggregate/operators/comparison/gt.py b/monggregate/operators/comparison/gt.py index dc13ae4..05d31e0 100644 --- a/monggregate/operators/comparison/gt.py +++ b/monggregate/operators/comparison/gt.py @@ -47,12 +47,12 @@ def statement(self) -> dict: Gt = GreatherThan -def greather_than(left:Any, right:Any)->dict: +def greather_than(left:Any, right:Any)->GreatherThan: """Returns a $gt statement""" return GreatherThan( left = left, right = right - ).statement + ) gt= greather_than \ No newline at end of file diff --git a/monggregate/operators/comparison/gte.py b/monggregate/operators/comparison/gte.py index 239073c..91bb5f7 100644 --- a/monggregate/operators/comparison/gte.py +++ b/monggregate/operators/comparison/gte.py @@ -45,12 +45,12 @@ def statement(self) -> dict: Gte = GreatherThanOrEqual -def grether_than_or_equal(left:Any, right:Any)->dict: +def grether_than_or_equal(left:Any, right:Any)->GreatherThanOrEqual: """Returns a $gte statement""" return GreatherThanOrEqual( left=left, right=right - ).statement + ) gte = grether_than_or_equal diff --git a/monggregate/operators/comparison/lt.py b/monggregate/operators/comparison/lt.py index d970d87..d0c1d28 100644 --- a/monggregate/operators/comparison/lt.py +++ b/monggregate/operators/comparison/lt.py @@ -44,12 +44,12 @@ def statement(self) -> dict: Lt = LowerThan -def lower_than(left:Any, right:Any)->dict: +def lower_than(left:Any, right:Any)->LowerThan: """Returns a $lt statement""" return LowerThan( left=left, right=right - ).statement + ) lt = lower_than diff --git a/monggregate/operators/comparison/lte.py b/monggregate/operators/comparison/lte.py index 136f3a9..0b293ef 100644 --- a/monggregate/operators/comparison/lte.py +++ b/monggregate/operators/comparison/lte.py @@ -45,12 +45,12 @@ def statement(self) -> dict: Lte = LowerThanOrEqual -def lower_than_or_equal(left:Any, right:Any)->dict: +def lower_than_or_equal(left:Any, right:Any)->LowerThanOrEqual: """Returns a $lt statement""" return LowerThanOrEqual( left=left, right=right - ).statement + ) lte = lower_than_or_equal diff --git a/monggregate/operators/comparison/ne.py b/monggregate/operators/comparison/ne.py index 7cd5593..ece5ce0 100644 --- a/monggregate/operators/comparison/ne.py +++ b/monggregate/operators/comparison/ne.py @@ -48,12 +48,12 @@ def statement(self) -> dict: Ne = NotEqual -def not_equal(left:Any, right:Any)->dict: +def not_equal(left:Any, right:Any)->NotEqual: """Returns a $ne statement""" return NotEqual( left=left, right=right - ).statement + ) ne = not_equal diff --git a/monggregate/operators/objects/merge_objects.py b/monggregate/operators/objects/merge_objects.py index 9941eb6..356db8b 100644 --- a/monggregate/operators/objects/merge_objects.py +++ b/monggregate/operators/objects/merge_objects.py @@ -63,7 +63,7 @@ def statement(self) -> dict: "$mergeObjects" : self.expression }) -def merge_objects(expression:Any)->dict: +def merge_objects(expression:Any)->MergeObjects: """Returns a merge_objects statement""" - return MergeObjects(expression=expression).statement + return MergeObjects(expression=expression) diff --git a/monggregate/operators/objects/object_to_array.py b/monggregate/operators/objects/object_to_array.py index 81a12f7..874b55a 100644 --- a/monggregate/operators/objects/object_to_array.py +++ b/monggregate/operators/objects/object_to_array.py @@ -48,7 +48,7 @@ def statement(self) -> dict: "$objectToArray" : self.expression }) -def object_to_array(expression:Any)->dict: +def object_to_array(expression:Any)->ObjectToArray: """Returns a *objectToArray statement""" - return ObjectToArray(expression=expression).statement + return ObjectToArray(expression=expression) From 74fb04c8530e02ed371cc0b0cbd40f0d0f20ae25 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 11 Aug 2023 11:23:08 +0200 Subject: [PATCH 04/26] MVP of S singleton to represent MongoDB $ sign --- monggregate/base.py | 2 +- monggregate/operators/__init__.py | 2 + monggregate/operators/dollar.py | 74 ++++++++++++++----------- monggregate/operators/type_/__init__.py | 1 + monggregate/operators/type_/type_.py | 24 ++++++++ 5 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 monggregate/operators/type_/__init__.py create mode 100644 monggregate/operators/type_/type_.py diff --git a/monggregate/base.py b/monggregate/base.py index e462c62..6309cb0 100644 --- a/monggregate/base.py +++ b/monggregate/base.py @@ -47,7 +47,7 @@ class Config(pyd.BaseConfig): smart_union = True #alias_generator = camelize - +# TODO : Use TypeGuard here to improve type checking def isbasemodel(instance:Any)->bool: """Returns true if instance is an instance of BaseModel""" diff --git a/monggregate/operators/__init__.py b/monggregate/operators/__init__.py index 5929961..17d4340 100644 --- a/monggregate/operators/__init__.py +++ b/monggregate/operators/__init__.py @@ -47,3 +47,5 @@ MergeObjects, merge_objects, ObjectToArray, object_to_array ) + +from monggregate.operators.type_ import type_ \ No newline at end of file diff --git a/monggregate/operators/dollar.py b/monggregate/operators/dollar.py index 671b6b4..ee6b3ee 100644 --- a/monggregate/operators/dollar.py +++ b/monggregate/operators/dollar.py @@ -9,15 +9,14 @@ array, comparison, objects, - boolean + boolean, + type_ ) # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with # it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance -# TODO : Do not return statement directly but rather return a class that can be used to build the statement -# TODO : Fix return types -class Dollar(BaseModel): +class Dollar: """Base class for all $ functions""" # Operators @@ -26,49 +25,49 @@ class Dollar(BaseModel): # Accumulators # -------------------------- @classmethod - def avg(cls, expression:Any)->str: + def avg(cls, expression:Any)->accumulators.Avg: """Returns the $avg operator""" return accumulators.avg(expression) @classmethod - def count(cls)->str: + def count(cls)->accumulators.Count: """Returns the $count operator""" return accumulators.count() @classmethod - def first(cls, expression:Any)->str: + def first(cls, expression:Any)->accumulators.First: """Returns the $first operator""" return accumulators.first(expression) @classmethod - def last(cls, expression:Any)->str: + def last(cls, expression:Any)->accumulators.Last: """Returns the $last operator""" return accumulators.last(expression) @classmethod - def max(cls, expression:Any)->str: + def max(cls, expression:Any)->accumulators.Max: """Returns the $max operator""" return accumulators.max(expression) @classmethod - def min(cls, expression:Any)->str: + def min(cls, expression:Any)->accumulators.Min: """Returns the $min operator""" return accumulators.min(expression) @classmethod - def push(cls, expression:Any)->str: + def push(cls, expression:Any)->accumulators.Push: """Returns the $push operator""" return accumulators.push(expression) @classmethod - def sum(cls, expression:Any)->str: + def sum(cls, expression:Any)->accumulators.Sum: """Returns the $sum operator""" return accumulators.sum(expression) @@ -76,44 +75,44 @@ def sum(cls, expression:Any)->str: # Array # -------------------------- @classmethod - def array_to_object(cls, expression:Any)->str: + def array_to_object(cls, expression:Any)->array.ArrayToObject: """Returns the $arrayToObject operator""" return array.array_to_object(expression) # TODO : Workout aliases @classmethod - def filter(cls, expression:Any,*, let:str, query:Any, limit:int|None=None)->str: + def filter(cls, expression:Any,*, let:str, query:Any, limit:int|None=None)->array.Filter: """Returns the $filter operator""" return array.filter(expression, let, query, limit) @classmethod - def in_(cls, left:Any, right:Any)->str: + def in_(cls, left:Any, right:Any)->array.In: """Returns the $in operator""" return array.in_(left, right) @classmethod - def is_array(cls, expression:Any)->str: + def is_array(cls, expression:Any)->array.IsArray: """Returns the $isArray operator""" return array.is_array(expression) @classmethod - def max_n(cls, expression:Any, n:int=1)->str: + def max_n(cls, expression:Any, n:int=1)->array.MaxN: """Returns the $max operator""" return array.max_n(expression, n) @classmethod - def min_n(cls, expression:Any, n:int=1)->str: + def min_n(cls, expression:Any, n:int=1)->array.MinN: """Returns the $min operator""" return array.min_n(expression, n) @classmethod - def size(cls, expression:Any)->str: + def size(cls, expression:Any)->array.Size: """Returns the $size operator""" return array.size(expression) @@ -121,7 +120,7 @@ def size(cls, expression:Any)->str: # 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]])->str: + def sort_array(cls, expression:Any, sort_spec:dict[str, Literal[1,-1]])->array.SortArray: """Returns the $sort operator""" return array.sort_array(expression, sort_spec) @@ -129,43 +128,43 @@ def sort_array(cls, expression:Any, sort_spec:dict[str, Literal[1,-1]])->str: # Comparison # -------------------------- @classmethod - def cmp(cls, left:Any, right:Any)->str: + def cmp(cls, left:Any, right:Any)->comparison.Cmp: """Returns the $cmp operator""" return comparison.cmp(left, right) @classmethod - def eq(cls, left:Any, right:Any)->str: + def eq(cls, left:Any, right:Any)->comparison.Eq: """Returns the $eq operator""" return comparison.eq(left, right) @classmethod - def gt(cls, left:Any, right:Any)->str: + def gt(cls, left:Any, right:Any)->comparison.Gt: """Returns the $gt operator""" return comparison.gt(left, right) @classmethod - def gte(cls, left:Any, right:Any)->str: + def gte(cls, left:Any, right:Any)->comparison.Gte: """Returns the $gte operator""" return comparison.gte(left, right) @classmethod - def lt(cls, left:Any, right:Any)->str: + def lt(cls, left:Any, right:Any)->comparison.Lt: """Returns the $lt operator""" return comparison.lt(left, right) @classmethod - def lte(cls, left:Any, right:Any)->str: + def lte(cls, left:Any, right:Any)->comparison.Lte: """Returns the $lte operator""" return comparison.lte(left, right) @classmethod - def ne(cls, left:Any, right:Any)->str: + def ne(cls, left:Any, right:Any)->comparison.Ne: """Returns the $ne operator""" return comparison.ne(left, right) @@ -173,13 +172,13 @@ def ne(cls, left:Any, right:Any)->str: # Objects # -------------------------- @classmethod - def merge_objects(cls, *args:Any)->str: + def merge_objects(cls, *args:Any)->objects.MergeObjects: """Returns the $mergeObjects operator""" return objects.merge_objects(*args) @classmethod - def object_to_array(cls, expression:Any)->str: + def object_to_array(cls, expression:Any)->objects.ObjectToArray: """Returns the $objectToArray operator""" return objects.object_to_array(expression) @@ -187,20 +186,31 @@ def object_to_array(cls, expression:Any)->str: # Boolean # -------------------------- @classmethod - def and_(cls, *args:Any)->str: + def and_(cls, *args:Any)->boolean.And: """Returns the $and operator""" return boolean.and_(*args) @classmethod - def or_(cls, *args:Any)->str: + def or_(cls, *args:Any)->boolean.Or: """Returns the $or operator""" return boolean.or_(*args) @classmethod - def not_(cls, expression:Any)->str: + def not_(cls, expression:Any)->boolean.Not: """Returns the $not operator""" return boolean.not_(expression) + # Type + # -------------------------- + @classmethod + def type_(cls, expression:Any)->type_.Type_: + """Returns the $type operator""" + + return type_.type_(expression) + + +# TODO : Make below instance a singleton +S = Dollar() diff --git a/monggregate/operators/type_/__init__.py b/monggregate/operators/type_/__init__.py new file mode 100644 index 0000000..54ff1f3 --- /dev/null +++ b/monggregate/operators/type_/__init__.py @@ -0,0 +1 @@ +"""xxx""" \ No newline at end of file diff --git a/monggregate/operators/type_/type_.py b/monggregate/operators/type_/type_.py new file mode 100644 index 0000000..4ef4775 --- /dev/null +++ b/monggregate/operators/type_/type_.py @@ -0,0 +1,24 @@ +"""xxxx""" + +from typing import Any +from monggregate.base import BaseModel + +class Type_(BaseModel): + """xxxx""" + + expression:Any + + @property + def statement(self)->dict: + + return self.resolve({ + "$type":self.expression + }) + + +def type_(expression:Any)->Type_: + """xxxx""" + + return Type_( + expression=expression + ) From 1bab62cffc6f61ec9f63d3315124023eb180c575 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 11 Aug 2023 22:58:55 +0200 Subject: [PATCH 05/26] Roadmap of next operators --- .../operators/accumulators/__init__.py | 16 ++++++++++++ monggregate/operators/arithmetic/__init__.py | 19 ++++++++++++++ monggregate/operators/array/__init__.py | 14 +++++++++++ monggregate/operators/conditional/__init__.py | 6 +++++ monggregate/operators/custom/__init__.py | 5 ++++ monggregate/operators/data_size/__init__.py | 5 ++++ monggregate/operators/date/__init__.py | 25 +++++++++++++++++++ monggregate/operators/objects/__init__.py | 3 +++ monggregate/operators/strings/__init__.py | 25 +++++++++++++++++++ 9 files changed, 118 insertions(+) create mode 100644 monggregate/operators/arithmetic/__init__.py create mode 100644 monggregate/operators/conditional/__init__.py create mode 100644 monggregate/operators/custom/__init__.py create mode 100644 monggregate/operators/data_size/__init__.py create mode 100644 monggregate/operators/date/__init__.py create mode 100644 monggregate/operators/strings/__init__.py diff --git a/monggregate/operators/accumulators/__init__.py b/monggregate/operators/accumulators/__init__.py index fa5288c..ba66c9a 100644 --- a/monggregate/operators/accumulators/__init__.py +++ b/monggregate/operators/accumulators/__init__.py @@ -8,3 +8,19 @@ from monggregate.operators.accumulators.max import Max, max from monggregate.operators.accumulators.push import Push, push from monggregate.operators.accumulators.sum import Sum, sum + +# TODO : +# * $accumulator +# * $addToSet +# * $bottom +# * $bottomN +# * $firstN +# * $lastN +# * $maxN +# * $mergeObjects +# * $stdDedPop +# * $stdDevSamp +# * $top +# * $topN +# +# \ No newline at end of file diff --git a/monggregate/operators/arithmetic/__init__.py b/monggregate/operators/arithmetic/__init__.py new file mode 100644 index 0000000..f0c56f7 --- /dev/null +++ b/monggregate/operators/arithmetic/__init__.py @@ -0,0 +1,19 @@ +"""xxx""" + +# TODO: +# * $abs +# * $add +# * $ceil +# * $divide +# * $exp +# * $floor +# * $ln +# * $log +# * $log10 +# * $mod +# * $multiply +# * $pow +# * $round +# * $sqrt +# * $subtract +# * $trunc diff --git a/monggregate/operators/array/__init__.py b/monggregate/operators/array/__init__.py index 8c1ed48..161fcf9 100644 --- a/monggregate/operators/array/__init__.py +++ b/monggregate/operators/array/__init__.py @@ -10,3 +10,17 @@ from monggregate.operators.array.min_n import MinN, min_n from monggregate.operators.array.size import Size, size from monggregate.operators.array.sort_array import SortArray, sort_array + +# TODO: +# * $arrayElemAt +# * $concatArrays +# * $indexOfArray +# * $map +# * $maxN +# * $minN +# * $objectToArray +# * $range +# * $reduce +# * $reverseArray +# * $slice +# * $zip \ No newline at end of file diff --git a/monggregate/operators/conditional/__init__.py b/monggregate/operators/conditional/__init__.py new file mode 100644 index 0000000..9a49289 --- /dev/null +++ b/monggregate/operators/conditional/__init__.py @@ -0,0 +1,6 @@ +"""xxx""" + +# TODO: +# * $cond +# * $ifNull +# * $switch diff --git a/monggregate/operators/custom/__init__.py b/monggregate/operators/custom/__init__.py new file mode 100644 index 0000000..085356f --- /dev/null +++ b/monggregate/operators/custom/__init__.py @@ -0,0 +1,5 @@ +"""xxx""" + +# TODO +# * $accumulator +# * $function diff --git a/monggregate/operators/data_size/__init__.py b/monggregate/operators/data_size/__init__.py new file mode 100644 index 0000000..eb28a12 --- /dev/null +++ b/monggregate/operators/data_size/__init__.py @@ -0,0 +1,5 @@ +"""xxx""" + +# TODO +# * $binarySize +# * $bsonSize \ No newline at end of file diff --git a/monggregate/operators/date/__init__.py b/monggregate/operators/date/__init__.py new file mode 100644 index 0000000..352c060 --- /dev/null +++ b/monggregate/operators/date/__init__.py @@ -0,0 +1,25 @@ +"""xxx""" + +# TODO: +# $dateAdd +# $dateDiff +# $dateFromParts +# $dateFromString +# $dateSubtract +# $dateToParts +# $dateToString +# $dateTrunc +# $dayOfMonth +# $dayOfWeek +# $dayOfYear +# $hour +# $isoDayOfWeek +# $isoWeek +# $isoWeekYear +# $millisecond +# $minute +# $month +# $second +# $toDate +# $week +# $year diff --git a/monggregate/operators/objects/__init__.py b/monggregate/operators/objects/__init__.py index 5896a91..c0fe5bc 100644 --- a/monggregate/operators/objects/__init__.py +++ b/monggregate/operators/objects/__init__.py @@ -2,3 +2,6 @@ from monggregate.operators.objects.merge_objects import MergeObjects, merge_objects from monggregate.operators.objects.object_to_array import ObjectToArray, object_to_array + +# TODO: +# * $setField diff --git a/monggregate/operators/strings/__init__.py b/monggregate/operators/strings/__init__.py new file mode 100644 index 0000000..7224e77 --- /dev/null +++ b/monggregate/operators/strings/__init__.py @@ -0,0 +1,25 @@ +"""xxxx""" + +# $concat +# $dateFromString +# $dateToString +# $indexOfBytes +# $indexOfCP +# $ltrim +# $regexFind +# $regexFindAll +# $regexMatch +# $replaceOne +# $replaceAll +# $rtrim +# $split +# $strLenBytes +# $strLenCP +# $strcasecmp +# $substr +# $substrBytes +# $substrCP +# $toLower +# $toString +# $trim +# $toUpper From 50e9332901a2708f02cbb3829f0cb2e8e6a5b847 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sat, 12 Aug 2023 21:55:08 +0200 Subject: [PATCH 06/26] Added getattr method on Dollar class + Some ground works on test --- monggregate/operators/dollar.py | 16 +++++++++ test/test_dollar.py | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 test/test_dollar.py diff --git a/monggregate/operators/dollar.py b/monggregate/operators/dollar.py index ee6b3ee..356bd14 100644 --- a/monggregate/operators/dollar.py +++ b/monggregate/operators/dollar.py @@ -19,6 +19,22 @@ class Dollar: """Base class for all $ functions""" + # Any below should be replaced by a Union of + # all operators or by Typevar bounded by Operator + def __getattr__(self, name)->str|Any: + """Overloads the __getattr__ method. + Return the name of the attribute with a $ prepended to it + (when it's not a method or an attribute of the classe) + + """ + + if name not in Dollar.__dict__: + output = f"${name}" + else: + output = Dollar.__dict__[name] + + return output + # Operators # ------------------------------ diff --git a/test/test_dollar.py b/test/test_dollar.py new file mode 100644 index 0000000..21ce732 --- /dev/null +++ b/test/test_dollar.py @@ -0,0 +1,61 @@ +"""Module to test dollar singleton class""" + +from monggregate.operators.dollar import S +from pydantic import BaseModel + +class AccessAll(BaseModel): + x:int = 1 + + def merge_objects(self, *args, **kwargs): + + + kwargs.update({"args":args}) + return kwargs + + def __getattr__(self, name): + + if name not in AccessAll.__dict__: + output = f"${name}" + else: + output = AccessAll.__dict__[name] + + return output + +def test_access_all()->None: + """Tests the access all class""" + + assert AccessAll().name == "$name" + assert AccessAll().age == "$age" + assert AccessAll().address == "$address" + assert AccessAll().x == 1 + assert AccessAll().merge_objects(1,2) == {"args":(1,2)} + + +def test_simple_expressions()->None: + """Tests some simple expressions""" + + assert S.sum(1).statement == {"$sum": 1} + + assert S.type_("number").statement == {"$type": "number"} + + #S.avg(S.multiply(S.price, S.quantity)).statement == {"$avg": [{"$multiply": ["$price", "$quantity"]}]} + + # S.avg(S.quantity).statement == {"$avg": "$quantity"} + + # S.first(S.date).statement == {"$first": "$date"} + + # S.merge_objects([ + # S.array_elem_at(S.from_items, 0),SS.ROOT + # ]).statement == {"$mergeObjects": [{"$arrayElemAt": ["$fromItems", 0]}, "$$ROOT"]} + + # S.map( + # input=S.quizzes, + # as_="grade", + # in_=S.add(SS.grade, 2) + # ).statement == {"$map": {"input": "$quizzes", "as": "grade", "in": {"$add": ["$$grade", 2]}}} + + +if __name__ == "__main__": + test_access_all() + test_simple_expressions() + print("Everything passed") \ No newline at end of file From 1d49de76bde1a971fa027f24b9254571a1d62ba0 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sat, 12 Aug 2023 22:03:21 +0200 Subject: [PATCH 07/26] Fixed tests taht brok following change on operators mirror functions --- test/test_operators_accumulators.py | 16 ++++++++-------- test/test_operators_array.py | 20 ++++++++++---------- test/test_operators_boolean.py | 6 +++--- test/test_operators_comparison.py | 14 +++++++------- test/test_operators_objects.py | 4 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/test/test_operators_accumulators.py b/test/test_operators_accumulators.py index fa2b201..3eca9fa 100644 --- a/test/test_operators_accumulators.py +++ b/test/test_operators_accumulators.py @@ -32,7 +32,7 @@ def test_average(self)->None: # Functional test # --------------------- - assert average_op.statement == average([1, 2, 3, 4]) == { + assert average_op.statement == average([1, 2, 3, 4]).statement == { "$avg" : [1, 2, 3, 4] } @@ -47,7 +47,7 @@ def test_count(self)->None: # Functional test # --------------------- - assert count_op.statement == count() == { + assert count_op.statement == count().statement == { "$count" : {} } @@ -67,7 +67,7 @@ def test_first(self)->None: # Functional test # --------------------- - assert first_op.statement == first([1, 2, 3, 4]) == { + assert first_op.statement == first([1, 2, 3, 4]).statement == { "$first" : [1, 2, 3, 4] } @@ -86,7 +86,7 @@ def test_last(self)->None: # Functional test # --------------------- - assert last_op.statement == last([1, 2, 3, 4]) == { + assert last_op.statement == last([1, 2, 3, 4]).statement == { "$last" : [1, 2, 3, 4] } @@ -104,7 +104,7 @@ def test_max(self)->None: # Functional test # --------------------- - assert max_op.statement == max([1, 2, 3, 4]) == { + assert max_op.statement == max([1, 2, 3, 4]).statement == { "$max" : [1, 2, 3, 4] } @@ -122,7 +122,7 @@ def test_min(self)->None: # Functional test # --------------------- - assert min_op.statement == min([1, 2, 3, 4]) == { + assert min_op.statement == min([1, 2, 3, 4]).statement == { "$min" : [1, 2, 3, 4] } @@ -146,7 +146,7 @@ def test_push(self)->None: assert push_op.statement == push({ "item" : "$item", "quantity" : "$quantity" - }) == { + }).statement == { "$push" : { "item" : "$item", "quantity" : "$quantity" @@ -168,6 +168,6 @@ def test_sum(self)->None: # Functional test # --------------------- - assert sum_op.statement == sum([1, 2, 3, {"$literal":4}]) == { + assert sum_op.statement == sum([1, 2, 3, {"$literal":4}]).statement == { "$sum" : [1, 2, 3, {"$literal":4}] } diff --git a/test/test_operators_array.py b/test/test_operators_array.py index 555caf8..705ba3b 100644 --- a/test/test_operators_array.py +++ b/test/test_operators_array.py @@ -37,7 +37,7 @@ def test_array_to_object(self)->None: # Functional test # ----------------- - assert array_to_object_op.statement == array_to_object("$dimensions") == { + assert array_to_object_op.statement == array_to_object("$dimensions").statement == { "$arrayToObject" :"$dimensions" } @@ -62,7 +62,7 @@ def test_filter(self)->None: [1, 2, 3, 4], "num", greather_than("$$num", 2) - ) == { + ).statement == { "$filter" : { "input" : [1, 2, 3, 4], "as" : "num", @@ -87,7 +87,7 @@ def test_first(self)->None: # Functional test # --------------------- - assert first_op.statement == first([1, 2, 3, 4]) == { + assert first_op.statement == first([1, 2, 3, 4]).statement == { "$first" : [1, 2, 3, 4] } @@ -105,7 +105,7 @@ def test_in(self)->None: # Functional test # ----------------------- - assert in_op.statement == in_(1, [1, 2, 3, 4]) == { + assert in_op.statement == in_(1, [1, 2, 3, 4]).statement == { "$in" : [1, [1, 2, 3, 4]] } @@ -120,7 +120,7 @@ def test_is_array(self)->None: # Functional test # -------------------- - assert is_array_op.statement == is_array([1, 2, 3, 4]) == { + assert is_array_op.statement == is_array([1, 2, 3, 4]).statement == { "$isArray" : [1, 2, 3, 4] } @@ -138,7 +138,7 @@ def test_last(self)->None: # Functional test # --------------------- - assert last_op.statement == last([1, 2, 3, 4]) == { + assert last_op.statement == last([1, 2, 3, 4]).statement == { "$last" : [1, 2, 3, 4] } @@ -157,7 +157,7 @@ def test_max_n(self)->None: # Functional test # -------------------- - assert max_n_op.statement == max_n([1, 2, 3, 4], 1) == { + assert max_n_op.statement == max_n([1, 2, 3, 4], 1).statement == { "$maxN" : { "n" : 1, "input" : [1, 2, 3, 4] @@ -178,7 +178,7 @@ def test_min_n(self)->None: # Functional test # -------------------- - assert min_n_op.statement == min_n([1, 2, 3, 4], 1) == { + assert min_n_op.statement == min_n([1, 2, 3, 4], 1).statement == { "$minN" : { "n" : 1, "input" : [1, 2, 3, 4] @@ -198,7 +198,7 @@ def test_size(self)->None: # Functional test # --------------------- - assert size_op.statement == size([1, 2, 3, 4]) == { + assert size_op.statement == size([1, 2, 3, 4]).statement == { "$size" : [1, 2, 3, 4] } @@ -217,7 +217,7 @@ def test_sort_array(self)->None: # Functional test # ---------------- - assert sort_array_op.statement == sort_array("$team", {"name":1}) == { + assert sort_array_op.statement == sort_array("$team", {"name":1}).statement == { "$sortArray" : { "input" : "$team", "sortBy" : { diff --git a/test/test_operators_boolean.py b/test/test_operators_boolean.py index 4b3d12d..a9b664b 100644 --- a/test/test_operators_boolean.py +++ b/test/test_operators_boolean.py @@ -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])) == { + assert and_operator.statement == and_(First(expression=[1, 2, 3, 4]), First(expression=[4, 5, 6, 7])).statement == { "$and" : [ { "$first" : [1, 2, 3, 4] @@ -55,7 +55,7 @@ def test_not(self)->None: # Functional test # -------------------- - assert not_op.statement == not_(greather_than("$qty", 250)) == { + assert not_op.statement == not_(greather_than("$qty", 250)).statement == { "$not" : [{"$gt":["$qty", 250]}] } @@ -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])) == { + assert or_operator.statement == or_(First(expression=[1, 2, 3, 4]), First(expression=[4, 5, 6, 7])).statement == { "$or" : [ { "$first" : [1, 2, 3, 4] diff --git a/test/test_operators_comparison.py b/test/test_operators_comparison.py index 5e501f5..a71ced2 100644 --- a/test/test_operators_comparison.py +++ b/test/test_operators_comparison.py @@ -32,7 +32,7 @@ def test_cmp(self)->None: # Functional test # ------------------- - assert cmp_op.statement == cmp("$qty", 250) == { + assert cmp_op.statement == cmp("$qty", 250).statement == { "$cmp" : ["$qty", 250] } @@ -50,7 +50,7 @@ def test_eq(self)->None: # Functional test # ------------------- - assert eq_op.statement == eq("$qty", 250) == { + assert eq_op.statement == eq("$qty", 250).statement == { "$eq" : ["$qty", 250] } @@ -68,7 +68,7 @@ def test_gt(self)->None: # Functional test # ------------------- - assert gt_op.statement == gt("$qty", 250) == { + assert gt_op.statement == gt("$qty", 250).statement == { "$gt" : ["$qty", 250] } @@ -86,7 +86,7 @@ def test_gte(self)->None: # Functional test # ------------------- - assert gte_op.statement == gte("$qty", 250) == { + assert gte_op.statement == gte("$qty", 250).statement == { "$gte" : ["$qty", 250] } @@ -104,7 +104,7 @@ def test_lt(self)->None: # Functional test # ------------------- - assert lt_op.statement == lt("$qty", 250) == { + assert lt_op.statement == lt("$qty", 250).statement == { "$lt" : ["$qty", 250] } @@ -122,7 +122,7 @@ def test_lte(self)->None: # Functional test # ------------------- - assert lte_op.statement == lte("$qty", 250) == { + assert lte_op.statement == lte("$qty", 250).statement == { "$lte" : ["$qty", 250] } @@ -140,6 +140,6 @@ def test_ne(self)->None: # Functional test # ------------------- - assert ne_op.statement == ne("$qty", 250) == { + assert ne_op.statement == ne("$qty", 250).statement == { "$ne" : ["$qty", 250] } diff --git a/test/test_operators_objects.py b/test/test_operators_objects.py index c016608..9ff4154 100644 --- a/test/test_operators_objects.py +++ b/test/test_operators_objects.py @@ -26,7 +26,7 @@ def test_merge_objects(self)->None: # Functinal test # --------------- - assert merge_objects_op.statement == merge_objects("$quantity") == { + assert merge_objects_op.statement == merge_objects("$quantity").statement == { "$mergeObjects" : "$quantity" } @@ -43,6 +43,6 @@ def test_object_to_array(self)->None: # Functinal test # --------------- - assert object_to_array_op.statement == object_to_array("$dimensions") == { + assert object_to_array_op.statement == object_to_array("$dimensions").statement == { "$objectToArray" : "$dimensions" } From ed24f6d72fc308673998ffb20abf101a2ca827d7 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 09:24:00 +0200 Subject: [PATCH 08/26] Moved fields outside of expressions sub-package --- monggregate/expressions/content.py | 2 +- monggregate/expressions/expressions.py | 2 +- monggregate/{expressions => }/fields.py | 0 monggregate/search/collectors/facet.py | 2 +- monggregate/stages/bucket.py | 2 +- monggregate/stages/bucket_auto.py | 2 +- monggregate/stages/count.py | 2 +- monggregate/stages/unset.py | 2 +- test/test_expressions.py | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename monggregate/{expressions => }/fields.py (100%) diff --git a/monggregate/expressions/content.py b/monggregate/expressions/content.py index bad1966..b78b270 100644 --- a/monggregate/expressions/content.py +++ b/monggregate/expressions/content.py @@ -1,7 +1,7 @@ """Module describing the content/statement of an expression""" from typing import Any -from monggregate.expressions.fields import FieldPath, Variable +from monggregate.fields import FieldPath, Variable #from monggregate.operators.operator import Operator # TODO : Distinguish between expressions evaluations (evaluated expressions) and lazy expressions (expressions that are not evaluated) diff --git a/monggregate/expressions/expressions.py b/monggregate/expressions/expressions.py index d154724..3e6b450 100644 --- a/monggregate/expressions/expressions.py +++ b/monggregate/expressions/expressions.py @@ -16,7 +16,7 @@ # Local imports # ---------------------------- -from monggregate.expressions.fields import FieldPath, Variable +from monggregate.fields import FieldPath, Variable from monggregate.expressions.content import Content from monggregate.operators.accumulators import( Avg, diff --git a/monggregate/expressions/fields.py b/monggregate/fields.py similarity index 100% rename from monggregate/expressions/fields.py rename to monggregate/fields.py diff --git a/monggregate/search/collectors/facet.py b/monggregate/search/collectors/facet.py index db19938..7bea453 100644 --- a/monggregate/search/collectors/facet.py +++ b/monggregate/search/collectors/facet.py @@ -169,7 +169,7 @@ from typing import Literal from monggregate.base import BaseModel, pyd -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName from monggregate.search.collectors.collector import SearchCollector from monggregate.search.operators import( Autocomplete, diff --git a/monggregate/stages/bucket.py b/monggregate/stages/bucket.py index e4db72a..749c12e 100644 --- a/monggregate/stages/bucket.py +++ b/monggregate/stages/bucket.py @@ -54,7 +54,7 @@ from monggregate.stages.stage import Stage from monggregate.expressions.content import Content, Const, Consts -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName from monggregate.operators.accumulators.accumulator import AccumulatorExpression from monggregate.utils import validate_field_path diff --git a/monggregate/stages/bucket_auto.py b/monggregate/stages/bucket_auto.py index c0e57f5..27dfd65 100644 --- a/monggregate/stages/bucket_auto.py +++ b/monggregate/stages/bucket_auto.py @@ -84,7 +84,7 @@ from monggregate.base import pyd from monggregate.stages.stage import Stage from monggregate.expressions.content import Content -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName from monggregate.operators.accumulators.accumulator import AccumulatorExpression from monggregate.utils import StrEnum, validate_field_path diff --git a/monggregate/stages/count.py b/monggregate/stages/count.py index 75f872f..e621134 100644 --- a/monggregate/stages/count.py +++ b/monggregate/stages/count.py @@ -35,7 +35,7 @@ """ from monggregate.stages.stage import Stage -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName class Count(Stage): diff --git a/monggregate/stages/unset.py b/monggregate/stages/unset.py index 4432029..ac4525f 100644 --- a/monggregate/stages/unset.py +++ b/monggregate/stages/unset.py @@ -48,7 +48,7 @@ """ from monggregate.stages.stage import Stage -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName class Unset(Stage): """ diff --git a/test/test_expressions.py b/test/test_expressions.py index 7f3e93f..a23065e 100644 --- a/test/test_expressions.py +++ b/test/test_expressions.py @@ -5,7 +5,7 @@ import pytest import pydantic from monggregate.base import pyd -from monggregate.expressions.fields import FieldName +from monggregate.fields import FieldName From ef420a3eff77d54589a9a81d44c361eab44a5aa4 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 09:40:24 +0200 Subject: [PATCH 09/26] Exploded expressions package --- .../aggregation_variables.py | 0 monggregate/{expressions => }/expressions.py | 136 +++++++----------- monggregate/expressions/__init__.py | 87 ----------- monggregate/expressions/content.py | 30 ---- monggregate/pipeline.py | 2 +- 5 files changed, 50 insertions(+), 205 deletions(-) rename monggregate/{expressions => }/aggregation_variables.py (100%) rename monggregate/{expressions => }/expressions.py (78%) delete mode 100644 monggregate/expressions/__init__.py delete mode 100644 monggregate/expressions/content.py diff --git a/monggregate/expressions/aggregation_variables.py b/monggregate/aggregation_variables.py similarity index 100% rename from monggregate/expressions/aggregation_variables.py rename to monggregate/aggregation_variables.py diff --git a/monggregate/expressions/expressions.py b/monggregate/expressions.py similarity index 78% rename from monggregate/expressions/expressions.py rename to monggregate/expressions.py index 3e6b450..4736ed1 100644 --- a/monggregate/expressions/expressions.py +++ b/monggregate/expressions.py @@ -68,78 +68,40 @@ class Expression(BaseModel): Expressions can be nested. """ - constant : int | float | str | bool | None = None - field : FieldPath | None = None - variable : Variable | None = None + content : Any - content : Content | None = None + @property + def statement(self)->Any: + return self.resolve(self.content) - @pyd.validator("variable", pre=True, always=True) + #---------------------------------------------------- + # Expression Internal Methods + #---------------------------------------------------- @classmethod - def validate_variable(cls, variable:str|None) -> Variable | None: - """Validates variable""" + def constant(cls, value:int | float | str | bool | None)->"Expression": + """Creates a constant expression.""" - if variable: - while not variable.startswith("$$"): - variable = "$" + variable - - return variable - - @pyd.validator("field", pre=True, always=True) + return cls(content=value) + @classmethod - def validate_field(cls, path:str|None)-> FieldPath | None: - """Validates field""" + def field(cls, name:str)->"Expression": + """Creates a field expression.""" - if path and not path.startswith("$"): - path = "$" + path + if not name.startswith("$"): + name = f"${name}" - return path + return cls(content=FieldPath(name=name)) + - @pyd.validator("content", pre=True, always=True) @classmethod - def set_content(cls, content:Any, values:dict)->Content: - """Sets content by parsing values and validates it""" - - - if content: - raise ValueError("Content should no be provided") - - constant = values.get("constant") - field = values.get("field") - variable = values.get("variable") + def variable(cls, name:str)->"Expression": + """Creates a variable expression.""" - if isinstance(constant, str): - if field: - content = {field:constant} - # elif variable: - # content = {field:variable} - else: - content = constant - elif field: - content = field - elif variable: - content = variable - else: - content = None - - return content - - - @property - def statement(self)->Any: - return self.content - - #---------------------------------------------------- - # Expression Internal Methods - #---------------------------------------------------- - def _clear(self)->None: - """Empties expression""" + while not variable.startswith("$$"): + variable = "$" + variable - self.constant = None - self.field = None - self.variable = None - #self.content = None # This would break most of the - # functions below + return cls(content=Variable(name=name)) + #--------------------------------------------------- # Logical Operators @@ -151,7 +113,7 @@ def __and__(self, other:"Expression")->"Expression": Overloads python bitwise AND operator ($). """ - #self._clear() + self.content = And(expressions=[self, other]).statement return self @@ -162,7 +124,7 @@ def __or__(self, other:"Expression")->"Expression": Overloads python bitwise OR operator (|). """ - #self._clear() + self.content = Or(expressions=[self, other]).statement return self @@ -173,7 +135,7 @@ def __invert__(self)->"Expression": Overloads the python bitwise NOT operator (~). """ - #self._clear() + self.content = Not(expression=self) return self @@ -187,7 +149,7 @@ def __eq__(self, other:"Expression")->"Expression": Overloads python Equal to operator (==). """ - #self._clear() + self.content = Eq(left=self, right=other) return self @@ -198,7 +160,7 @@ def __lt__(self, other:"Expression")->"Expression": Overloads python Less than operator (<). """ - #self._clear() + self.content = Lt(left=self, right=other) return self @@ -209,7 +171,7 @@ def __le__(self, other:"Expression")->"Expression": Overloads python Less than or equal to operator (<=). """ - #self._clear() + self.content = Lte(left=self, right=other) return self @@ -220,7 +182,7 @@ def __gt__(self, other:"Expression")->"Expression": Overloads python Greater than operator (>). """ - #self._clear() + self.content = Gt(left=self, right=other) return self @@ -231,7 +193,7 @@ def __ge__(self, other:"Expression")->"Expression": Overloads python Greather than or equal to operator (>=). """ - #self._clear() + self.content = Gte(left=self, right=other) return self @@ -242,14 +204,14 @@ def __ne__(self, other:"Expression")->"Expression": Overloads python Not equal to operator (!=). """ - #self._clear() + self.content = Ne(left=self, right=other) return self def compare(self, other:"Expression")->"Expression": """Creates a $cmp expression.""" - #self._clear() + self.content = Cmp(left=self, right=other) return self @@ -259,49 +221,49 @@ def compare(self, other:"Expression")->"Expression": def average(self)->"Expression": """Creates an $avg expression""" - #self._clear() + self.content = Avg(expression=self) return self def count(self)->"Expression": """Creates a $count expression""" - #self._clear() + self.content = Count() return self def first(self)->"Expression": """Creates a $first expression""" - #self._clear() + self.content = First(expression=self) return self def last(self)->"Expression": """Creates a $last expression""" - #self._clear() + self.content = Last(expression=self) return self def max(self)->"Expression": """Creates a $max expression""" - #self._clear() + self.content = Max(expression=self) return self def min(self)->"Expression": """Creates a $min expression""" - #self._clear() + self.content = Min(expression=self) return self def push(self)->"Expression": """Creates a $push expression""" - #self._clear() + self.content = Push(expression=self) return self @@ -317,14 +279,14 @@ def sum(self)->"Expression": def array_to_object(self)->"Expression": """Creates a $arrayToObject expression""" - #self._clear() + self.content = ArrayToObject(expression=self) return self def in_(self, right:"Expression")->"Expression": """Creates a $in operator""" - #self._clear() + self.content = In(left=self, right=right) return self @@ -336,7 +298,7 @@ def __contains__(self, right:"Expression")->"Expression": def filter(self, query:"Expression", let:str|None=None, limit:int|None=None)->"Expression": """"Creates a $filter expression""" - #self._clear() + self.content = Filter( expression=self, query=query, @@ -348,28 +310,28 @@ def filter(self, query:"Expression", let:str|None=None, limit:int|None=None)->"E def is_array(self)->"Expression": """Creates a $isArray expression""" - #self._clear() + self.content = IsArray(expression=self) return self def max_n(self, limit:int=1)->"Expression": """Creates a $maxN expression""" - #self._clear() + self.content = MaxN(expression=self, limit=limit) return self def min_n(self, limit:int=1)->"Expression": """Creates a $maxN expression""" - #self._clear() + self.content = MinN(expression=self, limit=limit) return self def sort_array(self, by:dict[str, Literal[1, -1]])->"Expression": """Creates a $sortArray expression""" - #self._clear() + self.content = SortArray(expression=self, by=by) return self @@ -379,14 +341,14 @@ def sort_array(self, by:dict[str, Literal[1, -1]])->"Expression": def merge_objects(self, )->"Expression": """Creates a $mergeObjects operator""" - #self._clear() + self.content = MergeObjects(expression=self) return self def object_to_array(self, )->"Expression": """Creates a $objectToArray operator""" - #self._clear() + self.content = ObjectToArray(expression=self) return self diff --git a/monggregate/expressions/__init__.py b/monggregate/expressions/__init__.py deleted file mode 100644 index c0611e2..0000000 --- a/monggregate/expressions/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Expressions Sub-package - -MongoDB Online Documentation - -Expressions ---------------------- - -Expressions can include field paths, literals, system variables, expression objects, and -expression operators. Expressions can be nested. - -Field Paths --------------------- -Aggregation expressions use field path to access fields in the input documents. -To specify a field path, prefix the field name or the dotted field name (if the field is in the embedded document) with a dollar sign $. -For example, "$user" to specify the field path for the user field or "$user.name" to specify the field path to "user.name" field. - -"$" is equivalent to "$$CURRENT." where the -CURRENT is a system variable that defaults to the root of the current object, unless stated otherwise in specific stages. - -Aggregation Variables ---------------------- -MongoDB provides various aggregation system variables for use in expressions. To access variables, prefix the variable name with $$. For example: - -For a more detailed description of these variables, see system variables. - -Literals -------------------- -Literals can be of any type. However, MongoDB parses string literals that start with a dollar sign $ as a path to a field and numeric/boolean literals -in expression objects as projection flags. To avoid parsing literals, use the -$literal expression. - -Expression Objects -------------------- - -Expression objects have the following form: - - >>> { : , ... } - -If the expressions are numeric or boolean literals, MongoDB treats the literals as projection flags (e.g. 1 or true to include the field), -valid only in the $project stage. To avoid treating numeric or boolean literals as projection flags, use the -$literal expression to wrap the numeric or boolean literals. - -Operator Expressions ------------------------ - -Operator expressions are similar to functions that take arguments. -In general, these expressions take an array of arguments and have the following form: - - >>> { : [ , ... ] } - -If operator accepts a single argument, you can omit the outer array designating the argument list: - - >>> { : } - -To avoid parsing ambiguity if the argument is a literal array, -you must wrap the literal array in a $literal expression or keep the outer array that designates the argument list. - -Arithmetic Expression Operators -------------------------------- - -Arithmetic expressions perform mathematic operations on numbers. Some arithmetic expressions can also support date arithmetic. - -See operators sub-package - -Array Expression Operators ------------------------------- -See operators sub-package - -Boolean Expression Operators ------------------------------- - -Boolean expressions evaluate their argument expressions as booleans and return a boolean as the result. -In addition to the false boolean value, Boolean expression evaluates as false the following: null, 0, and undefined values. T -he Boolean expression evaluates all other values as true, including non-zero numeric values and arrays. - -See operators sub-package - -Comparison Expression Operators -------------------------------- -Comparison expressions return a boolean excep for $cmp which returns a number. - -The comparison expressions take two argument expressions and compare both value and type, -using the specified BSON comparison order for values of different types. - - -""" diff --git a/monggregate/expressions/content.py b/monggregate/expressions/content.py deleted file mode 100644 index b78b270..0000000 --- a/monggregate/expressions/content.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Module describing the content/statement of an expression""" - -from typing import Any -from monggregate.fields import FieldPath, Variable -#from monggregate.operators.operator import Operator - -# TODO : Distinguish between expressions evaluations (evaluated expressions) and lazy expressions (expressions that are not evaluated) -Const = int | float | str | bool -Consts = list[int] | list[float] | list[str] | list[bool] -Content = dict[str, Any] | Variable | FieldPath | int | float | str | bool | list[int] | list[float] | list[str] | list[bool] -# dict[str, Any] above represents Operator Expressions, Expression Objects and nested expressions - -# In all these cases, an expression is just something that dynamically populates and returns a new JSON/BSON data type element, which can be one of: - - # * a Number (including integer, long, float, double, decimal128) - # * a String (UTF-8) - # * a Boolean - # * a DateTime (UTC) - # * an Array - # * an Object - -# In a nutshell (Vianney's words): Expressions are lazily evaluated objects - - -# The previously stated generalisation about $match not supporting expressions is actually inaccurate. -# Version 3.6 of MongoDB introduced the $expr operator, which you can embed within a $match stage (or in MQL) to leverage aggregation expressions when filtering records. -# Essentially, this enables MongoDB's query runtime (which executes an aggregation's $match) to reuse expressions provided by MongoDB's aggregation runtime. - -# Inside a $expr operator, you can include any composite expression fashioned from $ operator functions, -# $ field paths and $$ variables. A few situations demand having to use $expr from inside a $match stage. Examples include: \ No newline at end of file diff --git a/monggregate/pipeline.py b/monggregate/pipeline.py index dba2465..093ccf9 100644 --- a/monggregate/pipeline.py +++ b/monggregate/pipeline.py @@ -31,7 +31,7 @@ from monggregate.stages.search import OperatorLiteral from monggregate.search.operators.compound import Compound from monggregate.operators import MergeObjects -from monggregate.expressions.aggregation_variables import ROOT +from monggregate.aggregation_variables import ROOT from monggregate.utils import StrEnum From f0878470af76fa1021c143422c13dc712499bb87 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 09:42:35 +0200 Subject: [PATCH 10/26] Moved dollar one level up --- monggregate/__init__.py | 2 +- monggregate/{operators => }/dollar.py | 0 test/test_dollar.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename monggregate/{operators => }/dollar.py (100%) diff --git a/monggregate/__init__.py b/monggregate/__init__.py index bf9f1c6..a83c49f 100644 --- a/monggregate/__init__.py +++ b/monggregate/__init__.py @@ -1,7 +1,7 @@ """App Package""" from monggregate.pipeline import Pipeline -from monggregate.expressions.expressions import Expression +from monggregate.dollar import S __version__ = "0.15.0" __author__ = "Vianney Mixtur" diff --git a/monggregate/operators/dollar.py b/monggregate/dollar.py similarity index 100% rename from monggregate/operators/dollar.py rename to monggregate/dollar.py diff --git a/test/test_dollar.py b/test/test_dollar.py index 21ce732..873c02a 100644 --- a/test/test_dollar.py +++ b/test/test_dollar.py @@ -1,6 +1,6 @@ """Module to test dollar singleton class""" -from monggregate.operators.dollar import S +from monggregate.dollar import S from pydantic import BaseModel class AccessAll(BaseModel): From 2a43b8c45545d0ca496df181e5236bae782346e3 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 10:39:09 +0200 Subject: [PATCH 11/26] Deprecated/Removed index and aggregation_variables modules --- monggregate/aggregation_variables.py | 16 --- monggregate/dollar.py | 29 +++++ monggregate/index.py | 169 --------------------------- monggregate/operators/operator.py | 141 ++++++++++++++++++++++ 4 files changed, 170 insertions(+), 185 deletions(-) delete mode 100644 monggregate/aggregation_variables.py delete mode 100644 monggregate/index.py diff --git a/monggregate/aggregation_variables.py b/monggregate/aggregation_variables.py deleted file mode 100644 index 1092ee3..0000000 --- a/monggregate/aggregation_variables.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Constants and types definitions related to expressions""" - -# Package imports -# --------------------------- -from monggregate.index import AggregationVariableEnum - -# Constants (Aggregation Variables) -#------------------------------------------- -CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value -NOW = AggregationVariableEnum.NOW.value -ROOT = AggregationVariableEnum.ROOT.value -CURRENT = AggregationVariableEnum.CURRENT.value -REMOVE = AggregationVariableEnum.REMOVE.value -DESCEND = AggregationVariableEnum.DESCEND.value -PRUNE = AggregationVariableEnum.PRUNE.value -KEEP = AggregationVariableEnum.KEEP.value diff --git a/monggregate/dollar.py b/monggregate/dollar.py index 356bd14..697a11d 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -13,8 +13,34 @@ type_ ) +from monggregate.utils import StrEnum + +class AggregationVariableEnum(StrEnum): + """Enumeration of available aggregation variables""" + + NOW = "$$NOW" # Returns the current datetime value, + # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. + # (Available in 4.2+) + CLUSTER_TIME = "$$CLUSTER_TIME" # Returns the current timestamp value, which is same across all members of the deployment and remains constant throughout the aggregation pipeline. + # For replica sets and sharded clusters only. (Available in 4.2+) + ROOT = "$$ROOT" # References the root document, i.e. the top-level document. + CURRENT = "$$CURRENT" # References the start of the field path, which by default is ROOT but can be changed. + REMOVE = "$$REMOVE" # Allows for the conditional exclusion of fields. (Available in 3.6+) + DESCEND = "$$DESCEND" # One of the allowed results of a $redact expression. + PRUNE = "$$PRUNE" # One of the allowed results of a $redact expression. + KEEP = "$$KEEP" # One of the allowed results of a $redact expression.NOW = "$$NOW" # Returns the current datetime value, + # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. + # (Available in 4.2+) # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with # it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance +CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value +NOW = AggregationVariableEnum.NOW.value +ROOT = AggregationVariableEnum.ROOT.value +CURRENT = AggregationVariableEnum.CURRENT.value +REMOVE = AggregationVariableEnum.REMOVE.value +DESCEND = AggregationVariableEnum.DESCEND.value +PRUNE = AggregationVariableEnum.PRUNE.value +KEEP = AggregationVariableEnum.KEEP.value class Dollar: """Base class for all $ functions""" @@ -227,6 +253,9 @@ def type_(cls, expression:Any)->type_.Type_: return type_.type_(expression) +class DollarDollar: + """xxx""" + # TODO : Make below instance a singleton S = Dollar() diff --git a/monggregate/index.py b/monggregate/index.py deleted file mode 100644 index 3948eca..0000000 --- a/monggregate/index.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Module listing the available operators and stages""" - -# Package imports -# --------------------------- -from monggregate.utils import StrEnum - - -# Enums -# --------------------------- -class AggregationVariableEnum(StrEnum): - """Enumeration of available aggregation variables""" - - NOW = "$$NOW" # Returns the current datetime value, - # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # (Available in 4.2+) - CLUSTER_TIME = "$$CLUSTER_TIME" # Returns the current timestamp value, which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # For replica sets and sharded clusters only. (Available in 4.2+) - ROOT = "$$ROOT" # References the root document, i.e. the top-level document. - CURRENT = "$$CURRENT" # References the start of the field path, which by default is ROOT but can be changed. - REMOVE = "$$REMOVE" # Allows for the conditional exclusion of fields. (Available in 3.6+) - DESCEND = "$$DESCEND" # One of the allowed results of a $redact expression. - PRUNE = "$$PRUNE" # One of the allowed results of a $redact expression. - KEEP = "$$KEEP" # One of the allowed results of a $redact expression.NOW = "$$NOW" # Returns the current datetime value, - # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # (Available in 4.2+) - -class StageEnum(StrEnum): - """"Enumeration of available stages""" - -class OperatorEnum(StrEnum): - """Enumeration of available operators""" - - - ABS = "$abs" - ACCUMULATOR = "$accumulator" - ACOS = "$acos" - ACOSH = "$acosh" - ADD = "$add" - ADD_TO_SET = "$addToSet" - ALL_ELEMENTS_TRUE = "$allElementsTrue" - AND = "$and" - ANY_ELEMENTS_TRUE = "$anyElementsTrue" - ARRAY_ELEM_AT = "$arrayElemAt" - ARRAY_TO_OBJECT = "$arrayToObject" - ASIN = "$asin" - ASINH = "$asinh" - ATAN = "$atan" - ATAN2 = "$atan2" - ATANH = "$atanh" - AVG = "$avg" - BINARY_SIZE = "$binarySize" - BSON_SIZE ="$bsonSize" - CEIL = "$ceil" - CMP = "$cmp" - CONCAT = "$concat" - CONCAT_ARRAYS = "$concatArrays" - COND = "$cond" - CONVERT = "$convert" - COS = "$cos" - COSH = "$cosh" - DATE_FROM_PARTS = "$dateFromParts" - DATE_FROM_STRING = "$dateFromString" - DATE_TO_PARTS = "$dateToParts" - DATE_TO_STRING = "$dateToString" - DAY_OF_MONTH ="$dayOfMonth" - DAY_OF_WEEK = "$dayOfWeek" - DAY_OF_YEAR = "$dayOfYear" - DEGREES_TO_RADIANS = "$degreesToRadians" - DIVIDE = "$divide" - EQ = "$eq" - EXP = "$exp" - FILTER = "$filter" - FIRST = "$first" # two operators one for array one for accumulator - FLOOR = "$floor" - FUNCTION = "$function" - GET_FIELD = "$getField" - GT = "$gt" - GTE = "$gte" - HOUR = "$hour" - IF_NULL = "$ifNull" - IN = "$in" - INDEX_OF_ARRAY = "$indexOfArray" - INDEX_OF_BYTES = "$indexOfBytes" - INDEX_OF_CP = "$indexOfCP" - IS_ARRAY = "$isArray" - IS_NUMBER = "$isNumber" - ISO_DAY_OF_WEEK = "$isoDayOfWeek" - ISO_WEEK = "$isoWeek" - ISO_WEEK_YEAR ="$isoWeekYear" - LAST = "$last" # two operators one for array one for accumulator - LET ="$let" - LITERAL = "$literal" - LN = "$ln" - LOG = "$log" - LOG10 = "$log10" - LT = "$lt" - LTE = "$lte" - LTRIM = "$ltrim" - MAP = "$map" - MAX = "$max" - MERGE_OBJECTS = "$mergeObjects" - META = "$meta" - MILLI_SECOND = "$millisecond" - MIN = "$min" - MINUTE ="$minute" - MOD ="$mod" - MONTH = "$month" - MULTIPLY ="$multiply" - NE ="$ne" - NOT ="$not" - OBJECT_TO_ARRAY ="$objectToArray" - OR = "$or" - POW = "$pow" - PUSH = "$push" - RADIANS_TO_DEGREES = "$radiansToDegrees" - RAND = "$rand" - RANGE = "$range" - REDUCE = "$reduce" - REGEX_FIND ="$regexFind" - REGEX_FIND_ALL = "$regexFindAll" - REGEX_MATCH = "$regexMatch" - REPLACE_ONE ="$replaceOne" - REPLACE_ALL = "$replaceAll" - REVERSE_ARRAY = "$reverseArray" - ROUND = "$round" - RTRIM = "$rtrim" - SECOND = "$second" - SET_DIFFERENCE = "$setDifference" - SET_EQUALS = "$setEquals" - SET_FIELD = "$setField" - SET_INTERSECTION = "$setIntersection" - SET_IS_SUBSET = "$setIsSubset" - SET_UNION = "$setUnion" - SIN = "$sin" - SINH = "$sinh" - SIZE = "$size" - SLICE = "$slice" - SPLIT = "$split" - SQRT = "$sqrt" - STD_DEV_POP = "$stdDevPop" - STD_DEV_SAMP = "$stdDevSamp" - STR_LEN_BYTES = "$strLenBytes" - STR_LEN_CP = "$strLenCP" - STR_CASE_CMP = "$strcasecmp" - SUBSTR = "$substr" - SUBSTR_BYTES = "$substrBytes" - SUBSTR_CP = "$substrCP" - SUBSTRACT = "$subtract" - SUM = "$sum" - SWITCH = "$switch" - TAN = "$tan" - TANH = "$tanh" - TO_BOOL ="$toBool" - TO_DATE = "$toDate" - TO_DECIMAL = "$toDecimal" - TO_DOUBLE = "$toDouble" - TO_INT = "$toInt" - TO_LONG = "$toLong" - TO_LOWER = "$toLower" - TO_OBJECT_ID = "$toObjectId" - TO_STRING = "$toString" - TO_UPPER = "$toUpper" - TRIM = "$trim" - TRUNC = "$trunc" - TYPE = "$type" - WEEK = "$week" - YEAR = "$year" - ZIP = "$zip" - diff --git a/monggregate/operators/operator.py b/monggregate/operators/operator.py index 02202d0..96b1493 100644 --- a/monggregate/operators/operator.py +++ b/monggregate/operators/operator.py @@ -7,6 +7,147 @@ # Package imports # --------------------------- from monggregate.base import BaseModel +from monggregate.utils import StrEnum class Operator(BaseModel, ABC): """MongoDB operator abstract base class""" + +class OperatorEnum(StrEnum): + """Enumeration of available operators""" + + + ABS = "$abs" + ACCUMULATOR = "$accumulator" + ACOS = "$acos" + ACOSH = "$acosh" + ADD = "$add" + ADD_TO_SET = "$addToSet" + ALL_ELEMENTS_TRUE = "$allElementsTrue" + AND = "$and" + ANY_ELEMENTS_TRUE = "$anyElementsTrue" + ARRAY_ELEM_AT = "$arrayElemAt" + ARRAY_TO_OBJECT = "$arrayToObject" + ASIN = "$asin" + ASINH = "$asinh" + ATAN = "$atan" + ATAN2 = "$atan2" + ATANH = "$atanh" + AVG = "$avg" + BINARY_SIZE = "$binarySize" + BSON_SIZE ="$bsonSize" + CEIL = "$ceil" + CMP = "$cmp" + CONCAT = "$concat" + CONCAT_ARRAYS = "$concatArrays" + COND = "$cond" + CONVERT = "$convert" + COS = "$cos" + COSH = "$cosh" + DATE_FROM_PARTS = "$dateFromParts" + DATE_FROM_STRING = "$dateFromString" + DATE_TO_PARTS = "$dateToParts" + DATE_TO_STRING = "$dateToString" + DAY_OF_MONTH ="$dayOfMonth" + DAY_OF_WEEK = "$dayOfWeek" + DAY_OF_YEAR = "$dayOfYear" + DEGREES_TO_RADIANS = "$degreesToRadians" + DIVIDE = "$divide" + EQ = "$eq" + EXP = "$exp" + FILTER = "$filter" + FIRST = "$first" # two operators one for array one for accumulator + FLOOR = "$floor" + FUNCTION = "$function" + GET_FIELD = "$getField" + GT = "$gt" + GTE = "$gte" + HOUR = "$hour" + IF_NULL = "$ifNull" + IN = "$in" + INDEX_OF_ARRAY = "$indexOfArray" + INDEX_OF_BYTES = "$indexOfBytes" + INDEX_OF_CP = "$indexOfCP" + IS_ARRAY = "$isArray" + IS_NUMBER = "$isNumber" + ISO_DAY_OF_WEEK = "$isoDayOfWeek" + ISO_WEEK = "$isoWeek" + ISO_WEEK_YEAR ="$isoWeekYear" + LAST = "$last" # two operators one for array one for accumulator + LET ="$let" + LITERAL = "$literal" + LN = "$ln" + LOG = "$log" + LOG10 = "$log10" + LT = "$lt" + LTE = "$lte" + LTRIM = "$ltrim" + MAP = "$map" + MAX = "$max" + MERGE_OBJECTS = "$mergeObjects" + META = "$meta" + MILLI_SECOND = "$millisecond" + MIN = "$min" + MINUTE ="$minute" + MOD ="$mod" + MONTH = "$month" + MULTIPLY ="$multiply" + NE ="$ne" + NOT ="$not" + OBJECT_TO_ARRAY ="$objectToArray" + OR = "$or" + POW = "$pow" + PUSH = "$push" + RADIANS_TO_DEGREES = "$radiansToDegrees" + RAND = "$rand" + RANGE = "$range" + REDUCE = "$reduce" + REGEX_FIND ="$regexFind" + REGEX_FIND_ALL = "$regexFindAll" + REGEX_MATCH = "$regexMatch" + REPLACE_ONE ="$replaceOne" + REPLACE_ALL = "$replaceAll" + REVERSE_ARRAY = "$reverseArray" + ROUND = "$round" + RTRIM = "$rtrim" + SECOND = "$second" + SET_DIFFERENCE = "$setDifference" + SET_EQUALS = "$setEquals" + SET_FIELD = "$setField" + SET_INTERSECTION = "$setIntersection" + SET_IS_SUBSET = "$setIsSubset" + SET_UNION = "$setUnion" + SIN = "$sin" + SINH = "$sinh" + SIZE = "$size" + SLICE = "$slice" + SPLIT = "$split" + SQRT = "$sqrt" + STD_DEV_POP = "$stdDevPop" + STD_DEV_SAMP = "$stdDevSamp" + STR_LEN_BYTES = "$strLenBytes" + STR_LEN_CP = "$strLenCP" + STR_CASE_CMP = "$strcasecmp" + SUBSTR = "$substr" + SUBSTR_BYTES = "$substrBytes" + SUBSTR_CP = "$substrCP" + SUBSTRACT = "$subtract" + SUM = "$sum" + SWITCH = "$switch" + TAN = "$tan" + TANH = "$tanh" + TO_BOOL ="$toBool" + TO_DATE = "$toDate" + TO_DECIMAL = "$toDecimal" + TO_DOUBLE = "$toDouble" + TO_INT = "$toInt" + TO_LONG = "$toLong" + TO_LOWER = "$toLower" + TO_OBJECT_ID = "$toObjectId" + TO_STRING = "$toString" + TO_UPPER = "$toUpper" + TRIM = "$trim" + TRUNC = "$trunc" + TYPE = "$type" + WEEK = "$week" + YEAR = "$year" + ZIP = "$zip" \ No newline at end of file From a861ffb0dd5bd66f9cca946356f7685dce980e44 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 10:46:08 +0200 Subject: [PATCH 12/26] Developed DollarDollar class - Moved former aggregation_variables constructs in DollarDollar class - Created SS singleton --- monggregate/__init__.py | 2 +- monggregate/dollar.py | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/monggregate/__init__.py b/monggregate/__init__.py index a83c49f..6a5d4b4 100644 --- a/monggregate/__init__.py +++ b/monggregate/__init__.py @@ -1,7 +1,7 @@ """App Package""" from monggregate.pipeline import Pipeline -from monggregate.dollar import S +from monggregate.dollar import S, SS __version__ = "0.15.0" __author__ = "Vianney Mixtur" diff --git a/monggregate/dollar.py b/monggregate/dollar.py index 697a11d..6339932 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -31,8 +31,7 @@ class AggregationVariableEnum(StrEnum): KEEP = "$$KEEP" # One of the allowed results of a $redact expression.NOW = "$$NOW" # Returns the current datetime value, # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. # (Available in 4.2+) -# NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with -# it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance + CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value NOW = AggregationVariableEnum.NOW.value ROOT = AggregationVariableEnum.ROOT.value @@ -42,6 +41,8 @@ class AggregationVariableEnum(StrEnum): PRUNE = AggregationVariableEnum.PRUNE.value KEEP = AggregationVariableEnum.KEEP.value +# NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with +# it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance class Dollar: """Base class for all $ functions""" @@ -54,10 +55,10 @@ def __getattr__(self, name)->str|Any: """ - if name not in Dollar.__dict__: + if name not in self.__class__.__dict__: output = f"${name}" else: - output = Dollar.__dict__[name] + output = self.__class__.__dict__[name] return output @@ -256,6 +257,30 @@ def type_(cls, expression:Any)->type_.Type_: class DollarDollar: """xxx""" + CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value + NOW = AggregationVariableEnum.NOW.value + ROOT = AggregationVariableEnum.ROOT.value + CURRENT = AggregationVariableEnum.CURRENT.value + REMOVE = AggregationVariableEnum.REMOVE.value + DESCEND = AggregationVariableEnum.DESCEND.value + PRUNE = AggregationVariableEnum.PRUNE.value + KEEP = AggregationVariableEnum.KEEP.value + + def __getattr__(self, name)->str|Any: + """Overloads the __getattr__ method. + Return the name of the attribute with a $ prepended to it + (when it's not a method or an attribute of the classe) + + """ + + if name not in self.__class__.__dict__: + output = f"$${name}" + else: + output = self.__class__.__dict__[name] + + return output + -# TODO : Make below instance a singleton +# TODO : Make below instances singletons S = Dollar() +SS = DollarDollar() From 59afa38d07ac3b62a4c96fd11af57772bd85eec7 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 16:47:41 +0200 Subject: [PATCH 13/26] Removed Content references --- monggregate/base.py | 8 ++++---- monggregate/expressions.py | 1 - monggregate/operators/accumulators/avg.py | 3 +-- monggregate/operators/accumulators/sum.py | 6 +++--- monggregate/pipeline.py | 2 +- monggregate/stages/bucket.py | 8 ++++---- monggregate/stages/bucket_auto.py | 3 +-- monggregate/stages/group.py | 3 +-- 8 files changed, 15 insertions(+), 19 deletions(-) diff --git a/monggregate/base.py b/monggregate/base.py index 6309cb0..4cfbf5c 100644 --- a/monggregate/base.py +++ b/monggregate/base.py @@ -3,7 +3,7 @@ # Standard Library imports #---------------------------- from abc import ABC, abstractmethod -from typing import Any +from typing import Any, TypeGuard # 3rd Party imports # --------------------------- @@ -45,10 +45,10 @@ class Config(pyd.BaseConfig): allow_population_by_field_name = True underscore_attrs_are_private = True smart_union = True - #alias_generator = camelize + alias_generator = camelize -# TODO : Use TypeGuard here to improve type checking -def isbasemodel(instance:Any)->bool: + +def isbasemodel(instance:Any)->TypeGuard[BaseModel]: """Returns true if instance is an instance of BaseModel""" return isinstance(instance, BaseModel) diff --git a/monggregate/expressions.py b/monggregate/expressions.py index 4736ed1..d714dd6 100644 --- a/monggregate/expressions.py +++ b/monggregate/expressions.py @@ -17,7 +17,6 @@ # Local imports # ---------------------------- from monggregate.fields import FieldPath, Variable -from monggregate.expressions.content import Content from monggregate.operators.accumulators import( Avg, Count, diff --git a/monggregate/operators/accumulators/avg.py b/monggregate/operators/accumulators/avg.py index 2c1156f..5ae4af2 100644 --- a/monggregate/operators/accumulators/avg.py +++ b/monggregate/operators/accumulators/avg.py @@ -72,7 +72,6 @@ from typing import Any from monggregate.operators.accumulators.accumulator import Accumulator -from monggregate.expressions.content import Content class Average(Accumulator): """ @@ -84,7 +83,7 @@ class Average(Accumulator): """ - expression : Content + expression : Any @property def statement(self) -> dict: diff --git a/monggregate/operators/accumulators/sum.py b/monggregate/operators/accumulators/sum.py index 85c89c6..a3e4b89 100644 --- a/monggregate/operators/accumulators/sum.py +++ b/monggregate/operators/accumulators/sum.py @@ -85,8 +85,8 @@ """ +from typing import Any from monggregate.operators.accumulators.accumulator import Accumulator -from monggregate.expressions.content import Content class Sum(Accumulator): """ @@ -98,7 +98,7 @@ class Sum(Accumulator): - operand, Expression : Any valid expression """ - expression : Content | list[Content] + expression : Any @property @@ -108,7 +108,7 @@ def statement(self) -> dict: "$sum" : self.expression }) -def sum(*args:Content)->Sum: +def sum(*args:Any)->Sum: """Creates a $sum statement""" if len(args)>1: diff --git a/monggregate/pipeline.py b/monggregate/pipeline.py index 093ccf9..2f3c93c 100644 --- a/monggregate/pipeline.py +++ b/monggregate/pipeline.py @@ -31,7 +31,7 @@ from monggregate.stages.search import OperatorLiteral from monggregate.search.operators.compound import Compound from monggregate.operators import MergeObjects -from monggregate.aggregation_variables import ROOT +from monggregate.dollar import ROOT from monggregate.utils import StrEnum diff --git a/monggregate/stages/bucket.py b/monggregate/stages/bucket.py index 749c12e..a6e291f 100644 --- a/monggregate/stages/bucket.py +++ b/monggregate/stages/bucket.py @@ -49,11 +49,11 @@ """ +from typing import Any from monggregate.base import pyd from monggregate.stages.stage import Stage -from monggregate.expressions.content import Content, Const, Consts from monggregate.fields import FieldName from monggregate.operators.accumulators.accumulator import AccumulatorExpression from monggregate.utils import validate_field_path @@ -98,9 +98,9 @@ class Bucket(Stage): """ - by : Content = pyd.Field(...,alias="group_by") - boundaries : Consts - default : Const | None = None + by : Any = pyd.Field(...,alias="group_by") + boundaries : list + default : Any = None output : dict[FieldName, AccumulatorExpression] | None = None # Validators diff --git a/monggregate/stages/bucket_auto.py b/monggregate/stages/bucket_auto.py index 27dfd65..72df035 100644 --- a/monggregate/stages/bucket_auto.py +++ b/monggregate/stages/bucket_auto.py @@ -83,7 +83,6 @@ from typing import Any from monggregate.base import pyd from monggregate.stages.stage import Stage -from monggregate.expressions.content import Content from monggregate.fields import FieldName from monggregate.operators.accumulators.accumulator import AccumulatorExpression from monggregate.utils import StrEnum, validate_field_path @@ -137,7 +136,7 @@ class BucketAuto(Stage): # Attributes # ---------------------------------------------------------------------------- - by : Content = pyd.Field(...,alias="group_by") # probably should restrict type to field_paths an operator expressions + by : Any = pyd.Field(...,alias="group_by") # probably should restrict type to field_paths an operator expressions buckets : int = pyd.Field(..., gt=0) output : dict[FieldName, AccumulatorExpression] | None = None# Accumulator Expressions #TODO : Define type and use it here granularity : GranularityEnum | None = None diff --git a/monggregate/stages/group.py b/monggregate/stages/group.py index a3b9ee5..53ad66b 100644 --- a/monggregate/stages/group.py +++ b/monggregate/stages/group.py @@ -66,7 +66,6 @@ from typing import Any from monggregate.base import pyd from monggregate.stages.stage import Stage -from monggregate.expressions.content import Content from monggregate.utils import validate_field_path class Group(Stage): @@ -81,7 +80,7 @@ class Group(Stage): """ - by : Content | None = pyd.Field(None, alias = "_id") # | or any constant value, in this case + by : Any = pyd.Field(None, alias = "_id") # | or any constant value, in this case # the stage returns a single document that aggregates values across all of the input documents #sum #avg From 13149ec01cd329fe5b6258e317a2ff6a5738ad74 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 18:32:25 +0200 Subject: [PATCH 14/26] Added first couple of test for Dollar class --- monggregate/dollar.py | 84 +++++++++++++++++++++++++++++++------------ test/test_dollar.py | 46 ++++++++++-------------- 2 files changed, 79 insertions(+), 51 deletions(-) diff --git a/monggregate/dollar.py b/monggregate/dollar.py index 6339932..1b31984 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -1,8 +1,7 @@ """WIP""" from typing import Any, Literal - -from monggregate.base import BaseModel +from typing_extensions import Self from monggregate.operators import( accumulators, @@ -43,14 +42,41 @@ class AggregationVariableEnum(StrEnum): # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with # it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance -class Dollar: - """Base class for all $ functions""" +class Singleton: + """Singleton metaclass""" + + _instance = None + def __new__(cls, *args, **kwargs): + if not isinstance(cls._instance, cls): + cls._instance = object.__new__(cls, *args, **kwargs) + return cls._instance + + +class Dollar(Singleton): + """ + MongoDB dollar sign ($) abstraction in python. + + This class is a singleton class meant to be used as a namespace for all MongoDB operators. Might include stages in the future. + Can also be used to reference a field name in a document. + + Examples: + ------------------------------ + + >>> Dollar.avg("$price")() + {"$avg": "$price"} + + >>> Dollar.name + "$name" + + """ # Any below should be replaced by a Union of # all operators or by Typevar bounded by Operator def __getattr__(self, name)->str|Any: - """Overloads the __getattr__ method. - Return the name of the attribute with a $ prepended to it + """ + Overloads the __getattr__ method. + + Returns the name of the attribute with a $ prepended to it (when it's not a method or an attribute of the classe) """ @@ -62,11 +88,9 @@ def __getattr__(self, name)->str|Any: return output - # Operators - # ------------------------------ - # Accumulators - # -------------------------- + # Accumulators + # -------------------------- @classmethod def avg(cls, expression:Any)->accumulators.Avg: """Returns the $avg operator""" @@ -115,8 +139,8 @@ def sum(cls, expression:Any)->accumulators.Sum: return accumulators.sum(expression) - # Array - # -------------------------- + # Array + # -------------------------- @classmethod def array_to_object(cls, expression:Any)->array.ArrayToObject: """Returns the $arrayToObject operator""" @@ -168,8 +192,8 @@ def sort_array(cls, expression:Any, sort_spec:dict[str, Literal[1,-1]])->array.S return array.sort_array(expression, sort_spec) - # Comparison - # -------------------------- + # Comparison + # -------------------------- @classmethod def cmp(cls, left:Any, right:Any)->comparison.Cmp: """Returns the $cmp operator""" @@ -212,8 +236,8 @@ def ne(cls, left:Any, right:Any)->comparison.Ne: return comparison.ne(left, right) - # Objects - # -------------------------- + # Objects + # -------------------------- @classmethod def merge_objects(cls, *args:Any)->objects.MergeObjects: """Returns the $mergeObjects operator""" @@ -226,8 +250,8 @@ def object_to_array(cls, expression:Any)->objects.ObjectToArray: return objects.object_to_array(expression) - # Boolean - # -------------------------- + # Boolean + # -------------------------- @classmethod def and_(cls, *args:Any)->boolean.And: """Returns the $and operator""" @@ -246,16 +270,30 @@ def not_(cls, expression:Any)->boolean.Not: return boolean.not_(expression) - # Type - # -------------------------- + # Type + # -------------------------- @classmethod def type_(cls, expression:Any)->type_.Type_: """Returns the $type operator""" return type_.type_(expression) -class DollarDollar: - """xxx""" +class DollarDollar(Singleton): + """ + MongoDB double dollar sign ($$) abstraction in python. + + This class is a singleton class meant to be used as a namespace for all MongoDB aggregation variables. + Can also be used to refrence a user-defined variable. + + Examples: + ------------------------------ + >>> DollarDollar.NOW + "$$NOW" + + >>> DollarDollar.product_name + "$$product_name" + + """ CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value NOW = AggregationVariableEnum.NOW.value @@ -281,6 +319,6 @@ def __getattr__(self, name)->str|Any: return output -# TODO : Make below instances singletons + S = Dollar() SS = DollarDollar() diff --git a/test/test_dollar.py b/test/test_dollar.py index 873c02a..286d198 100644 --- a/test/test_dollar.py +++ b/test/test_dollar.py @@ -1,35 +1,24 @@ """Module to test dollar singleton class""" -from monggregate.dollar import S -from pydantic import BaseModel - -class AccessAll(BaseModel): - x:int = 1 - - def merge_objects(self, *args, **kwargs): - - - kwargs.update({"args":args}) - return kwargs - - def __getattr__(self, name): - - if name not in AccessAll.__dict__: - output = f"${name}" - else: - output = AccessAll.__dict__[name] - - return output - -def test_access_all()->None: +from monggregate.dollar import Dollar, DollarDollar, S, SS +from monggregate.operators import And + +def test_dollar_getattr()->None: """Tests the access all class""" - assert AccessAll().name == "$name" - assert AccessAll().age == "$age" - assert AccessAll().address == "$address" - assert AccessAll().x == 1 - assert AccessAll().merge_objects(1,2) == {"args":(1,2)} + assert S.name == "$name" + assert S.age == "$age" + assert S.address == "$address" + + assert S.and_(True, True) == And(expressions=[True, True]) + +def test_singletons()->None: + """Tests that Dollar and DollarDollar are singletons""" + assert Dollar() is Dollar() + assert DollarDollar() is DollarDollar() + assert S is Dollar() + assert SS is DollarDollar() def test_simple_expressions()->None: """Tests some simple expressions""" @@ -56,6 +45,7 @@ def test_simple_expressions()->None: if __name__ == "__main__": - test_access_all() + test_singletons() + test_dollar_getattr() test_simple_expressions() print("Everything passed") \ No newline at end of file From 8bccd6233957c8dd450159219feed81cafb9cf4a Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sun, 13 Aug 2023 23:06:11 +0200 Subject: [PATCH 15/26] Quick and dirty prototypes of some arithmetic operators --- monggregate/operators/arithmetic/add.py | 34 ++++++++++++++ .../operators/arithmetic/arithmetic.py | 42 +++++++++++++++++ monggregate/operators/arithmetic/divide.py | 30 +++++++++++++ monggregate/operators/arithmetic/multiply.py | 30 +++++++++++++ monggregate/operators/arithmetic/pow.py | 30 +++++++++++++ monggregate/operators/arithmetic/substract.py | 30 +++++++++++++ monggregate/operators/strings/concat.py | 0 monggregate/operators/strings/string.py | 45 +++++++++++++++++++ 8 files changed, 241 insertions(+) create mode 100644 monggregate/operators/arithmetic/add.py create mode 100644 monggregate/operators/arithmetic/arithmetic.py create mode 100644 monggregate/operators/arithmetic/divide.py create mode 100644 monggregate/operators/arithmetic/multiply.py create mode 100644 monggregate/operators/arithmetic/pow.py create mode 100644 monggregate/operators/arithmetic/substract.py create mode 100644 monggregate/operators/strings/concat.py create mode 100644 monggregate/operators/strings/string.py diff --git a/monggregate/operators/arithmetic/add.py b/monggregate/operators/arithmetic/add.py new file mode 100644 index 0000000..a6e6b22 --- /dev/null +++ b/monggregate/operators/arithmetic/add.py @@ -0,0 +1,34 @@ +""" +Module defining an interface to $and operator + +""" + +from typing import Any +from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator + +class Add(ArithmeticOperator): + """ + Creates a $add expression + + Attributes + ------------------- + - expressions : list[Any] + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$add" : self.expressions + }) + +def add(*args:Any)->Add: + """Returns an $add statement""" + + return Add( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/arithmetic/arithmetic.py b/monggregate/operators/arithmetic/arithmetic.py new file mode 100644 index 0000000..14ffe5c --- /dev/null +++ b/monggregate/operators/arithmetic/arithmetic.py @@ -0,0 +1,42 @@ +"""Base arithmetic operator module""" + +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class ArithmeticOperatorEnum(StrEnum): + """Enumeration of available arithmetic operators""" + + ABS = "$abs" # Returns the absolute value of a number. Accepts a single argument expression. + ADD = "$add" # Adds numbers together or adds numbers and a date. Accepts any number of argument expressions, but at most, one expression can resolve to a date. + CEIL = "$ceil" # Returns the smallest integer greater than or equal to the specified number. Accepts a single argument expression. + DIVIDE = "$divide" # Divides one number by another and returns the result. Accepts two argument expressions. + EXP = "$exp" # Raises Euler’s number to the specified exponent and returns the result. Accepts a single argument expression. + FLOOR = "$floor" # Returns the largest integer less than or equal to the specified number. Accepts a single argument expression. + LN = "$ln" # Calculates the natural logarithm ln (i.e loge) of a number and returns the result as a double. Accepts a single argument expression. + LOG = "$log" # Calculates the log of a number in the specified base and returns the result as a double. Accepts two argument expressions. + LOG10 = "$log10" # Calculates the log base 10 of a number and returns the result as a double. Accepts a single argument expression. + MOD = "$mod" # Performs a modulo operation on the first argument and returns the remainder. Accepts two argument expressions. + MULTIPLY = "$multiply" # Multiplies numbers together and returns the result. Accepts any number of argument expressions. + POW = "$pow" # Raises a number to the specified exponent and returns the result. Accepts two argument expressions. + ROUND = "$round" # Rounds a number to to a whole integer or to a specified decimal place. Accepts two argument expressions. + SQRT = "$sqrt" # Calculates the square root of a positive number and returns the result as a double. Accepts a single argument expression. + SUBTRACT = "$subtract" # Subtracts two numbers to return the difference, or adds two numbers to return the sum. Accepts two argument expressions. If both arguments are dates, $subtract returns the difference in milliseconds. + TRUNC = "$trunc" # Truncates a number to a whole integer or to a specified decimal place. Accepts a single argument expression. + +# Classes +# ----------------------------------------- +class ArithmeticOperator(Operator, ABC): + """Base class for arithmetic operators""" + +# Type aliases +# ----------------------------------------- +ArithmeticOperatorExpression = dict[ArithmeticOperatorEnum, Any] \ No newline at end of file diff --git a/monggregate/operators/arithmetic/divide.py b/monggregate/operators/arithmetic/divide.py new file mode 100644 index 0000000..b42130a --- /dev/null +++ b/monggregate/operators/arithmetic/divide.py @@ -0,0 +1,30 @@ +""" +Module defining an interface to $divide operator + +""" + +from typing import Any +from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator + +class Divide(ArithmeticOperator): + """ + xxx + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$add" : self.expressions + }) + +def divide(*args:Any)->Divide: + """Returns an $divide statement""" + + return Divide( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/arithmetic/multiply.py b/monggregate/operators/arithmetic/multiply.py new file mode 100644 index 0000000..aeb7b19 --- /dev/null +++ b/monggregate/operators/arithmetic/multiply.py @@ -0,0 +1,30 @@ +""" +Module defining an interface to $multiply operator + +""" + +from typing import Any +from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator + +class Multiply(ArithmeticOperator): + """ + xxx + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$add" : self.expressions + }) + +def multiply(*args:Any)->Multiply: + """Returns an $multiply statement""" + + return Multiply( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/arithmetic/pow.py b/monggregate/operators/arithmetic/pow.py new file mode 100644 index 0000000..5adb128 --- /dev/null +++ b/monggregate/operators/arithmetic/pow.py @@ -0,0 +1,30 @@ +""" +Module defining an interface to $pow operator + +""" + +from typing import Any +from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator + +class Pow(ArithmeticOperator): + """ + xxx + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$add" : self.expressions + }) + +def pow(*args:Any)->Pow: + """Returns an $pow statement""" + + return Pow( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/arithmetic/substract.py b/monggregate/operators/arithmetic/substract.py new file mode 100644 index 0000000..18bf944 --- /dev/null +++ b/monggregate/operators/arithmetic/substract.py @@ -0,0 +1,30 @@ +""" +Module defining an interface to $substract operator + +""" + +from typing import Any +from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator + +class Substract(ArithmeticOperator): + """ + xxx + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$add" : self.expressions + }) + +def substract(*args:Any)->Substract: + """Returns an $substract statement""" + + return Substract( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/strings/concat.py b/monggregate/operators/strings/concat.py new file mode 100644 index 0000000..e69de29 diff --git a/monggregate/operators/strings/string.py b/monggregate/operators/strings/string.py new file mode 100644 index 0000000..30ab70b --- /dev/null +++ b/monggregate/operators/strings/string.py @@ -0,0 +1,45 @@ +"""Base string operator module""" + +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class StringOperatorEnum(StrEnum): + """Enumeration of available string operators""" + + CONCAT = "$concat" # Concatenates any number of strings. + CONCAT_WS = "$concatWS" # Concatenates strings and returns the concatenated string. + INDEX_OF_BYTES = "$indexOfBytes" # Searches a string for an occurence of a substring and returns the UTF-8 byte index of the first occurence. If the substring is not found, returns -1. + INDEX_OF_CP = "$indexOfCP" # Searches a string for an occurence of a substring and returns the UTF-8 code point index of the first occurence. If the substring is not found, returns -1. + LTRIM = "$ltrim" # Removes whitespace or the specified characters from the beginning of a string. + REGEX_FIND = "$regexFind" # Searches a string for an occurence of a substring that matches the given regular expression pattern and returns the first occurence as a substring. + REGEX_FIND_ALL = "$regexFindAll" # Searches a string for occurences of a substring that matches the given regular expression pattern and returns the occurences as a list of substrings. + REGEX_MATCH = "$regexMatch" # Performs a regular expression match of a string against a pattern and returns a boolean that indicates if the pattern is found or not. + REPLACE_ALL = "$replaceAll" # Replaces all occurences of a specified string or regular expression in a given input string with another specified string. + REPLACE_ONE = "$replaceOne" # Replaces the first occurence of a specified string or regular expression in a given input string with another specified string. + RTRIM = "$rtrim" # Removes whitespace or the specified characters from the end of a string. + SPLIT = "$split" # Divides a string into substrings based on a delimiter. + STR_LEN_BYTES = "$strLenBytes" # Returns the number of UTF-8 encoded bytes in a string. + STR_LEN_CP = "$strLenCP" # Returns the number of UTF-8 code points in a string. + STRCASECMP = "$strcasecmp" # Performs case-insensitive string comparison and returns: 0 if two strings are equivalent, 1 if the first string is greater than the second, and -1 if the first string is less than the second. + SUBSTR = "$substr" # Returns the substring of a string. + SUBSTR_BYTES = "$substrBytes" # Returns the substring of a string. + + + +# Classes +# ----------------------------------------- +class StringOperator(Operator, ABC): + """Base class for string operators""" + +# Type aliases +# ----------------------------------------- +StringOperatorExpression = dict[StringOperatorEnum, Any] \ No newline at end of file From af991c17c22771d56cf640acfc5effa5be435f86 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Mon, 14 Aug 2023 11:47:43 +0200 Subject: [PATCH 16/26] Quick n dirty prototypes of some operators part II - conditional - date - strings - Postponed for custom (a bit complex) and data_size (not that useful) --- monggregate/operators/arithmetic/divide.py | 2 +- monggregate/operators/arithmetic/multiply.py | 2 +- monggregate/operators/arithmetic/pow.py | 2 +- monggregate/operators/arithmetic/substract.py | 2 +- monggregate/operators/conditional/cond.py | 98 +++++++++++++++++++ .../operators/conditional/conditional.py | 26 +++++ monggregate/operators/conditional/if_null.py | 32 ++++++ monggregate/operators/conditional/switch.py | 38 +++++++ monggregate/operators/custom/custom.py | 26 +++++ monggregate/operators/data_size/data_size.py | 27 +++++ monggregate/operators/date/date.py | 50 ++++++++++ monggregate/operators/date/millisecond.py | 41 ++++++++ monggregate/operators/strings/concat.py | 30 ++++++ .../operators/strings/date_from_string.py | 45 +++++++++ .../operators/strings/date_to_string.py | 42 ++++++++ 15 files changed, 459 insertions(+), 4 deletions(-) create mode 100644 monggregate/operators/conditional/cond.py create mode 100644 monggregate/operators/conditional/conditional.py create mode 100644 monggregate/operators/conditional/if_null.py create mode 100644 monggregate/operators/conditional/switch.py create mode 100644 monggregate/operators/custom/custom.py create mode 100644 monggregate/operators/data_size/data_size.py create mode 100644 monggregate/operators/date/date.py create mode 100644 monggregate/operators/date/millisecond.py create mode 100644 monggregate/operators/strings/date_from_string.py create mode 100644 monggregate/operators/strings/date_to_string.py diff --git a/monggregate/operators/arithmetic/divide.py b/monggregate/operators/arithmetic/divide.py index b42130a..69ece01 100644 --- a/monggregate/operators/arithmetic/divide.py +++ b/monggregate/operators/arithmetic/divide.py @@ -19,7 +19,7 @@ class Divide(ArithmeticOperator): @property def statement(self) -> dict: return self.resolve({ - "$add" : self.expressions + "$divide" : self.expressions }) def divide(*args:Any)->Divide: diff --git a/monggregate/operators/arithmetic/multiply.py b/monggregate/operators/arithmetic/multiply.py index aeb7b19..e5298c0 100644 --- a/monggregate/operators/arithmetic/multiply.py +++ b/monggregate/operators/arithmetic/multiply.py @@ -19,7 +19,7 @@ class Multiply(ArithmeticOperator): @property def statement(self) -> dict: return self.resolve({ - "$add" : self.expressions + "$multiply" : self.expressions }) def multiply(*args:Any)->Multiply: diff --git a/monggregate/operators/arithmetic/pow.py b/monggregate/operators/arithmetic/pow.py index 5adb128..daaf7e8 100644 --- a/monggregate/operators/arithmetic/pow.py +++ b/monggregate/operators/arithmetic/pow.py @@ -19,7 +19,7 @@ class Pow(ArithmeticOperator): @property def statement(self) -> dict: return self.resolve({ - "$add" : self.expressions + "$pow" : self.expressions }) def pow(*args:Any)->Pow: diff --git a/monggregate/operators/arithmetic/substract.py b/monggregate/operators/arithmetic/substract.py index 18bf944..6752fb5 100644 --- a/monggregate/operators/arithmetic/substract.py +++ b/monggregate/operators/arithmetic/substract.py @@ -19,7 +19,7 @@ class Substract(ArithmeticOperator): @property def statement(self) -> dict: return self.resolve({ - "$add" : self.expressions + "$substract" : self.expressions }) def substract(*args:Any)->Substract: diff --git a/monggregate/operators/conditional/cond.py b/monggregate/operators/conditional/cond.py new file mode 100644 index 0000000..35a06fe --- /dev/null +++ b/monggregate/operators/conditional/cond.py @@ -0,0 +1,98 @@ +""" +Module defining an interface to $cond operator + +""" + +from typing import Any +from monggregate.base import pyd +from monggregate.operators.conditional.conditional import ConditionalOperator + +class Cond(ConditionalOperator): + """ + xxx + + + """ + + # Syntax 2 + expression : 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") + false_ : Any|None = pyd.Field(alias="false") + + # Syntax 1 + if_ : Any = pyd.Field(alias="if") + then_ : Any = pyd.Field(alias="then") + else_ : Any = pyd.Field(alias="else") + + @pyd.root_validator(pre=True) + def _validate_conditional(cls, values:dict)->dict: + """Checks combination of arguments""" + + if_ = values.get("if_") + then_ = values.get("then_") + else_ = values.get("else_") + + expression = values.get("expression") + true_ = values.get("true_") + false_ = values.get("false_") + + c1 = if_ is not None and then_ is not None and else_ is not None + c2 = expression is not None and true_ is not None and false_ is not None + + if not (c1 or c2): + raise ValueError("Either (if_, then_, else_) or (expression, true_, false_) must be provided") + + return values + + @pyd.validator("if_") + def _validate_if(cls, v:Any, values:dict)->Any: + """Checks if_ is not None""" + + if not v: + return values.get("expression") + + return v + + @pyd.validator("then_") + def _validate_then(cls, v:Any, values:dict)->Any: + """Checks then_ is not None""" + + if not v: + return values.get("true_") + + return v + + @pyd.validator("else_") + def _validate_else(cls, v:Any, values:dict)->Any: + """Checks else_ is not None""" + + if not v: + return values.get("false_") + + return v + + + @property + def statement(self) -> dict: + return self.resolve({ + "$cond" : { + "if" : self.if_, + "then" : self.then_, + "else" : self.else_ + } + }) + +def cond(*args:Any, **kwargs:Any)->Cond: + """Returns an $cond statement""" + + if_ = args[0] if args else (kwargs.get("if_") or kwargs.get("expression")) + then_ = args[1] if len(args) > 1 else (kwargs.get("then_") or kwargs.get("true_")) + else_ = args[2] if len(args) > 2 else (kwargs.get("else_") or kwargs.get("false_")) + + return Cond( + if_=if_, + then_=then_, + else_=else_ + ) \ No newline at end of file diff --git a/monggregate/operators/conditional/conditional.py b/monggregate/operators/conditional/conditional.py new file mode 100644 index 0000000..47d6fff --- /dev/null +++ b/monggregate/operators/conditional/conditional.py @@ -0,0 +1,26 @@ +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class ConditionalOperatorEnum(StrEnum): + """Enumeration of available boolean operators""" + + ACCUMULATOR = "$accumulator" # Defines a custom accumulator function or expression. + FUNCTION = "$function" # Defines a custom function or expression. + +# Classes +# ----------------------------------------- +class ConditionalOperator(Operator, ABC): + """Base class for boolean operators""" + +# Type aliases +# ----------------------------------------- +ConditionalOperatorExpression = dict[ConditionalOperatorEnum, Any] \ No newline at end of file diff --git a/monggregate/operators/conditional/if_null.py b/monggregate/operators/conditional/if_null.py new file mode 100644 index 0000000..8424719 --- /dev/null +++ b/monggregate/operators/conditional/if_null.py @@ -0,0 +1,32 @@ +""" +Module defining an interface to $if_null operator + +""" + +from typing import Any +from monggregate.operators.conditional.conditional import ConditionalOperator + +class IfNull(ConditionalOperator): + """ + xxx + + + """ + + + expression : 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 if_null(expression:Any, output:Any)->IfNull: + """Returns an $if_null statement""" + + return IfNull( + expression=expression, + output=output + ) \ No newline at end of file diff --git a/monggregate/operators/conditional/switch.py b/monggregate/operators/conditional/switch.py new file mode 100644 index 0000000..6fa5611 --- /dev/null +++ b/monggregate/operators/conditional/switch.py @@ -0,0 +1,38 @@ +""" +Module defining an interface to $switch operator + +""" + +from typing import Any +from monggregate.operators.conditional.conditional import ConditionalOperator + +# TODO : Define branc +# {"case": , "then": } + +class Switch(ConditionalOperator): + """ + xxx + + + """ + + + branches : list[Any] + default : Any + + @property + def statement(self) -> dict: + return self.resolve({ + "$switch" : { + "branches" : self.branches, + "default" : self.default + } + }) + +def switch(branches:list[Any], default:Any)->Switch: + """Returns an $switch statement""" + + return Switch( + branches=branches, + default=default + ) \ No newline at end of file diff --git a/monggregate/operators/custom/custom.py b/monggregate/operators/custom/custom.py new file mode 100644 index 0000000..c03f11a --- /dev/null +++ b/monggregate/operators/custom/custom.py @@ -0,0 +1,26 @@ +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class CustomOperatorEnum(StrEnum): + """Enumeration of available boolean operators""" + + ACCUMULATOR = "$accumulator" # Defines a custom accumulator function or expression. + FUNCTION = "$function" # Defines a custom function or expression. + +# Classes +# ----------------------------------------- +class CustomOperator(Operator, ABC): + """Base class for boolean operators""" + +# Type aliases +# ----------------------------------------- +CustomOperatorExpression = dict[CustomOperatorEnum, Any] diff --git a/monggregate/operators/data_size/data_size.py b/monggregate/operators/data_size/data_size.py new file mode 100644 index 0000000..e170592 --- /dev/null +++ b/monggregate/operators/data_size/data_size.py @@ -0,0 +1,27 @@ +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class DataSizeOperatorEnum(StrEnum): + """Enumeration of available boolean operators""" + + BINARY_SIZE = "$binarySize" # Returns the size of a BinData value in bytes. + BSON_SIZE = "$bsonSize" # Returns the size of a document in bytes. + + +# Classes +# ----------------------------------------- +class DataSizeOperator(Operator, ABC): + """Base class for boolean operators""" + +# Type aliases +# ----------------------------------------- +DataSizeOperatorExpression = dict[DataSizeOperatorEnum, Any] \ No newline at end of file diff --git a/monggregate/operators/date/date.py b/monggregate/operators/date/date.py new file mode 100644 index 0000000..a67b86b --- /dev/null +++ b/monggregate/operators/date/date.py @@ -0,0 +1,50 @@ +# Standard Library Imports +# ----------------------------------------- +from abc import ABC +from typing import Any + +# Local imports +# ----------------------------------------- +from monggregate.operators import Operator +from monggregate.utils import StrEnum + +# Enums +# ----------------------------------------- +class DateOperatorEnum(StrEnum): + """Enumeration of available boolean operators""" + + DATE_ADD = "$dateAdd" # Adds the specified number of units to a date expression. + DATE_DIFF = "$dateDiff" # Returns the difference between two date expressions in the specified time unit. + DATE_FROM_PARTS = "$dateFromParts" # Returns a date given the date’s constituent properties. + DATE_FROM_STRING = "$dateFromString" # Converts a date/time string to a date object. + DATE_SUBTRACT = "$dateSubtract" # Subtracts the specified number of units from a date expression. + DATE_TO_PARTS = "$dateToParts" # Returns a document containing the constituent parts of a date. + DATE_TO_STRING = "$dateToString" # Converts a date object to a string according to a user-specified format. + DATE_TRUNC = "$dateTrunc" # Returns a date with the specified part(s) reset to a set value. + DAY_OF_MONTH = "$dayOfMonth" # Returns the day of the month for a date as a number between 1 and 31. + DAY_OF_WEEK = "$dayOfWeek" # Returns the day of the week for a date as a number between 1 (Sunday) and 7 (Saturday). + DAY_OF_YEAR = "$dayOfYear" # Returns the day of the year for a date as a number between 1 and 366 (leap year). + HOUR = "$hour" # Returns the hour portion of a date as a number between 0 and 23. + ISO_DAY_OF_WEEK = "$isoDayOfWeek" # Returns the weekday number in ISO 8601 format, ranging from 1 (for Monday) to 7 (for Sunday). + ISO_WEEK = "$isoWeek" # Returns the week number in ISO 8601 format, ranging from 1 to 53. Week numbers start at 1 with the week (Monday through Sunday) that contains the year’s first Thursday. + ISO_WEEK_YEAR = "$isoWeekYear" # Returns the year number in ISO 8601 format. The year starts with the Monday of week 1 (ISO 8601) and ends with the Sunday of the last week (ISO 8601). + MILLISECOND = "$millisecond" # Returns the millisecond portion of a date as an integer between 0 and 999. + MINUTE = "$minute" # Returns the minute portion of a date as a number between 0 and 59. + MONTH = "$month" # Returns the month of a date as a number between 1 and 12. + SECOND = "$second" # Returns the second portion of a date as a number between 0 and 59, but can be 60 to account for leap seconds. + TO_DATE = "$toDate" # Converts a value of any type to a date. + WEEK = "$week" # Returns the week number for a date as a number between 0 (the partial week that precedes the first Sunday of the year) and 53 (leap year). + YEAR = "$year" # Returns the year portion of a date as a number (e.g. 2014). + + + + + +# Classes +# ----------------------------------------- +class DateOperator(Operator, ABC): + """Base class for boolean operators""" + +# Type aliases +# ----------------------------------------- +DateOperatorExpression = dict[DateOperatorEnum, Any] \ No newline at end of file diff --git a/monggregate/operators/date/millisecond.py b/monggregate/operators/date/millisecond.py new file mode 100644 index 0000000..533c41b --- /dev/null +++ b/monggregate/operators/date/millisecond.py @@ -0,0 +1,41 @@ +""" +Module defining an interface to $millisecond operator + +""" + +from typing import Any +from monggregate.operators.date.date import DateOperator + +class MilliSecond(DateOperator): + """ + xxx + + + """ + + + expression : Any + timezeone : Any | None + + @property + def statement(self) -> dict: + + if self.timezeone: + inner = { + "date" : self.expression, + "timezone" : self.timezeone + } + else: + inner = self.expression + + return self.resolve({ + "$millisecond" : inner + }) + +def millisecond(expression:Any, timezone:Any)->MilliSecond: + """Returns an $millisecond statement""" + + return MilliSecond( + expression=expression, + timezone=timezone + ) \ No newline at end of file diff --git a/monggregate/operators/strings/concat.py b/monggregate/operators/strings/concat.py index e69de29..39721c1 100644 --- a/monggregate/operators/strings/concat.py +++ b/monggregate/operators/strings/concat.py @@ -0,0 +1,30 @@ +""" +Module defining an interface to $concat operator + +""" + +from typing import Any +from monggregate.operators.strings.string import StringOperator + +class Concat(StringOperator): + """ + xxx + + + """ + + + expressions : list[Any] + + @property + def statement(self) -> dict: + return self.resolve({ + "$concat" : self.expressions + }) + +def concat(*args:Any)->Concat: + """Returns an $concat statement""" + + return Concat( + expressions=list(args) + ) \ No newline at end of file diff --git a/monggregate/operators/strings/date_from_string.py b/monggregate/operators/strings/date_from_string.py new file mode 100644 index 0000000..fdde60e --- /dev/null +++ b/monggregate/operators/strings/date_from_string.py @@ -0,0 +1,45 @@ +""" +Module defining an interface to $date_from_string operator + +""" + +from typing import Any +from monggregate.base import pyd +from monggregate.operators.strings.string import StringOperator + +class DateFromString(StringOperator): + """ + xxx + + + """ + + + date_string : Any + format_ : Any = pyd.Field(alias="format") + timezone : Any + on_error : Any + on_null : Any + + @property + def statement(self) -> dict: + return self.resolve({ + "$dateFromString" : self.dict(by_alias=True, exclude_none=True) + }) + +def date_from_string( + date_string:Any, + format_:Any=None, + timezone:Any=None, + on_error:Any=None, + on_null:Any=None +)->DateFromString: + """Returns an $dateFromString statement""" + + return DateFromString( + date_string=date_string, + format_=format_, + timezone=timezone, + on_error=on_error, + on_null=on_null + ) \ No newline at end of file diff --git a/monggregate/operators/strings/date_to_string.py b/monggregate/operators/strings/date_to_string.py new file mode 100644 index 0000000..2a9386a --- /dev/null +++ b/monggregate/operators/strings/date_to_string.py @@ -0,0 +1,42 @@ +""" +Module defining an interface to $date_to_string operator + +""" + +from typing import Any +from monggregate.base import pyd +from monggregate.operators.strings.string import StringOperator + +class DateToString(StringOperator): + """ + xxx + + + """ + + + date : Any + format_ : Any = pyd.Field(alias="format") + timezone : Any + on_null : Any + + @property + def statement(self) -> dict: + return self.resolve({ + "$dateToString" : self.dict(by_alias=True, exclude_none=True) + }) + +def date_to_string( + date:Any, + format_:Any=None, + timezone:Any=None, + on_null:Any=None +)->DateToString: + """Returns an $dateToString statement""" + + return DateToString( + date=date, + format_=format_, + timezone=timezone, + on_null=on_null + ) \ No newline at end of file From e04e67d30e9f97220ac054962026bfbde9badb9d Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Mon, 14 Aug 2023 19:36:49 +0200 Subject: [PATCH 17/26] WIP : Added docstrings for newly interfaced operators --- monggregate/operators/arithmetic/add.py | 24 ++++- monggregate/operators/arithmetic/divide.py | 42 ++++++-- monggregate/operators/arithmetic/multiply.py | 26 ++++- monggregate/operators/arithmetic/pow.py | 57 +++++++++-- monggregate/operators/arithmetic/substract.py | 52 ++++++++-- monggregate/operators/boolean/and_.py | 2 +- monggregate/operators/conditional/cond.py | 45 ++++++++- monggregate/operators/conditional/if_null.py | 51 +++++++++- monggregate/operators/conditional/switch.py | 64 +++++++++++- monggregate/operators/strings/concat.py | 26 ++++- .../operators/strings/date_from_string.py | 84 +++++++++++++++- .../operators/strings/date_to_string.py | 81 +++++++++++++++- monggregate/operators/type_/type_.py | 97 ++++++++++++++++++- 13 files changed, 609 insertions(+), 42 deletions(-) diff --git a/monggregate/operators/arithmetic/add.py b/monggregate/operators/arithmetic/add.py index a6e6b22..a45350d 100644 --- a/monggregate/operators/arithmetic/add.py +++ b/monggregate/operators/arithmetic/add.py @@ -1,5 +1,23 @@ """ -Module defining an interface to $and operator +Module defining an interface to the $add operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/add/#mongodb-expression-exp.-add + +Definition +------------------- +$add +Adds numbers together or adds numbers and a date. If one of the arguments is a date, +$add treats the other arguments as milliseconds to add to the date. + +The $add expression has the following syntax: + + >>> { $add: [ , , ... ] } + +The arguments can be any valid expression +as long as they resolve to either all numbers or to numbers and a date. For more information on expressions, see Expressions. """ @@ -12,7 +30,9 @@ class Add(ArithmeticOperator): Attributes ------------------- - - expressions : list[Any] + - expressions, list[Any] : list of valid expressions, + each expression must resolve to either + all numbers or to numbers and a date """ diff --git a/monggregate/operators/arithmetic/divide.py b/monggregate/operators/arithmetic/divide.py index 69ece01..ad39be8 100644 --- a/monggregate/operators/arithmetic/divide.py +++ b/monggregate/operators/arithmetic/divide.py @@ -1,5 +1,24 @@ """ -Module defining an interface to $divide operator +Module defining an interface to the $divide operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/divide/#mongodb-expression-exp.-divide + +Definition +------------------- +$divide +Divides one number by another and returns the result. Pass the arguments to +$divide in an array. + +The $divide expression has the following syntax: + + >>> { $divide: [ , ] } + +The first argument is the dividend, and the second argument is the divisor; i.e. the first argument is divided by the second argument. + +The arguments can be any valid expression as long as they resolve to numbers. For more information on expressions, see Expressions. """ @@ -8,23 +27,30 @@ class Divide(ArithmeticOperator): """ - xxx + Creates a $divide expression + + Attributes + ------------------- + - numerator, Any : the numerator of the division + - denominator, Any : the denominator of the division """ - expressions : list[Any] + numerator : Any + denominator : Any @property def statement(self) -> dict: return self.resolve({ - "$divide" : self.expressions + "$divide" : [self.numerator, self.denominator] }) -def divide(*args:Any)->Divide: - """Returns an $divide statement""" +def divide(numerator:Any, denominator:Any)->Divide: + """Returns a $divide statement""" return Divide( - expressions=list(args) - ) \ No newline at end of file + numerator=numerator, + denominator=denominator + ) diff --git a/monggregate/operators/arithmetic/multiply.py b/monggregate/operators/arithmetic/multiply.py index e5298c0..c3dffc1 100644 --- a/monggregate/operators/arithmetic/multiply.py +++ b/monggregate/operators/arithmetic/multiply.py @@ -1,5 +1,23 @@ """ -Module defining an interface to $multiply operator +Module defining an interface to the $multiply operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/multiply/#mongodb-expression-exp.-multiply + +Definition +------------------- +$multiply +Multiplies numbers together and returns the result. Pass the arguments to +$multiply in an array. + +The $multiply expression has the following syntax: + + >>> { $multiply: [ , ] } + +The arguments can be any valid expression as long as they resolve to numbers. +For more information on expressions, see Expressions. """ @@ -8,7 +26,11 @@ class Multiply(ArithmeticOperator): """ - xxx + Creates a $multiply expression + + Attributes + ------------------- + - expressions, list[Any] : list of valid expressions """ diff --git a/monggregate/operators/arithmetic/pow.py b/monggregate/operators/arithmetic/pow.py index daaf7e8..7a1f409 100644 --- a/monggregate/operators/arithmetic/pow.py +++ b/monggregate/operators/arithmetic/pow.py @@ -1,5 +1,41 @@ """ -Module defining an interface to $pow operator +Module defining an interface to the $pow operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/pow/#mongodb-expression-exp.-pow + +Definition +------------------- +$pow +Raises a number to the specified exponent and returns the result. +$pow has the following syntax:. + +The $pow expression has the following syntax: + + >>> { $pow: [ , ] } + +The expression can be any valid expression as long as it resolves to a number. + +The expression can be any valid expression as long as it resolves to a number. + +You cannot raise 0 to a negative exponent. + +Behavior +------------------- + +The result will have the same type as the input except when it cannot be represented accurately in that type. In these cases: + + * A 32-bit integer will be converted to a 64-bit integer if the result is representable as a 64-bit integer. + + * A 32-bit integer will be converted to a double if the result is not representable as a 64-bit integer. + + * A 64-bit integer will be converted to double if the result is not representable as a 64-bit integer. + +If either argument resolves to a value of null or refers to a field that is missing, $pow returns null. +If either argument resolves to NaN, $pow returns NaN. + """ @@ -8,23 +44,30 @@ class Pow(ArithmeticOperator): """ - xxx + Creates a $pow expression + + Attributes + ------------------- + - number, Any : the numerator of the division + - exponent, Any : the denominator of the division """ - expressions : list[Any] + number : Any + exponent : Any @property def statement(self) -> dict: return self.resolve({ - "$pow" : self.expressions + "$pow" : [self.number, self.exponent] }) -def pow(*args:Any)->Pow: +def pow(number:Any, exponent:Any)->Pow: """Returns an $pow statement""" return Pow( - expressions=list(args) - ) \ No newline at end of file + number=number, + exponent=exponent + ) diff --git a/monggregate/operators/arithmetic/substract.py b/monggregate/operators/arithmetic/substract.py index 6752fb5..6fd8c88 100644 --- a/monggregate/operators/arithmetic/substract.py +++ b/monggregate/operators/arithmetic/substract.py @@ -1,5 +1,38 @@ """ -Module defining an interface to $substract operator +Module defining an interface to the $substract operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/substract/#mongodb-expression-exp.-substract + +Definition +------------------- +$substract +Subtracts two numbers to return the difference, or two dates to return the difference in milliseconds, +or a date and a number in milliseconds to return the resulting date. + +The $substract expression has the following syntax: + + >>> { $substract: [ , ] } + +The second argument is subtracted from the first argument. + +The arguments can be any valid expression as long as they resolve to numbers and/or dates. To subtract a number from a date, the date must be the first argument. +For more information on expressions, see Expressions. + +Behavior +------------------- + +Starting in MongoDB 5.0, the result will have the same type as the input except when it cannot be represented accurately in that type. In these cases: + + * A 32-bit integer will be converted to a 64-bit integer if the result is representable as a 64-bit integer. + + * A 32-bit integer will be converted to a double if the result is not representable as a 64-bit integer. + + * A 64-bit integer will be converted to double if the result is not representable as a 64-bit integer. + + """ @@ -8,23 +41,30 @@ class Substract(ArithmeticOperator): """ - xxx + Creates a $substract expression + + Attributes + ------------------- + - left, Any : the numerator of the division + - right, Any : the denominator of the division """ - expressions : list[Any] + left: Any + right: Any @property def statement(self) -> dict: return self.resolve({ - "$substract" : self.expressions + "$substract" : [self.left, self.right] }) -def substract(*args:Any)->Substract: +def substract(left:Any, right:Any)->Substract: """Returns an $substract statement""" return Substract( - expressions=list(args) + left=left, + right=right ) \ No newline at end of file diff --git a/monggregate/operators/boolean/and_.py b/monggregate/operators/boolean/and_.py index e5262ed..c92be5e 100644 --- a/monggregate/operators/boolean/and_.py +++ b/monggregate/operators/boolean/and_.py @@ -1,5 +1,5 @@ """ -Module defining an interface to $and operator +Module defining an interface to the $and operator Online MongoDB documentation: -------------------------------------------------------------------------------------------------------------------- diff --git a/monggregate/operators/conditional/cond.py b/monggregate/operators/conditional/cond.py index 35a06fe..b17620c 100644 --- a/monggregate/operators/conditional/cond.py +++ b/monggregate/operators/conditional/cond.py @@ -1,5 +1,33 @@ """ -Module defining an interface to $cond operator +Module defining an interface to the $cond operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/cond/#mongodb-expression-exp.-cond + +Definition +------------------- +$cond +Evaluates a boolean expression to return one of the two specified return expressions. + +The $cond expression has one of two syntaxes: + + >>> { $cond: { if: , then: , else: } } + + or + + >>> { $cond: [ , , ] } + + +$cond requires all three arguments (if-then-else) for either syntax. + +If the evaluates to true, then +$cond evaluates and returns the value of the expression. +Otherwise, $cond evaluates and returns the value of the expression. + +The arguments can be any valid expression. +For more information on expressions, see Expressions. """ @@ -9,7 +37,20 @@ class Cond(ConditionalOperator): """ - xxx + Creates a $cond expression + + Attributes + ------------------- + - if_, Any : the boolean expression to evaluate + - then_, Any : the expression to evaluate if if_ is true + - else_, Any : the expression to evaluate if if_ is false + + - expression, Any : the boolean expression to evaluate (alias for if_) + - true_, Any : the expression to evaluate if expression is true (alias for then_) + - false_, Any : the expression to evaluate if expression is false (alias for else_) + + if_, then_ and else_ have precedence over expression, true_ and false_ + Thus only the first syntax will be used whatever combination of arguments is provided (as long as it is valid) """ diff --git a/monggregate/operators/conditional/if_null.py b/monggregate/operators/conditional/if_null.py index 8424719..ed014e8 100644 --- a/monggregate/operators/conditional/if_null.py +++ b/monggregate/operators/conditional/if_null.py @@ -1,5 +1,45 @@ """ -Module defining an interface to $if_null operator +Module defining an interface to the $ifNull operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/#mongodb-expression-exp.-ifNull + +Definition +------------------- +$ifNull +Changed in version 5.0. + +The $ifNull expression evaluates input expressions for null values and returns: + + * The first non-null input expression value found. + + * A replacement expression value if all input expressions evaluate to null. + +$ifNull treats undefined values and missing fields as null. + +Syntax: + + >>> { + $ifNull: [ + , + ... + , + + ] + } + +In MongoDB 4.4 and earlier versions, $ifNull only accepts a single input expression: +$ifNull requires all three arguments (if-then-else) for either syntax. + + >>> { + $ifNull: [ + , + + ] + } + """ @@ -8,7 +48,12 @@ class IfNull(ConditionalOperator): """ - xxx + Creates a $cond expression + + Attributes + ------------------- + - expression, Any : the boolean expression to evaluate + - output, Any : the expression to evaluate if expression is null """ @@ -29,4 +74,4 @@ def if_null(expression:Any, output:Any)->IfNull: return IfNull( expression=expression, output=output - ) \ No newline at end of file + ) diff --git a/monggregate/operators/conditional/switch.py b/monggregate/operators/conditional/switch.py index 6fa5611..9285292 100644 --- a/monggregate/operators/conditional/switch.py +++ b/monggregate/operators/conditional/switch.py @@ -1,24 +1,80 @@ """ -Module defining an interface to $switch operator +Module defining an interface to the $switch operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/switch/#mongodb-expression-exp.-switch + +Definition +------------------- +$switch +Evaluates a series of case expressions. +When it finds an expression which evaluates to true, +$switch executes a specified expression and breaks out of the control flow. + +The $switch expression has the following syntax: + + >>> { + $switch: { + branches: [ + { case: , then: }, + { case: , then: }, + ... + ], + default: + } + } + + +The objects in the branches array must contain only a case field and a then field. + + +Behavior +------------------- +The various case statements do not need to be mutually exclusive. +$switch executes the first branch it finds which evaluates to true. If none of the branches evaluates to true, +$switch executes the default option. + +The following conditions cause $switch to fail with an error: + + * The branches field is missing or is not an array with at least one entry. + + * An object in the branches array does not contain a case field. + + * An object in the branches array does not contain a then field. + + * An object in the branches array contains a field other than case or then. + + * No default is specified and no case evaluates to true. + """ from typing import Any from monggregate.operators.conditional.conditional import ConditionalOperator -# TODO : Define branc +# TODO : Define branch # {"case": , "then": } class Switch(ConditionalOperator): """ - xxx + Creates a $switch expression + + Attributes + ------------------- + - branches, list[Any] : An array of control branch documents + The branches array must contain at least one branch document. + - default, Any|None : The path to take if no branch case expression evaluates to true. + Although optional, if default is unspecified and no branch case evaluates to true, + $switch returns an error. """ branches : list[Any] - default : Any + default : Any|None @property def statement(self) -> dict: diff --git a/monggregate/operators/strings/concat.py b/monggregate/operators/strings/concat.py index 39721c1..ef91a39 100644 --- a/monggregate/operators/strings/concat.py +++ b/monggregate/operators/strings/concat.py @@ -1,5 +1,23 @@ """ -Module defining an interface to $concat operator +Module defining an interface to the $concat operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/concat/#mongodb-expression-exp.-concat + +Definition +------------------- +$concat +Concatenates strings and returns the concatenated string. + +$concat has the following syntax: + >>> { $concat: [ , , ... ] } + +The arguments can be any valid expression as long as they resolve to strings. For more information on expressions, see Expressions. + +If the argument resolves to a value of null or refers to a field that is missing, +$concat returns null. """ @@ -8,7 +26,11 @@ class Concat(StringOperator): """ - xxx + Creates a $concat expression + + Attributes + ------------------- + - expressions, list[Any] : the list of expressions that must resolve to strings to be concatenated """ diff --git a/monggregate/operators/strings/date_from_string.py b/monggregate/operators/strings/date_from_string.py index fdde60e..7b8d9f9 100644 --- a/monggregate/operators/strings/date_from_string.py +++ b/monggregate/operators/strings/date_from_string.py @@ -1,5 +1,77 @@ """ -Module defining an interface to $date_from_string operator +Module defining an interface to the $dateFromString operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/#mongodb-expression-exp.-dateFromString + +Definition +------------------- +$dateFromString +Converts a date/time string to a date object. + +The $dateFromString expression has the following syntax: + + >>> { + $dateFromString: { + dateString: , + format: , + timezone: , + onError: , + onNull: + } + } + +The $dateFromString takes a document with the following fields: + + +Field Description +------------------- ------------------- +dateString The date/time string to convert to a date object. See Date for more information on date/time formats. + NOTE: If specifying the timezone option to the operator, do not include time zone information in the dateString. + +format Optional. The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + For a list of specifiers available, see Format Specifiers. + If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. + +timezone Optional. The time zone to use to format the date. + NOTE: If the dateString argument is formatted like '2017-02-08T12:10:40.787Z', in which the 'Z' at the end indicates Zulu time (UTC time zone), you cannot specify the timezone argument. + +onError Optional. If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. + This result value can be of any type. + + If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. + +onNull Optional. If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. + This result value can be of any type. + If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. + +Format Specifiers +------------------- + +Specifiers Description Possible Values + +%d Day of Month (2 digits, zero padded) 01-31 +%G Year in ISO 8601 format 0000-9999 +%H Hour (2 digits, zero padded, 24-hour clock) 00-23 +%j Day of year (3 digits, zero padded) 001-366 +%L Millisecond (3 digits, zero padded) 000-999 +%m Month (2 digits, zero padded) 01-12 +%M Minute (2 digits, zero padded) 00-59 +%S Second (2 digits, zero padded) 00-60 +%w Day of week (1-Sunday, 7-Saturday) 1-7 +%u Day of week number in ISO 8601 format + (1-Monday, 7-Sunday) 1-7 +%U Week of year (2 digits, zero padded) 00-53 +%V Week of Year in ISO 8601 format 01-53 +%Y Year (4 digits, zero padded) 0000-9999 +%z The timezone offset from UTC. +/-[hh][mm] +%Z The minutes offset from UTC as a number. + For example, if the timezone offset + (+/-[hhmm]) was +0445, + the minutes offset is +285. +/-mmm +%% Percent Character as a Literal % """ @@ -9,7 +81,15 @@ class DateFromString(StringOperator): """ - xxx + Creates a $concat expression + + Attributes + ------------------- + - date_string, Any : the date/time string to convert to a date object. See Date for more information on date/time formats. + - format_, Any : Optional. The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + - timezone, Any : Optional. The time zone to use to format the date. + - on_error, Any : Optional. If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. + - on_null, Any : Optional. If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. """ diff --git a/monggregate/operators/strings/date_to_string.py b/monggregate/operators/strings/date_to_string.py index 2a9386a..f8bd08c 100644 --- a/monggregate/operators/strings/date_to_string.py +++ b/monggregate/operators/strings/date_to_string.py @@ -1,5 +1,84 @@ """ -Module defining an interface to $date_to_string operator +Module defining an interface to the $dateToString operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/dateToString/#mongodb-expression-exp.-dateToString + +Definition +------------------- +$dateToString +Converts a date object to a string according to a user-specified format. + +The $dateToString expression has the following operator expression syntax: + + >>> { + $dateToString: { + date: , + format: , + timezone: , + onNull: + } + } + +The $dateToString takes a document with the following fields: + +Field Description +------------------- ------------------- + +date Changed in version 3.6. + The date to convert to string. must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + +format Optional. The date format specification. can be any string literal, containing 0 or more format specifiers. + For a list of specifiers available, see Format Specifiers. + + If unspecified, $dateToString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format. + +timezone Optional. The timezone of the operation result. must be a valid expression that resolves to a string formatted as either an + Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + + Format Examples + + Olson Timezone Identifier "America/New_York" + "Europe/London" + "GMT" + + + UTC Offset +/-[hh]:[mm], e.g. "+04:45" + +/-[hh][mm], e.g. "-0530" + +/-[hh], e.g. "+03" + +onNull Optional. The value to return if the date is null or missing. The arguments can be any valid expression. + If unspecified, $dateToString returns null if the date is null or missing. + +Format Specifiers +------------------- + +Specifiers Description Possible Values + +%d Day of Month (2 digits, zero padded) 01-31 +%G Year in ISO 8601 format 0000-9999 +%H Hour (2 digits, zero padded, 24-hour clock) 00-23 +%j Day of year (3 digits, zero padded) 001-366 +%L Millisecond (3 digits, zero padded) 000-999 +%m Month (2 digits, zero padded) 01-12 +%M Minute (2 digits, zero padded) 00-59 +%S Second (2 digits, zero padded) 00-60 +%w Day of week (1-Sunday, 7-Saturday) 1-7 +%u Day of week number in ISO 8601 format + (1-Monday, 7-Sunday) 1-7 +%U Week of year (2 digits, zero padded) 00-53 +%V Week of Year in ISO 8601 format 01-53 +%Y Year (4 digits, zero padded) 0000-9999 +%z The timezone offset from UTC. +/-[hh][mm] +%Z The minutes offset from UTC as a number. + For example, if the timezone offset + (+/-[hhmm]) was +0445, + the minutes offset is +285. +/-mmm +%% Percent Character as a Literal % + + """ diff --git a/monggregate/operators/type_/type_.py b/monggregate/operators/type_/type_.py index 4ef4775..36b6c8c 100644 --- a/monggregate/operators/type_/type_.py +++ b/monggregate/operators/type_/type_.py @@ -1,10 +1,103 @@ -"""xxxx""" +""" +Module defining an interface to the $type operator + +Online MongoDB documentation: +-------------------------------------------------------------------------------------------------------------------- +Last Updated (in this package) : 14/08/2023 +Source : https://docs.mongodb.com/manual/reference/operator/aggregation/type/#mongodb-expression-exp.-type + +Definition +------------------- +$type +Returns a string that specifies the BSON type of the argument. + +$type has the following operator expression syntax: + + >>> { $type: } + +The argument can be any valid expression. + +Behavior +------------------- + +Unlike the $type query operator, which matches array elements based on their BSON type, +the $type aggregation operator does not examine array elements. +Instead, when passed an array as its argument, the $type aggregation operator returns the type of the argument, i.e. "array". + +If the argument is a field that is missing in the input document, $type returns the string "missing". + +The following table shows the $type output for several common types of expressions: + +Example Results +------------------- ------------------- + +{ $type: "a" } "string" +{ $type: /a/ } "regex" +{ $type: 1 } "double" +{ $type: NumberLong(627) } "long" +{ $type: { x: 1 } } "object" +{ $type: [ [ 1, 2, 3 ] ] } "array" + +NOTE: +In the case of a literal array such as [ 1, 2, 3 ], +enclose the expression in an outer set of array brackets to prevent MongoDB from parsing [ 1, 2, 3 ] +as an argument list with three arguments (1, 2, 3). +Wrapping the array [ 1, 2, 3 ] in a $literal expression achieves the same result. + +See operator expression syntax forms for more information. + + +Available Types +------------------- + +The following table lists the BSON types and the corresponding integer values returned by $type: + +Type Number Alias Notes +------------------------------------------------------------- + +Double 1 "double" +String 2 "string" +Object 3 "object" +Array 4 "array" +Binary data 5 "binData" +Undefined 6 "undefined" Deprecated. +ObjectId 7 "objectId" +Boolean 8 "bool" +Date 9 "date" +Null 10 "null" +Regular +Expression 11 "regex" +DBPointer 12 "dbPointer" Deprecated. +JavaScript 13 "javascript" +Symbol 14 "symbol" Deprecated. +JavaScript +code with +scope 15 "javascriptWithScope" Deprecated in MongoDB 4.4. +32-bit integer 16 "int" +Timestamp 17 "timestamp" +64-bit integer 18 "long" +Decimal128 19 "decimal" +Min key -1 "minKey" +Max key 127 "maxKey" + +If the argument is a field that is missing in the input document, +$type returns the string "missing". + + +""" from typing import Any from monggregate.base import BaseModel class Type_(BaseModel): - """xxxx""" + """ + Creates a $type expression + + Attributes + ------------------- + - expression, Any : expression whose type must be evaluated + + """ expression:Any From b2785aa9df305c64cfb487f50e5295dcf6816591 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 15 Aug 2023 11:44:42 +0200 Subject: [PATCH 18/26] Added changelog --- changelog.md | 26 +++++++++++++++++++++++ monggregate/operators/accumulators/avg.py | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f09f053..adf7d85 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,31 @@ # Release Notes +## 0.16.0 + + +### New Features + +* Created S object (represents $ sign since it is not a valid variable name in python) to store all MongoDB operators and to create references to fields +* Created SS object (represents $$) to store aggregation variables and referenes to user variables +* Interfaced a chunk of new operators(add, divide, multiply, pow, substract, cond, if_null, switch, millisecond, date_from_string, date_to_string, type_) + +### Refactoring + +* Removed index module that was at the root of the package (monggregate.index.py -> ø) +* Removed expressions subpackage (monggregate.expression -> ø) +* Moved expressions fields module to the root of the package (monggregate.expressions.fields.py -> monggregate.fields.py) +* Removed expressions aggregation_variables module (monggregate.expression.aggregation_variables.py -> ø) +* Moved the enums that were defined in index to a more relevant place. Ex OperatorEnum is now in monggregate.operators.py + +### Breaking Changes + +* Operators now return python objects rather than expressions/statements. + NOTE: The wording might change for clarification purposes. + statement might be renamed expression and resolve might renamed express + To do so, some arguments might need to be renamed in the operators +* Expressions subpackage has been exploded and some parts have been deleted + + ## 0.15.0 ### Fixes diff --git a/monggregate/operators/accumulators/avg.py b/monggregate/operators/accumulators/avg.py index 5ae4af2..301c559 100644 --- a/monggregate/operators/accumulators/avg.py +++ b/monggregate/operators/accumulators/avg.py @@ -57,7 +57,7 @@ Array Operand -In the $group stage, if the expression resolves to an array, $avgtreats the operand as a non-numerical value. +In the $group stage, if the expression resolves to an array, $avg treats the operand as a non-numerical value. In the other supported stages: From 396de2a9b9e4488a71994d18f52a925b5d90ecdd Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 15 Aug 2023 12:12:45 +0200 Subject: [PATCH 19/26] WIP : Improving docstrings --- monggregate/base.py | 13 ++++++++++++- monggregate/dollar.py | 27 ++++++++++++++------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/monggregate/base.py b/monggregate/base.py index 4cfbf5c..c15b40f 100644 --- a/monggregate/base.py +++ b/monggregate/base.py @@ -1,4 +1,8 @@ -"""Module defining the base class of the package""" +""" +Module defining the base classes of the package. + +All the classes of the package inherit from one of the classes defined in this module. +""" # Standard Library imports #---------------------------- @@ -15,7 +19,14 @@ from humps.main import camelize +class Singleton: + """Singleton metaclass""" + _instance = None + def __new__(cls, *args, **kwargs): + if not isinstance(cls._instance, cls): + cls._instance = object.__new__(cls, *args, **kwargs) + return cls._instance class BaseModel(pyd.BaseModel, ABC): """Mongreggate base class""" diff --git a/monggregate/dollar.py b/monggregate/dollar.py index 1b31984..7736847 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -1,8 +1,19 @@ -"""WIP""" +""" +Module defining the Dollar and DollarDollar classes. +Those classes aim to abstract the MongoDB dollar sign ($) and double dollar sign ($$) in python. +It is recommended to import S and SS from this module. With thoses two objects you will get a +reference to all MongoDB operators and aggregation variables with in-code documentation, typing, validation and autocompletion. + +""" + +# Standard Library imports +#---------------------------- from typing import Any, Literal -from typing_extensions import Self +# Local imports +#---------------------------- +from monggregate.base import Singleton from monggregate.operators import( accumulators, array, @@ -41,17 +52,7 @@ class AggregationVariableEnum(StrEnum): KEEP = AggregationVariableEnum.KEEP.value # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with -# it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance -class Singleton: - """Singleton metaclass""" - - _instance = None - def __new__(cls, *args, **kwargs): - if not isinstance(cls._instance, cls): - cls._instance = object.__new__(cls, *args, **kwargs) - return cls._instance - - +# it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance class Dollar(Singleton): """ MongoDB dollar sign ($) abstraction in python. From 08a95f47b0efaa3c05e8a8629162f6f98ae8af22 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Sat, 19 Aug 2023 10:22:49 +0200 Subject: [PATCH 20/26] Integrating new operators in Dollar class --- monggregate/dollar.py | 144 +++++++++++++++++- .../operators/accumulators/__init__.py | 2 - monggregate/operators/arithmetic/__init__.py | 18 ++- .../arithmetic/{substract.py => subtract.py} | 6 +- monggregate/operators/conditional/__init__.py | 10 +- monggregate/operators/date/__init__.py | 6 +- monggregate/operators/date/millisecond.py | 19 ++- monggregate/operators/strings/__init__.py | 9 +- monggregate/operators/type_/__init__.py | 2 +- test/test_expressions.py | 1 - 10 files changed, 185 insertions(+), 32 deletions(-) rename monggregate/operators/arithmetic/{substract.py => subtract.py} (94%) diff --git a/monggregate/dollar.py b/monggregate/dollar.py index 7736847..bb88257 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -16,10 +16,14 @@ from monggregate.base import Singleton from monggregate.operators import( accumulators, + arithmetic, array, + boolean, comparison, + conditional, + date, objects, - boolean, + strings, type_ ) @@ -140,6 +144,80 @@ def sum(cls, expression:Any)->accumulators.Sum: return accumulators.sum(expression) + # Arithmetic + # -------------------------- + @classmethod + def add(cls, *args:Any)->arithmetic.Add: + """Returns the $add operator""" + + return arithmetic.add(*args) + + # @classmethod + # def ceil(cls, expression:Any)->arithmetic.Ceil: + # """Returns the $ceil operator""" + + # return arithmetic.ceil(expression) + + @classmethod + def divide(cls, *args:Any)->arithmetic.Divide: + """Returns the $divide operator""" + + return arithmetic.divide(*args) + + # @classmethod + # def exp(cls, expression:Any)->arithmetic.Exp: + # """Returns the $exp operator""" + + # return arithmetic.exp(expression) + + # @classmethod + # def floor(cls, expression:Any)->arithmetic.Floor: + # """Returns the $floor operator""" + + # return arithmetic.floor(expression) + + + # @classmethod + # def ln(cls, expression:Any)->arithmetic.Ln: + # """Returns the $ln operator""" + + # return arithmetic.ln(expression) + + + # @classmethod + # def log(cls, *args:Any)->arithmetic.Log: + # """Returns the $log operator""" + + # return arithmetic.log(*args) + + + # @classmethod + # def log10(cls, expression:Any)->arithmetic.Log10: + # """Returns the $log10 operator""" + + # return arithmetic.log10(expression) + + + # @classmethod + # def mod(cls, *args:Any)->arithmetic.Mod: + # """Returns the $mod operator""" + + # return arithmetic.mod(*args) + + + @classmethod + def multiply(cls, *args:Any)->arithmetic.Multiply: + """Returns the $multiply operator""" + + return arithmetic.multiply(*args) + + + @classmethod + def pow(cls, *args:Any)->arithmetic.Pow: + """Returns the $pow operator""" + + return arithmetic.pow(*args) + # Array # -------------------------- @classmethod @@ -237,6 +315,70 @@ def ne(cls, left:Any, right:Any)->comparison.Ne: return comparison.ne(left, right) + # Conditional + # -------------------------- + @classmethod + def cond(cls, if_:Any, then:Any, else_:Any)->conditional.Cond: + """Returns the $cond operator""" + + return conditional.cond(if_, then, else_) + + @classmethod + def if_null(cls, expression:Any, replacement:Any)->conditional.IfNull: + """Returns the $ifNull operator""" + + return conditional.if_null(expression, replacement) + + @classmethod + def switch(cls, branches:dict[Any, Any], default:Any)->conditional.Switch: + """Returns the $switch operator""" + + return conditional.switch(branches, default) + + # Date + # -------------------------- + @classmethod + def millisecond(cls, expression:Any, timezone:Any)->date.Millisecond: + """Returns the $millisecond operator""" + + return date.millisecond(expression, timezone) + + + # String + # -------------------------- + @classmethod + def concat(cls, *args:Any)->strings.Concat: + """Returns the $concat operator""" + + return strings.concat(*args) + + @classmethod + def date_from_string( + cls, + date_string:Any, + format:Any=None, + timezone:Any=None, + on_error:Any=None, + on_null:Any=None + )->strings.DateFromString: + """Returns the $dateFromString operator""" + + return strings.date_from_string(date_string, format, timezone, on_error, on_null) + + + @classmethod + def date_to_string( + cls, + expression: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) + + # Objects # -------------------------- @classmethod diff --git a/monggregate/operators/accumulators/__init__.py b/monggregate/operators/accumulators/__init__.py index ba66c9a..2a93968 100644 --- a/monggregate/operators/accumulators/__init__.py +++ b/monggregate/operators/accumulators/__init__.py @@ -22,5 +22,3 @@ # * $stdDevSamp # * $top # * $topN -# -# \ No newline at end of file diff --git a/monggregate/operators/arithmetic/__init__.py b/monggregate/operators/arithmetic/__init__.py index f0c56f7..66cf201 100644 --- a/monggregate/operators/arithmetic/__init__.py +++ b/monggregate/operators/arithmetic/__init__.py @@ -1,19 +1,25 @@ -"""xxx""" +"""Arithmetic operators subpackage""" + +from monggregate.operators.arithmetic.add import Add, add +from monggregate.operators.arithmetic.divide import Divide, divide +from monggregate.operators.arithmetic.multiply import Multiply, multiply +from monggregate.operators.arithmetic.pow import Pow, pow +from monggregate.operators.arithmetic.subtract import Subtract, subtract # TODO: # * $abs -# * $add + # * $ceil -# * $divide + # * $exp # * $floor # * $ln # * $log # * $log10 # * $mod -# * $multiply -# * $pow + + # * $round # * $sqrt -# * $subtract + # * $trunc diff --git a/monggregate/operators/arithmetic/substract.py b/monggregate/operators/arithmetic/subtract.py similarity index 94% rename from monggregate/operators/arithmetic/substract.py rename to monggregate/operators/arithmetic/subtract.py index 6fd8c88..1612476 100644 --- a/monggregate/operators/arithmetic/substract.py +++ b/monggregate/operators/arithmetic/subtract.py @@ -39,7 +39,7 @@ from typing import Any from monggregate.operators.arithmetic.arithmetic import ArithmeticOperator -class Substract(ArithmeticOperator): +class Subtract(ArithmeticOperator): """ Creates a $substract expression @@ -61,10 +61,10 @@ def statement(self) -> dict: "$substract" : [self.left, self.right] }) -def substract(left:Any, right:Any)->Substract: +def subtract(left:Any, right:Any)->Subtract: """Returns an $substract statement""" - return Substract( + return Subtract( left=left, right=right ) \ No newline at end of file diff --git a/monggregate/operators/conditional/__init__.py b/monggregate/operators/conditional/__init__.py index 9a49289..bf273b2 100644 --- a/monggregate/operators/conditional/__init__.py +++ b/monggregate/operators/conditional/__init__.py @@ -1,6 +1,6 @@ -"""xxx""" +"""Conditional (control flows) operators subpackage""" + +from monggregate.operators.conditional.cond import Cond, cond +from monggregate.operators.conditional.if_null import IfNull, if_null +from monggregate.operators.conditional.switch import Switch, switch -# TODO: -# * $cond -# * $ifNull -# * $switch diff --git a/monggregate/operators/date/__init__.py b/monggregate/operators/date/__init__.py index 352c060..0b03162 100644 --- a/monggregate/operators/date/__init__.py +++ b/monggregate/operators/date/__init__.py @@ -1,4 +1,6 @@ -"""xxx""" +"""Date operators subpackage""" + +from monggregate.operators.date.millisecond import Millisecond, millisecond # TODO: # $dateAdd @@ -16,7 +18,7 @@ # $isoDayOfWeek # $isoWeek # $isoWeekYear -# $millisecond + # $minute # $month # $second diff --git a/monggregate/operators/date/millisecond.py b/monggregate/operators/date/millisecond.py index 533c41b..0d29264 100644 --- a/monggregate/operators/date/millisecond.py +++ b/monggregate/operators/date/millisecond.py @@ -6,24 +6,29 @@ from typing import Any from monggregate.operators.date.date import DateOperator -class MilliSecond(DateOperator): +class Millisecond(DateOperator): """ - xxx + Creates a $millisecond expression + + Attributes + ------------------- + - expression, Any : the expression that must resolve to a date + - timezone, Any | None : the timezone to use for the date """ expression : Any - timezeone : Any | None + timezone : Any | None @property def statement(self) -> dict: - if self.timezeone: + if self.timezone: inner = { "date" : self.expression, - "timezone" : self.timezeone + "timezone" : self.timezone } else: inner = self.expression @@ -32,10 +37,10 @@ def statement(self) -> dict: "$millisecond" : inner }) -def millisecond(expression:Any, timezone:Any)->MilliSecond: +def millisecond(expression:Any, timezone:Any)->Millisecond: """Returns an $millisecond statement""" - return MilliSecond( + return Millisecond( expression=expression, timezone=timezone ) \ No newline at end of file diff --git a/monggregate/operators/strings/__init__.py b/monggregate/operators/strings/__init__.py index 7224e77..5736f0a 100644 --- a/monggregate/operators/strings/__init__.py +++ b/monggregate/operators/strings/__init__.py @@ -1,8 +1,9 @@ -"""xxxx""" +"""String Operators subpackage""" + +from monggregate.operators.strings.concat import Concat, concat +from monggregate.operators.strings.date_from_string import DateFromString, date_from_string +from monggregate.operators.strings.date_to_string import DateToString, date_to_string -# $concat -# $dateFromString -# $dateToString # $indexOfBytes # $indexOfCP # $ltrim diff --git a/monggregate/operators/type_/__init__.py b/monggregate/operators/type_/__init__.py index 54ff1f3..585cfae 100644 --- a/monggregate/operators/type_/__init__.py +++ b/monggregate/operators/type_/__init__.py @@ -1 +1 @@ -"""xxx""" \ No newline at end of file +"""Type operators subpackage""" \ No newline at end of file diff --git a/test/test_expressions.py b/test/test_expressions.py index a23065e..6a3c1bb 100644 --- a/test/test_expressions.py +++ b/test/test_expressions.py @@ -1,4 +1,3 @@ - """Module to test expressions""" From 198387c53917800fe0a6a90d7e2fd8d88fc2057d Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 22 Aug 2023 19:54:06 +0200 Subject: [PATCH 21/26] refactored and clarified how to use expressions module --- monggregate/expressions.py | 230 +++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 126 deletions(-) diff --git a/monggregate/expressions.py b/monggregate/expressions.py index d714dd6..8cf8ed0 100644 --- a/monggregate/expressions.py +++ b/monggregate/expressions.py @@ -9,6 +9,7 @@ # Standard Library imports #---------------------------- from typing import Any, Literal +from typing_extensions import Self # 3rd Party imports # --------------------------- @@ -17,41 +18,18 @@ # Local imports # ---------------------------- from monggregate.fields import FieldPath, Variable -from monggregate.operators.accumulators import( - Avg, - Count, - First, - Last, - Max, - Min, - Push, - Sum +from monggregate.operators import( + accumulators, + arithmetic, + array, + boolean, + comparison, + conditional, + date, + objects, + strings, + type_, ) -from monggregate.operators.array import( - ArrayToObject, - Filter, - #First, - In, - IsArray, - #Last, - MaxN, - MinN, - SortArray -) -from monggregate.operators.comparison import( - Cmp, - Eq, - Gt, - Gte, - Lt, - Lte, - Ne -) -from monggregate.operators.objects import( - MergeObjects, - ObjectToArray -) -from monggregate.operators.boolean import And, Or, Not #Expressions can include field paths, literals, system variables, expression objects, and expression operators. Expressions can be nested. #ExpressionObjects {field1: Expression} #OperatorExpression {operator:[arg, .. argN]} or {operator:arg} @@ -77,35 +55,35 @@ def statement(self)->Any: # Expression Internal Methods #---------------------------------------------------- @classmethod - def constant(cls, value:int | float | str | bool | None)->"Expression": + def constant(cls, value:int | float | str | bool | None)->Self: """Creates a constant expression.""" return cls(content=value) @classmethod - def field(cls, name:str)->"Expression": + def field(cls, name:str)->Self: """Creates a field expression.""" if not name.startswith("$"): name = f"${name}" - return cls(content=FieldPath(name=name)) + return cls(content=FieldPath(name)) @classmethod - def variable(cls, name:str)->"Expression": + def variable(cls, name:str)->Self: """Creates a variable expression.""" while not variable.startswith("$$"): variable = "$" + variable - return cls(content=Variable(name=name)) + return cls(content=Variable(name)) #--------------------------------------------------- # Logical Operators #--------------------------------------------------- - def __and__(self, other:"Expression")->"Expression": + def __and__(self, other:Self)->Self: """ Creates an And operator expression. @@ -113,10 +91,10 @@ def __and__(self, other:"Expression")->"Expression": """ - self.content = And(expressions=[self, other]).statement - return self + return self.__class__(content=boolean.And(expressions=[self, other])) + - def __or__(self, other:"Expression")->"Expression": + def __or__(self, other:Self)->Self: """ Creates an Or operator expression. @@ -124,10 +102,10 @@ def __or__(self, other:"Expression")->"Expression": """ - self.content = Or(expressions=[self, other]).statement - return self + return self.__class__(content=boolean.Or(expressions=[self, other])) + - def __invert__(self)->"Expression": + def __invert__(self)->Self: """ Creates an Not operator expression. @@ -135,13 +113,13 @@ def __invert__(self)->"Expression": """ - self.content = Not(expression=self) - return self + return self.__class__(content=boolean.Not(expression=self)) + #--------------------------------------------------- # Comparison Operators #--------------------------------------------------- - def __eq__(self, other:"Expression")->"Expression": + def __eq__(self, other:Self)->Self: """ Creates a $eq expression. @@ -149,10 +127,10 @@ def __eq__(self, other:"Expression")->"Expression": """ - self.content = Eq(left=self, right=other) - return self + return self.__class__(content=comparison.Eq(left=self, right=other)) + - def __lt__(self, other:"Expression")->"Expression": + def __lt__(self, other:Self)->Self: """ Creates a $lt expression. @@ -160,10 +138,10 @@ def __lt__(self, other:"Expression")->"Expression": """ - self.content = Lt(left=self, right=other) - return self + return self.__class__(content=comparison.Lt(left=self, right=other)) + - def __le__(self, other:"Expression")->"Expression": + def __le__(self, other:Self)->Self: """ Creates a $le expression. @@ -171,10 +149,10 @@ def __le__(self, other:"Expression")->"Expression": """ - self.content = Lte(left=self, right=other) - return self + return self.__class__(content=comparison.Lte(left=self, right=other)) + - def __gt__(self, other:"Expression")->"Expression": + def __gt__(self, other:Self)->Self: """ Creates a $gt expression. @@ -182,10 +160,10 @@ def __gt__(self, other:"Expression")->"Expression": """ - self.content = Gt(left=self, right=other) - return self + return self.__class__(content=comparison.Gt(left=self, right=other)) + - def __ge__(self, other:"Expression")->"Expression": + def __ge__(self, other:Self)->Self: """ Creates a $gte expression. @@ -193,10 +171,10 @@ def __ge__(self, other:"Expression")->"Expression": """ - self.content = Gte(left=self, right=other) - return self + return self.__class__(content=comparison.Gte(left=self, right=other)) + - def __ne__(self, other:"Expression")->"Expression": + def __ne__(self, other:Self)->Self: """ Creates a $lt expression. @@ -204,156 +182,156 @@ def __ne__(self, other:"Expression")->"Expression": """ - self.content = Ne(left=self, right=other) - return self + return self.__class__(content=comparison.Ne(left=self, right=other)) + - def compare(self, other:"Expression")->"Expression": + def compare(self, other:Self)->Self: """Creates a $cmp expression.""" - self.content = Cmp(left=self, right=other) - return self + return self.__class__(content=comparison.Cmp(left=self, right=other)) + #--------------------------------------------------- # Accumulators (Aggregation) Operators #--------------------------------------------------- - def average(self)->"Expression": + def average(self)->Self: """Creates an $avg expression""" - self.content = Avg(expression=self) - return self + return self.__class__(content=accumulators.Avg(expression=self)) + - def count(self)->"Expression": + def count(self)->Self: """Creates a $count expression""" - self.content = Count() - return self + return self.__class__(content=accumulators.Count()) + - def first(self)->"Expression": + def first(self)->Self: """Creates a $first expression""" - self.content = First(expression=self) - return self + return self.__class__(content=accumulators.First(expression=self)) + - def last(self)->"Expression": + def last(self)->Self: """Creates a $last expression""" - self.content = Last(expression=self) - return self + return self.__class__(content=accumulators.Last(expression=self)) + - def max(self)->"Expression": + def max(self)->Self: """Creates a $max expression""" - self.content = Max(expression=self) - return self + return self.__class__(content=accumulators.Max(expression=self)) + - def min(self)->"Expression": + def min(self)->Self: """Creates a $min expression""" - self.content = Min(expression=self) - return self + return self.__class__(content=accumulators.Min(expression=self)) + - def push(self)->"Expression": + def push(self)->Self: """Creates a $push expression""" - self.content = Push(expression=self) - return self + return self.__class__(content=accumulators.Push(expression=self)) + - def sum(self)->"Expression": + def sum(self)->Self: """Creates a $sum expression""" - self.content = Sum(expression=self) - return self + return self.__class__(content=accumulators.Sum(expression=self)) + #--------------------------------------------------- # Array Operators #--------------------------------------------------- - def array_to_object(self)->"Expression": + def array_to_object(self)->Self: """Creates a $arrayToObject expression""" - self.content = ArrayToObject(expression=self) - return self - - def in_(self, right:"Expression")->"Expression": + return self.__class__(content=array.ArrayToObject(expression=self)) + + + def in_(self, right:Self)->Self: """Creates a $in operator""" - self.content = In(left=self, right=right) - return self + return self.__class__(content=array.In(left=self, right=right)) + - def __contains__(self, right:"Expression")->"Expression": + def __contains__(self, right:Self)->Self: """Creates a $in expression""" - return self.in_(right=right) + return self.__class__(content=array.In(left=self, right=right)) - def filter(self, query:"Expression", let:str|None=None, limit:int|None=None)->"Expression": + def filter(self, query:Self, let:str|None=None, limit:int|None=None)->Self: """"Creates a $filter expression""" - self.content = Filter( + return self.__class__(content=array.Filter( expression=self, query=query, let=let, limit=limit - ) - return self + )) + - def is_array(self)->"Expression": + def is_array(self)->Self: """Creates a $isArray expression""" - self.content = IsArray(expression=self) - return self + return self.__class__(content=array.IsArray(expression=self)) + - def max_n(self, limit:int=1)->"Expression": + def max_n(self, limit:int=1)->Self: """Creates a $maxN expression""" - self.content = MaxN(expression=self, limit=limit) - return self + return self.__class__(content=array.MaxN(expression=self, limit=limit)) + - def min_n(self, limit:int=1)->"Expression": + def min_n(self, limit:int=1)->Self: """Creates a $maxN expression""" - self.content = MinN(expression=self, limit=limit) - return self + return self.__class__(content=array.MinN(expression=self, limit=limit)) + - def sort_array(self, by:dict[str, Literal[1, -1]])->"Expression": + def sort_array(self, by:dict[str, Literal[1, -1]])->Self: """Creates a $sortArray expression""" - self.content = SortArray(expression=self, by=by) - return self + return self.__class__(content=array.SortArray(expression=self, by=by)) + #--------------------------------------------------- # Objects Operators #--------------------------------------------------- - def merge_objects(self, )->"Expression": + def merge_objects(self)->Self: """Creates a $mergeObjects operator""" - self.content = MergeObjects(expression=self) - return self + return self.__class__(content=objects.MergeObjects(expression=self)) + - def object_to_array(self, )->"Expression": + def object_to_array(self)->Self: """Creates a $objectToArray operator""" - self.content = ObjectToArray(expression=self) - return self + return self.__class__(content=objects.ObjectToArray(expression=self)) + if __name__ == "__main__": - result = Expression(field="left") & Expression(field="right") - print(result) + result = Expression.field("left") & Expression.field("right") + print(result()) # Examples of expression: From 76529bf7b0e3de46a93fdbbbe3ea500205cecfcc Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 25 Aug 2023 17:31:13 +0200 Subject: [PATCH 22/26] Integrated new operators into expressions module --- monggregate/expressions.py | 404 +++++++++++++++++++++++++------------ 1 file changed, 270 insertions(+), 134 deletions(-) diff --git a/monggregate/expressions.py b/monggregate/expressions.py index 8cf8ed0..6aeb66c 100644 --- a/monggregate/expressions.py +++ b/monggregate/expressions.py @@ -1,11 +1,5 @@ """Expressions Module""" -# https://github.com/pydantic/pydantic/issues/2279 -# https://stackoverflow.com/questions/53638973/recursive-type-annotations -# https://stackoverflow.com/questions/53845024/defining-a-recursive-type-hint-in-python - -# TO HELP REFACTOR: https://www.practical-mongodb-aggregations.com/guides/expressions.html - # Standard Library imports #---------------------------- from typing import Any, Literal @@ -30,10 +24,6 @@ strings, type_, ) -#Expressions can include field paths, literals, system variables, expression objects, and expression operators. Expressions can be nested. -#ExpressionObjects {field1: Expression} -#OperatorExpression {operator:[arg, .. argN]} or {operator:arg} - class Expression(BaseModel): """ @@ -79,177 +69,176 @@ def variable(cls, name:str)->Self: return cls(content=Variable(name)) - #--------------------------------------------------- - # Logical Operators + # Accumulators (Aggregation) Operators #--------------------------------------------------- - def __and__(self, other:Self)->Self: - """ - Creates an And operator expression. + def average(self)->Self: + """Creates an $avg expression""" - Overloads python bitwise AND operator ($). - """ + + return self.__class__(content=accumulators.Avg(expression=self)) + + + def count(self)->Self: + """Creates a $count expression""" - return self.__class__(content=boolean.And(expressions=[self, other])) + return self.__class__(content=accumulators.Count()) - def __or__(self, other:Self)->Self: - """ - Creates an Or operator expression. + def first(self)->Self: + """Creates a $first expression""" - Overloads python bitwise OR operator (|). - """ + + return self.__class__(content=accumulators.First(expression=self)) + + + def last(self)->Self: + """Creates a $last expression""" - return self.__class__(content=boolean.Or(expressions=[self, other])) + 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 __invert__(self)->Self: - """ - Creates an Not operator expression. + def min(self)->Self: + """Creates a $min expression""" - Overloads the python bitwise NOT operator (~). - """ + + return self.__class__(content=accumulators.Min(expression=self)) + + + def push(self)->Self: + """Creates a $push expression""" - return self.__class__(content=boolean.Not(expression=self)) + return self.__class__(content=accumulators.Push(expression=self)) + + def sum(self)->Self: + """Creates a $sum expression""" + + return self.__class__(content=accumulators.Sum(expression=self)) + + #--------------------------------------------------- - # Comparison Operators + # Arithmetic Operators #--------------------------------------------------- - def __eq__(self, other:Self)->Self: + def __add__(self, other:Self)->Self: """ - Creates a $eq expression. + Creates a $add expression. - Overloads python Equal to operator (==). + Overloads python addition operator (+). """ - return self.__class__(content=comparison.Eq(left=self, right=other)) - + return self.__class__(content=arithmetic.Add(left=self, right=other)) + - def __lt__(self, other:Self)->Self: + def __sub__(self, other:Self)->Self: """ - Creates a $lt expression. + Creates a $subtract expression. - Overloads python Less than operator (<). + Overloads python subtraction operator (-). """ - return self.__class__(content=comparison.Lt(left=self, right=other)) - + return self.__class__(content=arithmetic.Subtract(left=self, right=other)) + - def __le__(self, other:Self)->Self: + def __mul__(self, other:Self)->Self: """ - Creates a $le expression. + Creates a $multiply expression. - Overloads python Less than or equal to operator (<=). + Overloads python multiplication operator (*). """ - return self.__class__(content=comparison.Lte(left=self, right=other)) - + return self.__class__(content=arithmetic.Multiply(left=self, right=other)) + - def __gt__(self, other:Self)->Self: + def __div__(self, other:Self)->Self: """ - Creates a $gt expression. + Creates a $divide expression. - Overloads python Greater than operator (>). + Overloads python division operator (/). """ - return self.__class__(content=comparison.Gt(left=self, right=other)) - + return self.__class__(content=arithmetic.Divide(left=self, right=other)) + - def __ge__(self, other:Self)->Self: + def __pow__(self, other:Self)->Self: """ - Creates a $gte expression. + Creates a $pow expression. - Overloads python Greather than or equal to operator (>=). + Overloads python power operator (**). """ - return self.__class__(content=comparison.Gte(left=self, right=other)) - + return self.__class__(content=arithmetic.Pow(left=self, right=other)) + - def __ne__(self, other:Self)->Self: + def __radd__(self, other:Self)->Self: """ - Creates a $lt expression. + Creates a $add expression. - Overloads python Not equal to operator (!=). + Overloads python addition 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)) - - - #--------------------------------------------------- - # Accumulators (Aggregation) Operators - #--------------------------------------------------- - def average(self)->Self: - """Creates an $avg expression""" + return self.__class__(content=arithmetic.Add(left=other, right=self)) + - - return self.__class__(content=accumulators.Avg(expression=self)) - + def __rsub__(self, other:Self)->Self: + """ + Creates a $subtract expression. - def count(self)->Self: - """Creates a $count expression""" + Overloads python subtraction operator (-). + """ - return self.__class__(content=accumulators.Count()) - - - def first(self)->Self: - """Creates a $first expression""" + return self.__class__(content=arithmetic.Subtract(left=other, right=self)) + - - return self.__class__(content=accumulators.First(expression=self)) - + def __rmul__(self, other:Self)->Self: + """ + Creates a $multiply expression. - def last(self)->Self: - """Creates a $last expression""" + Overloads python multiplication operator (*). + """ - return self.__class__(content=accumulators.Last(expression=self)) - + return self.__class__(content=arithmetic.Multiply(left=other, right=self)) + + def __rdiv__(self, other:Self)->Self: + """ + Creates a $divide expression. - def max(self)->Self: - """Creates a $max expression""" + Overloads python division operator (/). + """ - return self.__class__(content=accumulators.Max(expression=self)) - - - def min(self)->Self: - """Creates a $min expression""" + return self.__class__(content=arithmetic.Divide(left=other, right=self)) + - - return self.__class__(content=accumulators.Min(expression=self)) - + def __rpow__(self, other:Self)->Self: + """ + Creates a $pow expression. - def push(self)->Self: - """Creates a $push expression""" + Overloads python power operator (**). + """ - return self.__class__(content=accumulators.Push(expression=self)) - - - def sum(self)->Self: - """Creates a $sum expression""" - - return self.__class__(content=accumulators.Sum(expression=self)) - - + return self.__class__(content=arithmetic.Pow(left=other, right=self)) + #--------------------------------------------------- # Array Operators #--------------------------------------------------- @@ -310,8 +299,150 @@ def sort_array(self, by:dict[str, Literal[1, -1]])->Self: 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 #--------------------------------------------------- @@ -327,31 +458,36 @@ def object_to_array(self)->Self: return self.__class__(content=objects.ObjectToArray(expression=self)) - - -if __name__ == "__main__": - result = Expression.field("left") & Expression.field("right") - print(result()) - -# Examples of expression: - -{"$sum":1} + -{"$type" : "number"} + #--------------------------------------------------- + # String Operators + #--------------------------------------------------- + def concat(self, *args:Self)->Self: + """Creates a $concat operator""" -{ "$avg": { "$multiply": [ "$price", "$quantity" ] } } # awesome example + + return self.__class__(content=strings.Concat(expressions=[self, *args])) + + def date_from_string(self)->Self: + """Creates a $dateFromString operator""" -{ "$avg": "$quantity" } + + return self.__class__(content=strings.DateFromString(expression=self)) + + + def date_to_string(self)->Self: + """Creates a $dateToString operator""" -{ "$first": "$date" } + + return self.__class__(content=strings.DateToString(expression=self)) -{ "$mergeObjects": [ { "$arrayElemAt": [ "$fromItems", 0 ] }, "$$ROOT" ] } + #--------------------------------------------------- + # Type Operators + #--------------------------------------------------- + def type_(self)->Self: + """Creates a $type operator""" -{ - "$map": - { - "input": "$quizzes", - "as": "grade", - "in": { "$add": [ "$$grade", 2 ] } - } -} + + return self.__class__(content=type_.Type_(expression=self)) + \ No newline at end of file From 89157885d7ba7bd654a2376192be731476ea1d94 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Fri, 25 Aug 2023 19:37:12 +0200 Subject: [PATCH 23/26] Improving docstrings --- monggregate/dollar.py | 90 +++++++++++++------ monggregate/operators/accumulators/avg.py | 2 +- monggregate/operators/accumulators/count.py | 2 +- monggregate/operators/accumulators/first.py | 2 +- monggregate/operators/accumulators/last.py | 2 +- monggregate/operators/accumulators/max.py | 2 +- monggregate/operators/accumulators/min.py | 2 +- monggregate/operators/accumulators/push.py | 2 +- monggregate/operators/accumulators/sum.py | 2 +- monggregate/operators/arithmetic/add.py | 2 +- monggregate/operators/arithmetic/divide.py | 2 +- monggregate/operators/arithmetic/multiply.py | 2 +- monggregate/operators/arithmetic/pow.py | 2 +- monggregate/operators/arithmetic/subtract.py | 2 +- .../operators/array/array_to_object.py | 2 +- monggregate/operators/array/filter.py | 2 +- monggregate/operators/array/first.py | 2 +- monggregate/operators/array/in_.py | 2 +- monggregate/operators/array/is_array.py | 2 +- monggregate/operators/array/last.py | 2 +- monggregate/operators/array/max_n.py | 2 +- monggregate/operators/array/min_n.py | 2 +- monggregate/operators/array/size.py | 2 +- monggregate/operators/array/sort_array.py | 2 +- monggregate/operators/boolean/and_.py | 2 +- monggregate/operators/boolean/not_.py | 2 +- monggregate/operators/boolean/or_.py | 2 +- monggregate/operators/comparison/eq.py | 2 +- monggregate/operators/comparison/gt.py | 2 +- monggregate/operators/comparison/gte.py | 2 +- monggregate/operators/comparison/lt.py | 2 +- monggregate/operators/comparison/lte.py | 2 +- monggregate/operators/comparison/ne.py | 2 +- monggregate/operators/conditional/cond.py | 2 +- monggregate/operators/conditional/if_null.py | 2 +- monggregate/operators/conditional/switch.py | 2 +- monggregate/operators/date/millisecond.py | 2 +- .../operators/objects/merge_objects.py | 2 +- .../operators/objects/object_to_array.py | 2 +- monggregate/operators/strings/concat.py | 2 +- .../operators/strings/date_from_string.py | 2 +- .../operators/strings/date_to_string.py | 2 +- monggregate/operators/type_/type_.py | 2 +- 43 files changed, 104 insertions(+), 70 deletions(-) diff --git a/monggregate/dollar.py b/monggregate/dollar.py index bb88257..0d9e415 100644 --- a/monggregate/dollar.py +++ b/monggregate/dollar.py @@ -29,23 +29,44 @@ from monggregate.utils import StrEnum +# Enums +#------------------------------------------- class AggregationVariableEnum(StrEnum): - """Enumeration of available aggregation variables""" - - NOW = "$$NOW" # Returns the current datetime value, - # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # (Available in 4.2+) - CLUSTER_TIME = "$$CLUSTER_TIME" # Returns the current timestamp value, which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # For replica sets and sharded clusters only. (Available in 4.2+) - ROOT = "$$ROOT" # References the root document, i.e. the top-level document. - CURRENT = "$$CURRENT" # References the start of the field path, which by default is ROOT but can be changed. - REMOVE = "$$REMOVE" # Allows for the conditional exclusion of fields. (Available in 3.6+) - DESCEND = "$$DESCEND" # One of the allowed results of a $redact expression. - PRUNE = "$$PRUNE" # One of the allowed results of a $redact expression. - KEEP = "$$KEEP" # One of the allowed results of a $redact expression.NOW = "$$NOW" # Returns the current datetime value, - # which is same across all members of the deployment and remains constant throughout the aggregation pipeline. - # (Available in 4.2+) + """ + Enumeration of available aggregation variables. + Members: + ------------------------------ + - NOW = "$$NOW" : Returns the current datetime value, which is same across all members of the deployment + (Available in 4.2+) + - CLUSTER_TIME = "$$CLUSTER_TIME" : Returns the current timestamp value, + which is same across all members of the deployment + and remains constant throughout the aggregation pipeline. + (Available in 4.2+) + - ROOT = "$$ROOT" : References the root document, i.e. the top-level document. + - CURRENT = "$$CURRENT" : References the start of the field path, which by default is ROOT but can be changed. + - REMOVE = "$$REMOVE" : Allows for the conditional exclusion of fields. (Available in 3.6+) + - DESCEND = "$$DESCEND" : One of the allowed results of a $redact expression. + - PRUNE = "$$PRUNE" : One of the allowed results of a $redact expression. + - KEEP = "$$KEEP" : One of the allowed results of a $redact expression.NOW = "$$NOW" : Returns the current datetime value, + which is same across all members of the deployment and remains constant throughout the aggregation pipeline. + (Available in 4.2+) + + + + """ + + NOW = "$$NOW" + CLUSTER_TIME = "$$CLUSTER_TIME" + ROOT = "$$ROOT" + CURRENT = "$$CURRENT" + REMOVE = "$$REMOVE" + DESCEND = "$$DESCEND" + PRUNE = "$$PRUNE" + KEEP = "$$KEEP" + +# Constants +#------------------------------------------- CLUSTER_TIME = AggregationVariableEnum.CLUSTER_TIME.value NOW = AggregationVariableEnum.NOW.value ROOT = AggregationVariableEnum.ROOT.value @@ -55,6 +76,10 @@ class AggregationVariableEnum(StrEnum): PRUNE = AggregationVariableEnum.PRUNE.value KEEP = AggregationVariableEnum.KEEP.value + +# Classes +#------------------------------------------- + # NOTE : If dollar is to be made to really store all of MongoDB functions i.e stages, operators and whathever they come up with # it might de interesting to create a DollarBase class, a DollarStage class and a DollarOperator class and to use inheritance class Dollar(Singleton): @@ -93,9 +118,9 @@ def __getattr__(self, name)->str|Any: return output - + #-------------------------------- # Accumulators - # -------------------------- + # ------------------------------- @classmethod def avg(cls, expression:Any)->accumulators.Avg: """Returns the $avg operator""" @@ -144,8 +169,9 @@ def sum(cls, expression:Any)->accumulators.Sum: return accumulators.sum(expression) + #-------------------------------- # Arithmetic - # -------------------------- + # ------------------------------- @classmethod def add(cls, *args:Any)->arithmetic.Add: """Returns the $add operator""" @@ -218,8 +244,9 @@ def pow(cls, *args:Any)->arithmetic.Pow: return arithmetic.pow(*args) + #-------------------------------- # Array - # -------------------------- + # ------------------------------- @classmethod def array_to_object(cls, expression:Any)->array.ArrayToObject: """Returns the $arrayToObject operator""" @@ -271,8 +298,9 @@ def sort_array(cls, expression:Any, sort_spec:dict[str, Literal[1,-1]])->array.S return array.sort_array(expression, sort_spec) + #-------------------------------- # Comparison - # -------------------------- + # ------------------------------- @classmethod def cmp(cls, left:Any, right:Any)->comparison.Cmp: """Returns the $cmp operator""" @@ -315,8 +343,9 @@ def ne(cls, left:Any, right:Any)->comparison.Ne: return comparison.ne(left, right) + #-------------------------------- # Conditional - # -------------------------- + # ------------------------------- @classmethod def cond(cls, if_:Any, then:Any, else_:Any)->conditional.Cond: """Returns the $cond operator""" @@ -335,8 +364,9 @@ def switch(cls, branches:dict[Any, Any], default:Any)->conditional.Switch: return conditional.switch(branches, default) + #-------------------------------- # Date - # -------------------------- + # ------------------------------- @classmethod def millisecond(cls, expression:Any, timezone:Any)->date.Millisecond: """Returns the $millisecond operator""" @@ -344,8 +374,9 @@ def millisecond(cls, expression:Any, timezone:Any)->date.Millisecond: return date.millisecond(expression, timezone) + #-------------------------------- # String - # -------------------------- + # ------------------------------- @classmethod def concat(cls, *args:Any)->strings.Concat: """Returns the $concat operator""" @@ -378,9 +409,9 @@ def date_to_string( return strings.date_to_string(expression, format, timezone, on_null) - + #-------------------------------- # Objects - # -------------------------- + # ------------------------------- @classmethod def merge_objects(cls, *args:Any)->objects.MergeObjects: """Returns the $mergeObjects operator""" @@ -393,8 +424,9 @@ def object_to_array(cls, expression:Any)->objects.ObjectToArray: return objects.object_to_array(expression) + #-------------------------------- # Boolean - # -------------------------- + # ------------------------------- @classmethod def and_(cls, *args:Any)->boolean.And: """Returns the $and operator""" @@ -413,14 +445,16 @@ def not_(cls, expression:Any)->boolean.Not: return boolean.not_(expression) + #-------------------------------- # Type - # -------------------------- + # ------------------------------- @classmethod def type_(cls, expression:Any)->type_.Type_: """Returns the $type operator""" return type_.type_(expression) - + + class DollarDollar(Singleton): """ MongoDB double dollar sign ($$) abstraction in python. diff --git a/monggregate/operators/accumulators/avg.py b/monggregate/operators/accumulators/avg.py index 301c559..6fb7747 100644 --- a/monggregate/operators/accumulators/avg.py +++ b/monggregate/operators/accumulators/avg.py @@ -95,7 +95,7 @@ def statement(self) -> dict: Avg = Average def average(expression:Any)->Average: - """Creates a push statement""" + """Returns a $avg operator""" return Average(expression=expression) diff --git a/monggregate/operators/accumulators/count.py b/monggregate/operators/accumulators/count.py index df7b371..ce246dc 100644 --- a/monggregate/operators/accumulators/count.py +++ b/monggregate/operators/accumulators/count.py @@ -63,7 +63,7 @@ def statement(self) -> dict: }) def count()->Count: - """Creates a $count statement""" + """Returns a $count operator""" return Count() diff --git a/monggregate/operators/accumulators/first.py b/monggregate/operators/accumulators/first.py index 64e1740..1de5af0 100644 --- a/monggregate/operators/accumulators/first.py +++ b/monggregate/operators/accumulators/first.py @@ -82,6 +82,6 @@ def statement(self) -> dict: }) def first(expression:Any)->First: - """Creates a push statement""" + """Returns a $first operator""" return First(expression=expression) diff --git a/monggregate/operators/accumulators/last.py b/monggregate/operators/accumulators/last.py index 901ee37..f9da983 100644 --- a/monggregate/operators/accumulators/last.py +++ b/monggregate/operators/accumulators/last.py @@ -76,6 +76,6 @@ def statement(self) -> dict: }) def last(expression:Any)->Last: - """Creates a push statement""" + """Returns a $last operator""" return Last(expression=expression) diff --git a/monggregate/operators/accumulators/max.py b/monggregate/operators/accumulators/max.py index 5f13a33..a49e79a 100644 --- a/monggregate/operators/accumulators/max.py +++ b/monggregate/operators/accumulators/max.py @@ -100,6 +100,6 @@ def statement(self) -> dict: }) def max(expression:Any)->Max: - """Creates a push statement""" + """Returns a $last operator""" return Max(expression=expression) diff --git a/monggregate/operators/accumulators/min.py b/monggregate/operators/accumulators/min.py index 753f6df..8bf1be7 100644 --- a/monggregate/operators/accumulators/min.py +++ b/monggregate/operators/accumulators/min.py @@ -100,6 +100,6 @@ def statement(self) -> dict: }) def min(expression:Any)->Min: - """Creates a $min statement""" + """Returns a $min operator""" return Min(expression=expression) diff --git a/monggregate/operators/accumulators/push.py b/monggregate/operators/accumulators/push.py index 8a30a8a..19d536c 100644 --- a/monggregate/operators/accumulators/push.py +++ b/monggregate/operators/accumulators/push.py @@ -53,6 +53,6 @@ def statement(self) -> dict: }) def push(expression:Any)->Push: - """Creates a push statement""" + """Returns a $push operator""" return Push(expression=expression) diff --git a/monggregate/operators/accumulators/sum.py b/monggregate/operators/accumulators/sum.py index a3e4b89..ac7c640 100644 --- a/monggregate/operators/accumulators/sum.py +++ b/monggregate/operators/accumulators/sum.py @@ -109,7 +109,7 @@ def statement(self) -> dict: }) def sum(*args:Any)->Sum: - """Creates a $sum statement""" + """Returns a $sum operator""" if len(args)>1: output = Sum(expression=list(args)) diff --git a/monggregate/operators/arithmetic/add.py b/monggregate/operators/arithmetic/add.py index a45350d..2816b6d 100644 --- a/monggregate/operators/arithmetic/add.py +++ b/monggregate/operators/arithmetic/add.py @@ -47,7 +47,7 @@ def statement(self) -> dict: }) def add(*args:Any)->Add: - """Returns an $add statement""" + """Returns a $add operator""" return Add( expressions=list(args) diff --git a/monggregate/operators/arithmetic/divide.py b/monggregate/operators/arithmetic/divide.py index ad39be8..dbaf7ae 100644 --- a/monggregate/operators/arithmetic/divide.py +++ b/monggregate/operators/arithmetic/divide.py @@ -48,7 +48,7 @@ def statement(self) -> dict: }) def divide(numerator:Any, denominator:Any)->Divide: - """Returns a $divide statement""" + """Returns a $divide operator""" return Divide( numerator=numerator, diff --git a/monggregate/operators/arithmetic/multiply.py b/monggregate/operators/arithmetic/multiply.py index c3dffc1..19e611b 100644 --- a/monggregate/operators/arithmetic/multiply.py +++ b/monggregate/operators/arithmetic/multiply.py @@ -45,7 +45,7 @@ def statement(self) -> dict: }) def multiply(*args:Any)->Multiply: - """Returns an $multiply statement""" + """Returns a $multiply operator""" return Multiply( expressions=list(args) diff --git a/monggregate/operators/arithmetic/pow.py b/monggregate/operators/arithmetic/pow.py index 7a1f409..15139b9 100644 --- a/monggregate/operators/arithmetic/pow.py +++ b/monggregate/operators/arithmetic/pow.py @@ -65,7 +65,7 @@ def statement(self) -> dict: }) def pow(number:Any, exponent:Any)->Pow: - """Returns an $pow statement""" + """Returns a $pow operator""" return Pow( number=number, diff --git a/monggregate/operators/arithmetic/subtract.py b/monggregate/operators/arithmetic/subtract.py index 1612476..03ad76b 100644 --- a/monggregate/operators/arithmetic/subtract.py +++ b/monggregate/operators/arithmetic/subtract.py @@ -62,7 +62,7 @@ def statement(self) -> dict: }) def subtract(left:Any, right:Any)->Subtract: - """Returns an $substract statement""" + """Returns a $substract operator""" return Subtract( left=left, diff --git a/monggregate/operators/array/array_to_object.py b/monggregate/operators/array/array_to_object.py index cc18f4a..9eb9391 100644 --- a/monggregate/operators/array/array_to_object.py +++ b/monggregate/operators/array/array_to_object.py @@ -73,6 +73,6 @@ def statement(self) -> dict: }) def array_to_object(expression:Any)->ArrayToObject: - """Returns an $arrayToObject statement""" + """Returns a $arrayToObject operator""" return ArrayToObject(expression=expression) diff --git a/monggregate/operators/array/filter.py b/monggregate/operators/array/filter.py index 3f0029d..d2817f0 100644 --- a/monggregate/operators/array/filter.py +++ b/monggregate/operators/array/filter.py @@ -85,7 +85,7 @@ def statement(self) -> dict: }) def filter(expression:Any, let:str, query:Any, limit:int|None=None)->Filter: - """Returns a $filter statement""" + """Returns a $filter operator""" return Filter( expression = expression, diff --git a/monggregate/operators/array/first.py b/monggregate/operators/array/first.py index cb1fefe..735626a 100644 --- a/monggregate/operators/array/first.py +++ b/monggregate/operators/array/first.py @@ -117,7 +117,7 @@ def statement(self) -> dict: }) def first(array:Any)->First: - """Returns a $first statement""" + """Returns a $first operator""" return First( expression = array diff --git a/monggregate/operators/array/in_.py b/monggregate/operators/array/in_.py index 8ed48fe..ca42aa6 100644 --- a/monggregate/operators/array/in_.py +++ b/monggregate/operators/array/in_.py @@ -55,7 +55,7 @@ def statement(self) -> dict: }) def in_(left:Any, right:Any)->In: - """Returns a $maxN statement""" + """Returns a $maxN operator""" return In( left = left, diff --git a/monggregate/operators/array/is_array.py b/monggregate/operators/array/is_array.py index 76012f5..4b49cba 100644 --- a/monggregate/operators/array/is_array.py +++ b/monggregate/operators/array/is_array.py @@ -45,7 +45,7 @@ def statement(self) -> dict: }) def is_array(array:Any)->IsArray: - """Returns a $isArray statement""" + """Returns a $isArray operator""" return IsArray( expression = array diff --git a/monggregate/operators/array/last.py b/monggregate/operators/array/last.py index b05666f..3e8bcea 100644 --- a/monggregate/operators/array/last.py +++ b/monggregate/operators/array/last.py @@ -121,7 +121,7 @@ def statement(self) -> dict: }) def last(array:Any)->Last: - """Returns a $last statement""" + """Returns a $last operator""" return Last( expression = array diff --git a/monggregate/operators/array/max_n.py b/monggregate/operators/array/max_n.py index f27a993..b9a266e 100644 --- a/monggregate/operators/array/max_n.py +++ b/monggregate/operators/array/max_n.py @@ -73,7 +73,7 @@ def statement(self) -> dict: }) def max_n(expression:Any, limit:Any=1)->MaxN: - """Returns a $maxN statement""" + """Returns a $maxN operator""" return MaxN( expression = expression, diff --git a/monggregate/operators/array/min_n.py b/monggregate/operators/array/min_n.py index e392bf3..a16ed05 100644 --- a/monggregate/operators/array/min_n.py +++ b/monggregate/operators/array/min_n.py @@ -73,7 +73,7 @@ def statement(self) -> dict: }) def min_n(expression:Any, limit:Any=1)->MinN: - """Returns a $minN statement""" + """Returns a $minN operator""" return MinN( expression = expression, diff --git a/monggregate/operators/array/size.py b/monggregate/operators/array/size.py index 64b4dbe..380b618 100644 --- a/monggregate/operators/array/size.py +++ b/monggregate/operators/array/size.py @@ -47,7 +47,7 @@ def statement(self) -> dict: }) def size(array:Any)->Size: - """Returns a $size statement""" + """Returns a $size operator""" return Size( expression = array diff --git a/monggregate/operators/array/sort_array.py b/monggregate/operators/array/sort_array.py index dd325a9..837b712 100644 --- a/monggregate/operators/array/sort_array.py +++ b/monggregate/operators/array/sort_array.py @@ -109,7 +109,7 @@ def statement(self) -> dict: }) def sort_array(expression:Any, sort_by:dict[str, Literal[1, -1]])->SortArray: - """Returns a $first statement""" + """Returns a $first operator""" return SortArray( expression = expression, diff --git a/monggregate/operators/boolean/and_.py b/monggregate/operators/boolean/and_.py index c92be5e..0a1dfbb 100644 --- a/monggregate/operators/boolean/and_.py +++ b/monggregate/operators/boolean/and_.py @@ -73,7 +73,7 @@ def statement(self) -> dict: }) def and_(*args:Any)->And: - """Returns an $and statement""" + """Returns a $and operator""" return And( expressions=list(args) diff --git a/monggregate/operators/boolean/not_.py b/monggregate/operators/boolean/not_.py index f4b8d6a..6546f6c 100644 --- a/monggregate/operators/boolean/not_.py +++ b/monggregate/operators/boolean/not_.py @@ -48,7 +48,7 @@ def statement(self) -> dict: }) def not_(expression:Any)->Not: - """Returns an $not statement""" + """Returns a $not operator""" return Not( expression=expression diff --git a/monggregate/operators/boolean/or_.py b/monggregate/operators/boolean/or_.py index 5e49b8b..aa4a70b 100644 --- a/monggregate/operators/boolean/or_.py +++ b/monggregate/operators/boolean/or_.py @@ -49,7 +49,7 @@ def statement(self) -> dict: }) def or_(*args:Any)->Or: - """Returns an $or statement""" + """Returns a $or operator""" return Or( expressions=list(args) diff --git a/monggregate/operators/comparison/eq.py b/monggregate/operators/comparison/eq.py index 21d5cc6..fa95c72 100644 --- a/monggregate/operators/comparison/eq.py +++ b/monggregate/operators/comparison/eq.py @@ -50,7 +50,7 @@ def statement(self) -> dict: Eq = Equal def equal(left:Any, right:Any)->Equal: - """Creates an $eq statement""" + """Creates an $eq operator""" return Equal( left=left, diff --git a/monggregate/operators/comparison/gt.py b/monggregate/operators/comparison/gt.py index 05d31e0..516848c 100644 --- a/monggregate/operators/comparison/gt.py +++ b/monggregate/operators/comparison/gt.py @@ -48,7 +48,7 @@ def statement(self) -> dict: Gt = GreatherThan def greather_than(left:Any, right:Any)->GreatherThan: - """Returns a $gt statement""" + """Returns a $gt operator""" return GreatherThan( left = left, diff --git a/monggregate/operators/comparison/gte.py b/monggregate/operators/comparison/gte.py index 91bb5f7..c727e51 100644 --- a/monggregate/operators/comparison/gte.py +++ b/monggregate/operators/comparison/gte.py @@ -46,7 +46,7 @@ def statement(self) -> dict: Gte = GreatherThanOrEqual def grether_than_or_equal(left:Any, right:Any)->GreatherThanOrEqual: - """Returns a $gte statement""" + """Returns a $gte operator""" return GreatherThanOrEqual( left=left, diff --git a/monggregate/operators/comparison/lt.py b/monggregate/operators/comparison/lt.py index d0c1d28..2e9e60c 100644 --- a/monggregate/operators/comparison/lt.py +++ b/monggregate/operators/comparison/lt.py @@ -45,7 +45,7 @@ def statement(self) -> dict: Lt = LowerThan def lower_than(left:Any, right:Any)->LowerThan: - """Returns a $lt statement""" + """Returns a $lt operator""" return LowerThan( left=left, diff --git a/monggregate/operators/comparison/lte.py b/monggregate/operators/comparison/lte.py index 0b293ef..477aea2 100644 --- a/monggregate/operators/comparison/lte.py +++ b/monggregate/operators/comparison/lte.py @@ -46,7 +46,7 @@ def statement(self) -> dict: Lte = LowerThanOrEqual def lower_than_or_equal(left:Any, right:Any)->LowerThanOrEqual: - """Returns a $lt statement""" + """Returns a $lt operator""" return LowerThanOrEqual( left=left, diff --git a/monggregate/operators/comparison/ne.py b/monggregate/operators/comparison/ne.py index ece5ce0..310d627 100644 --- a/monggregate/operators/comparison/ne.py +++ b/monggregate/operators/comparison/ne.py @@ -49,7 +49,7 @@ def statement(self) -> dict: Ne = NotEqual def not_equal(left:Any, right:Any)->NotEqual: - """Returns a $ne statement""" + """Returns a $ne operator""" return NotEqual( left=left, diff --git a/monggregate/operators/conditional/cond.py b/monggregate/operators/conditional/cond.py index b17620c..d72d1ed 100644 --- a/monggregate/operators/conditional/cond.py +++ b/monggregate/operators/conditional/cond.py @@ -126,7 +126,7 @@ def statement(self) -> dict: }) def cond(*args:Any, **kwargs:Any)->Cond: - """Returns an $cond statement""" + """Returns an $cond operator""" if_ = args[0] if args else (kwargs.get("if_") or kwargs.get("expression")) then_ = args[1] if len(args) > 1 else (kwargs.get("then_") or kwargs.get("true_")) diff --git a/monggregate/operators/conditional/if_null.py b/monggregate/operators/conditional/if_null.py index ed014e8..93c775a 100644 --- a/monggregate/operators/conditional/if_null.py +++ b/monggregate/operators/conditional/if_null.py @@ -69,7 +69,7 @@ def statement(self) -> dict: }) def if_null(expression:Any, output:Any)->IfNull: - """Returns an $if_null statement""" + """Returns an $if_null operator""" return IfNull( expression=expression, diff --git a/monggregate/operators/conditional/switch.py b/monggregate/operators/conditional/switch.py index 9285292..fdb4c47 100644 --- a/monggregate/operators/conditional/switch.py +++ b/monggregate/operators/conditional/switch.py @@ -86,7 +86,7 @@ def statement(self) -> dict: }) def switch(branches:list[Any], default:Any)->Switch: - """Returns an $switch statement""" + """Returns an $switch operator""" return Switch( branches=branches, diff --git a/monggregate/operators/date/millisecond.py b/monggregate/operators/date/millisecond.py index 0d29264..a1ea5d2 100644 --- a/monggregate/operators/date/millisecond.py +++ b/monggregate/operators/date/millisecond.py @@ -38,7 +38,7 @@ def statement(self) -> dict: }) def millisecond(expression:Any, timezone:Any)->Millisecond: - """Returns an $millisecond statement""" + """Returns an $millisecond operator""" return Millisecond( expression=expression, diff --git a/monggregate/operators/objects/merge_objects.py b/monggregate/operators/objects/merge_objects.py index 356db8b..e09d3bb 100644 --- a/monggregate/operators/objects/merge_objects.py +++ b/monggregate/operators/objects/merge_objects.py @@ -64,6 +64,6 @@ def statement(self) -> dict: }) def merge_objects(expression:Any)->MergeObjects: - """Returns a merge_objects statement""" + """Returns a $mergeObjects operator""" return MergeObjects(expression=expression) diff --git a/monggregate/operators/objects/object_to_array.py b/monggregate/operators/objects/object_to_array.py index 874b55a..9d4424c 100644 --- a/monggregate/operators/objects/object_to_array.py +++ b/monggregate/operators/objects/object_to_array.py @@ -49,6 +49,6 @@ def statement(self) -> dict: }) def object_to_array(expression:Any)->ObjectToArray: - """Returns a *objectToArray statement""" + """Returns a $objectToArray operator""" return ObjectToArray(expression=expression) diff --git a/monggregate/operators/strings/concat.py b/monggregate/operators/strings/concat.py index ef91a39..7e4b707 100644 --- a/monggregate/operators/strings/concat.py +++ b/monggregate/operators/strings/concat.py @@ -45,7 +45,7 @@ def statement(self) -> dict: }) def concat(*args:Any)->Concat: - """Returns an $concat statement""" + """Returns an $concat operator""" return Concat( expressions=list(args) diff --git a/monggregate/operators/strings/date_from_string.py b/monggregate/operators/strings/date_from_string.py index 7b8d9f9..8464abb 100644 --- a/monggregate/operators/strings/date_from_string.py +++ b/monggregate/operators/strings/date_from_string.py @@ -114,7 +114,7 @@ def date_from_string( on_error:Any=None, on_null:Any=None )->DateFromString: - """Returns an $dateFromString statement""" + """Returns an $dateFromString operator""" return DateFromString( date_string=date_string, diff --git a/monggregate/operators/strings/date_to_string.py b/monggregate/operators/strings/date_to_string.py index f8bd08c..cc5ae35 100644 --- a/monggregate/operators/strings/date_to_string.py +++ b/monggregate/operators/strings/date_to_string.py @@ -111,7 +111,7 @@ def date_to_string( timezone:Any=None, on_null:Any=None )->DateToString: - """Returns an $dateToString statement""" + """Returns an $dateToString operator""" return DateToString( date=date, diff --git a/monggregate/operators/type_/type_.py b/monggregate/operators/type_/type_.py index 36b6c8c..ac787b9 100644 --- a/monggregate/operators/type_/type_.py +++ b/monggregate/operators/type_/type_.py @@ -110,7 +110,7 @@ def statement(self)->dict: def type_(expression:Any)->Type_: - """xxxx""" + """Returns a $type operator""" return Type_( expression=expression From c4c962155f8da3a4c195b03cd2e58fec650191b1 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 29 Aug 2023 22:41:09 +0200 Subject: [PATCH 24/26] Added missing operators to expressions --- monggregate/expressions.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/monggregate/expressions.py b/monggregate/expressions.py index 6aeb66c..c4ff884 100644 --- a/monggregate/expressions.py +++ b/monggregate/expressions.py @@ -292,6 +292,13 @@ def min_n(self, limit:int=1)->Self: 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: @@ -490,4 +497,9 @@ def type_(self)->Self: return self.__class__(content=type_.Type_(expression=self)) - \ No newline at end of file + + +if __name__ == "__main__": + #result = Expression.field("left") & Expression.field("right") + result = Expression.field("related_comments").size() + print(result()) \ No newline at end of file From 6c49cce69ce25489574acc2cd340be6c6a8ece6b Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 29 Aug 2023 22:41:29 +0200 Subject: [PATCH 25/26] Promote Expressions to public API --- monggregate/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monggregate/__init__.py b/monggregate/__init__.py index 6a5d4b4..e275448 100644 --- a/monggregate/__init__.py +++ b/monggregate/__init__.py @@ -1,7 +1,9 @@ """App Package""" -from monggregate.pipeline import Pipeline +from monggregate.expressions import Expression from monggregate.dollar import S, SS +from monggregate.pipeline import Pipeline + __version__ = "0.15.0" __author__ = "Vianney Mixtur" From 467f263c077e42030eb5b1a12c9c1ea5e0130ab2 Mon Sep 17 00:00:00 2001 From: Vianney Mixtur Date: Tue, 29 Aug 2023 22:42:22 +0200 Subject: [PATCH 26/26] Updated README and changelog --- changelog.md | 6 +++++ readme.md | 76 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/changelog.md b/changelog.md index adf7d85..ea29a41 100644 --- a/changelog.md +++ b/changelog.md @@ -8,9 +8,12 @@ * Created S object (represents $ sign since it is not a valid variable name in python) to store all MongoDB operators and to create references to fields * Created SS object (represents $$) to store aggregation variables and referenes to user variables * Interfaced a chunk of new operators(add, divide, multiply, pow, substract, cond, if_null, switch, millisecond, date_from_string, date_to_string, type_) +* Integrated new operators in Expressions class + ### Refactoring +* Redefined Expressions completely. Simplified and clarified how they can be used when using this package. * Removed index module that was at the root of the package (monggregate.index.py -> ø) * Removed expressions subpackage (monggregate.expression -> ø) * Moved expressions fields module to the root of the package (monggregate.expressions.fields.py -> monggregate.fields.py) @@ -25,6 +28,9 @@ To do so, some arguments might need to be renamed in the operators * Expressions subpackage has been exploded and some parts have been deleted +### Documentation + +* Updated readme to reflect changes in the packge. Readme now focuses on the recommended way to use the package and clarifies how to use MongoDB operators. ## 0.15.0 diff --git a/readme.md b/readme.md index 0a708d1..3bc13ba 100644 --- a/readme.md +++ b/readme.md @@ -18,32 +18,24 @@ This package requires python > 3.10, pydantic > 1.8.0 ## Installation -### PIP - The repo is now available on PyPI: ```shell pip install monggregate ``` -### Manually - -1. Download the repo from https://github.com/VianneyMI/mongreggate -2. Copy the repo to your project -3. Navigate to the folder containing the downloaded repo -4. Install the repo locally by executing the following command: ` python -m pip install -e .` ## Usage The below examples reference the MongoDB sample_mflix database -### ... through the pipeline interface +### Basic Pipeline usage ```python from dotenv import load_dotenv import pymongo -from monggregate.pipeline import Pipeline +from monggregate import Pipeline, S # Load config from a .env file: load_dotenv(verbose=True) @@ -58,6 +50,7 @@ db = client["sample_mflix"] # Creating the pipeline pipeline = Pipeline() +# The below pipeline will return the most recent movie with the title "A Star is Born" pipeline.match( title="A Star is Born" ).sort( @@ -67,18 +60,21 @@ pipeline.match( ) # Executing the pipeline -db["movies"].aggregate(pipeline.export()) +results = db["movies"].aggregate(pipeline.export()) + +print(results) ``` -### ... through the stage classes +### More advanced usage, with MongoDB operators + ```python from dotenv import load_dotenv import pymongo -from monggregate.stages import Match, Limit Sort +from monggregate import Pipeline, S # Load config from a .env file: load_dotenv(verbose=True) @@ -90,34 +86,49 @@ client = pymongo.MongoClient(MONGODB_URI) # Get a reference to the "sample_mflix" database: db = client["sample_mflix"] -# Get a reference to the "movies" collection: -movie_collection = db["movies"] # Creating the pipeline -filter_on_title = Match( - query = { - "title" : "A Star is Born" - } -) -sorting_per_year = Sort( +pipeline = Pipeline() +pipeline.match( + year=S.type_("number") # Filtering out documents where the year field is not a number +).group( + by="year", query = { - "year":1 + "movie_count":S.sum(1), # Aggregating the movies per year + "movie_titles":S.push("$title") } -) +).sort( + by="_id", + descending=True +).limit(10) -limiting_to_most_recent = Limit( - value=1 -) +# Executing the pipeline +results = db["movies"].aggregate(pipeline.export()) + +print(results) -pipeline = [filter_on_title, sorting_per_year, limiting_to_most_recent] -pipeline = [stage.statment for stage in pipeline] +``` -# Lauching the pipeline +### Advanced usage with Expressions -results = move_collection.aggregate(pipeline) +```python -``` +from monggregate import Pipeline, S, Expression +pipeline = Pipeline() +pipeline.lookup( + right="comments", + right_on="_id", + left_on="movie_id", + name="comments +).add_fields( + comment_count=Expression.field("related_comments").size() +).match( + comment_count=S.gte(2) +) + + +``` ## Motivation @@ -135,7 +146,8 @@ Basically, the package mirrors every* stage and operator available on MongoDB. ## Roadmap -As of now, the package covers 50% of the available stages and 20% of the available operators. +As of now, the package covers around 40% of the available stages and 25% of the available operators. +I would argue, that the most important stages and operators are probably covered but this is subjective. The goal is to quickly reach 100% of both stages and operators. The source code integrates most of the online MongoDB documentation. If the online documentation evolves, it will need to be updated here as well. The current documentation is not consistent throughout the package it will need to be standardized later on.