Skip to content

Commit

Permalink
Merge pull request #76 from VianneyMI/75-cannot-group-with-more-than-…
Browse files Browse the repository at this point in the history
…one-field-name

 75-cannot-group-with-more-than-one-field-name
  • Loading branch information
VianneyMI committed Sep 17, 2023
2 parents b222bf8 + 90853f3 commit bc3582a
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 8 deletions.
14 changes: 13 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# Release Notes

## 0.16.0
## 0.16.2

### Fixes

* Allow to use iterable and dicts to group by in Group class and pipeline group function

## 0.16.1

### Fixes

* Fixed replace_root by passing document argument to ReplaceRoot class


## 0.16.0

### New Features

Expand Down
2 changes: 1 addition & 1 deletion monggregate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from monggregate.pipeline import Pipeline


__version__ = "0.16.1"
__version__ = "0.16.2"
__author__ = "Vianney Mixtur"
__contact__ = "prenom.nom@outlook.fr"
__copyright__ = "Copyright © 2022 Vianney Mixtur"
Expand Down
2 changes: 1 addition & 1 deletion monggregate/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def group(self, *, by:Any|None=None, query:dict={})->"Pipeline":
Arguments:
------------------------
- by / _id (offcial MongoDB name represented by a pydantic alias), str | list[str] | set[str] : field or group of fields to group on
- by, str | list[str] | set[str] | dict | None : field or group of fields to group by
- query, dict | None : Computed aggregated values (per group)
"""
Expand Down
5 changes: 3 additions & 2 deletions monggregate/stages/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@
from typing import Any
from monggregate.base import pyd
from monggregate.stages.stage import Stage
from monggregate.utils import validate_field_path
from monggregate.utils import validate_field_path, validate_field_paths

class Group(Stage):
"""
Creates a group statement for an aggregation pipeline group stage.
Attributes:
------------------------
- by / _id (offcial MongoDB name represented by a pydantic alias), str | list[str] | set[str] : field or group of fields to group on
- by, str | list[str] | set[str] | dict | None : field or group of fields to group by
- query, dict | None : Computed aggregated values (per group)
Expand All @@ -91,6 +91,7 @@ class Group(Stage):
# Validators
# ------------------------------------------
_validate_by = pyd.validator("by", pre=True, always=True, allow_reuse=True)(validate_field_path) # re-used pyd.validator
_validate_iterable_by = pyd.validator("by", pre=True, always=True, allow_reuse=True)(validate_field_paths) # re-used pyd.validator

@pyd.validator("query", always=True)
@classmethod
Expand Down
14 changes: 13 additions & 1 deletion monggregate/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,19 @@ def to_unique_list(keys:T)->list[str]|T:
def validate_field_path(path:str|None)->str|None:
"""Validates field path"""

if path and not path.startswith("$"):
if isinstance(path, str) and not path.startswith("$"):
path = "$" + path

return path


def validate_field_paths(paths:list[str]|set[str])->list[str]:
"""Validates field paths"""

if isinstance(paths, list):
paths = [validate_field_path(path) for path in paths]
elif isinstance(paths, set):
paths = [validate_field_path(path) for path in paths]
paths.sort()

return paths
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "monggregate"
version = "0.16.1"
version = "0.16.2"
description = "MongoDB aggregation pipelines made easy. Joins, grouping, counting and much more..."
readme = "README.md"
authors = [{ name = "Vianney Mixtur", email = "vianney.mixtur@outlook.fr" }]
Expand Down Expand Up @@ -35,7 +35,7 @@ dev = ["bumpver", "pytest", "mypy", "pylint"]
Homepage = "https://github.com/VianneyMI/monggregate"

[tool.bumpver]
current_version = "0.16.1"
current_version = "0.16.2"
version_pattern = "MAJOR.MINOR.PATCH"
commit_message = "bump version {old_version} -> {new_version}"
commit = true
Expand Down
Binary file modified requirements.txt
Binary file not shown.
115 changes: 115 additions & 0 deletions test/test_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Module to test group (temp)"""

#from dictdiffer import diff
from monggregate import Pipeline
from monggregate.stages.group import Group


def test_group_stage():
"""Test group stage"""

# Test by as list
# ------------------------------
assert Group(
by=["name", "age"],
query = {
"output":{"$sum":"income"}
}
).statement == {
"$group":{
"_id":["$name", "$age"],
"output":{"$sum":"income"}
}
}

# Test by as set
# ------------------------------
group = Group(
by={"name", "age"},
query = {
"output":{"$sum":"income"}
}
)

expected_statement = {
"$group":{
"_id":["$age", "$name"],
"output":{"$sum":"income"}
}
}
assert group.statement == expected_statement
#, list(diff(expected_statement, group.statement))


# Test by as dict
# ------------------------------
assert Group(
by={"name":"$name", "age":"$age"},
query = {
"output":{"$sum":"income"}
}
).statement == {
"$group":{
"_id":{"name":"$name", "age":"$age"},
"output":{"$sum":"income"}
}
}

def test_group_in_pipeline():

# Reproduce tests above with pipeline
# -----------------------------------
#
# Test by as list
# ------------------------------

assert Pipeline().group(
by=["name", "age"],
query = {
"output":{"$sum":"income"}
}
).statement == [
{
"$group":{
"_id":["$name", "$age"],
"output":{"$sum":"income"}
}
}
]

# Test by as set
# ------------------------------
assert Pipeline().group(
by={"name", "age"},
query = {
"output":{"$sum":"income"}
}
).statement == [
{
"$group":{
"_id":["$age", "$name"],
"output":{"$sum":"income"}
}
}
]

# Test by as dict
# ------------------------------
assert Pipeline().group(
by={"name":"$name", "age":"$age"},
query = {
"output":{"$sum":"income"}
}
).statement == [
{
"$group":{
"_id":{"name":"$name", "age":"$age"},
"output":{"$sum":"income"}
}
}
]

if __name__ == "__main__":
test_group_stage()
test_group_in_pipeline()
print("All tests passed")
47 changes: 47 additions & 0 deletions test/test_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def test_count(self, state:State)->None:
assert count
state["count"] = count


def test_group(self, state:State)->None:
"""Tests the group stage"""

Expand All @@ -141,6 +142,52 @@ def test_group(self, state:State)->None:
}
)

# Test by as list
# ------------------------
assert Group(
by=["name", "age"],
query = {
"output":{"$sum":"income"}
}
)

# Test by as set
# ------------------------
assert Group(
by=set(["name", "age"]),
query = {
"output":{"$sum":"income"}
}
)

# Test by as constant
# ------------------------
assert Group(
by=1,
query = {
"output":{"$sum":"income"}
}
)

# Test by as dict
# ------------------------
assert Group(
by={"name":"$name"},
query = {
"output":{"$sum":"income"}
}
)

# Test by as None
# ------------------------
assert Group(
by=None,
query = {
"output":{"$sum":"income"}
}
)



def test_limit(self, state:State)->None:
"""Tests the limit stage"""
Expand Down

0 comments on commit bc3582a

Please sign in to comment.