From 702b560b0177b5d37e9242ede098f0355b8a679a Mon Sep 17 00:00:00 2001 From: Roman <79310030+roman-right@users.noreply.github.com> Date: Tue, 18 May 2021 15:44:51 +0200 Subject: [PATCH] rework tutorial (#39) --- README.md | 21 ++-- beanie/__init__.py | 2 +- beanie/odm/documents.py | 6 +- docs/changelog.md | 7 +- docs/index.md | 21 ++-- docs/tutorial/aggregate.md | 65 ++++--------- docs/tutorial/delete.md | 2 + docs/tutorial/document.md | 134 ++++++++++++++++++++++++++ docs/tutorial/find.md | 2 +- docs/tutorial/indexes.md | 19 ++-- docs/tutorial/init.md | 26 +++++ docs/tutorial/insert.md | 2 + docs/tutorial/install.md | 83 ---------------- mkdocs.yml | 15 ++- pyproject.toml | 2 +- tests/odm/documents/test_aggregate.py | 2 +- tests/test_beanie.py | 2 +- 17 files changed, 238 insertions(+), 173 deletions(-) create mode 100644 docs/tutorial/document.md create mode 100644 docs/tutorial/init.md delete mode 100644 docs/tutorial/install.md diff --git a/README.md b/README.md index 697c54ff..aa65ea2e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ class Product(Document): # This is the model ``` More details about Documents, collections, and indexes configuration could be -found in the [tutorial](https://roman-right.github.io/beanie/tutorial/install/). +found in the [documentation](https://roman-right.github.io/beanie/document/). ### Initialization @@ -109,7 +109,7 @@ peanut_bar = Product(name="Peanut Bar", price=4.44, category=chocolate) await Product.insert_many([milka, peanut_bar]) ``` -Other details and examples could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/insert/) +Other details and examples could be found in the [documentation](https://roman-right.github.io/beanie/insert/) ### Find @@ -238,7 +238,7 @@ all_products = await Product.all().to_list() ``` Information about sorting, skips, limits, and projections could be found in -the [tutorial](https://roman-right.github.io/beanie/tutorial/find/) +the [documentation](https://roman-right.github.io/beanie/find/) ### Update @@ -324,7 +324,7 @@ await Product.find( ``` More details and examples about update queries could be found in -the [tutorial](https://roman-right.github.io/beanie/tutorial/update/) +the [documentation](https://roman-right.github.io/beanie/update/) ### Delete @@ -359,7 +359,7 @@ await Product.find( await Product.delete_all() ``` -More information could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/delete/) +More information could be found in the [documentation](https://roman-right.github.io/beanie/delete/) ### Aggregate @@ -369,14 +369,16 @@ You can aggregate and over the whole collection, using `aggregate()` method of t `FindMany` and `Document` classes implements [AggregateMethods](https://roman-right.github.io/beanie/api/interfaces/#aggregatemethods) interface with preset methods -**With search criteria** +Example of average calculation: + +*With search criteria* ```python avg_price = await Product.find( Product.category.name == "Chocolate" ).avg(Product.price) ``` -**Over the whole collection** +*Over the whole collection* ```python avg_price = await Product.avg(Product.price) ``` @@ -399,12 +401,9 @@ result = await Product.find( ``` -Information about aggregation preset aggregation methods and native syntax -aggregations could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/aggregate/) - ### Documentation -- **[Tutorial](https://roman-right.github.io/beanie/tutorial/install/)** - +- **[Doc](https://roman-right.github.io/beanie/)** - Usage examples with descriptions - **[API](https://roman-right.github.io/beanie/api/document/)** - Full list of the classes and methods diff --git a/beanie/__init__.py b/beanie/__init__.py index b7af4009..1016addf 100644 --- a/beanie/__init__.py +++ b/beanie/__init__.py @@ -4,7 +4,7 @@ from beanie.odm.utils.general import init_beanie from beanie.odm.documents import Document -__version__ = "1.0.3" +__version__ = "1.0.4" __all__ = [ # ODM "Document", diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index 23441403..9152d298 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -379,20 +379,20 @@ async def delete_all( def aggregate( cls, aggregation_pipeline: list, - aggregation_model: Type[BaseModel] = None, + projection_model: Type[BaseModel] = None, session: Optional[ClientSession] = None, ) -> AggregationQuery: """ Aggregate over collection. Returns [AggregationQuery](https://roman-right.github.io/beanie/api/queries/#aggregationquery) query object :param aggregation_pipeline: list - aggregation pipeline - :param aggregation_model: Type[BaseModel] + :param projection_model: Type[BaseModel] :param session: Optional[ClientSession] :return: [AggregationQuery](https://roman-right.github.io/beanie/api/queries/#aggregationquery) """ return cls.find_all().aggregate( aggregation_pipeline=aggregation_pipeline, - projection_model=aggregation_model, + projection_model=projection_model, session=session, ) diff --git a/docs/changelog.md b/docs/changelog.md index a213e9d0..7ba6f6a4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,10 @@ # Changelog Beanie project changes +## [1.0.4] - 2021-05-18 +### Fixed +- `aggregation_model` -> `projection_model` parameter. [PR](https://github.com/roman-right/beanie/pull/39) + ## [1.0.3] - 2021-05-16 ### Added - Index kwargs in the Indexed field. [PR](https://github.com/roman-right/beanie/pull/32). @@ -95,4 +99,5 @@ Beanie project changes [1.0.0]: https://pypi.org/project/beanie/1.0.0 [1.0.1]: https://pypi.org/project/beanie/1.0.1 [1.0.2]: https://pypi.org/project/beanie/1.0.2 -[1.0.3]: https://pypi.org/project/beanie/1.0.3 \ No newline at end of file +[1.0.3]: https://pypi.org/project/beanie/1.0.3 +[1.0.4]: https://pypi.org/project/beanie/1.0.4 \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 697c54ff..aa65ea2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -65,7 +65,7 @@ class Product(Document): # This is the model ``` More details about Documents, collections, and indexes configuration could be -found in the [tutorial](https://roman-right.github.io/beanie/tutorial/install/). +found in the [documentation](https://roman-right.github.io/beanie/document/). ### Initialization @@ -109,7 +109,7 @@ peanut_bar = Product(name="Peanut Bar", price=4.44, category=chocolate) await Product.insert_many([milka, peanut_bar]) ``` -Other details and examples could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/insert/) +Other details and examples could be found in the [documentation](https://roman-right.github.io/beanie/insert/) ### Find @@ -238,7 +238,7 @@ all_products = await Product.all().to_list() ``` Information about sorting, skips, limits, and projections could be found in -the [tutorial](https://roman-right.github.io/beanie/tutorial/find/) +the [documentation](https://roman-right.github.io/beanie/find/) ### Update @@ -324,7 +324,7 @@ await Product.find( ``` More details and examples about update queries could be found in -the [tutorial](https://roman-right.github.io/beanie/tutorial/update/) +the [documentation](https://roman-right.github.io/beanie/update/) ### Delete @@ -359,7 +359,7 @@ await Product.find( await Product.delete_all() ``` -More information could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/delete/) +More information could be found in the [documentation](https://roman-right.github.io/beanie/delete/) ### Aggregate @@ -369,14 +369,16 @@ You can aggregate and over the whole collection, using `aggregate()` method of t `FindMany` and `Document` classes implements [AggregateMethods](https://roman-right.github.io/beanie/api/interfaces/#aggregatemethods) interface with preset methods -**With search criteria** +Example of average calculation: + +*With search criteria* ```python avg_price = await Product.find( Product.category.name == "Chocolate" ).avg(Product.price) ``` -**Over the whole collection** +*Over the whole collection* ```python avg_price = await Product.avg(Product.price) ``` @@ -399,12 +401,9 @@ result = await Product.find( ``` -Information about aggregation preset aggregation methods and native syntax -aggregations could be found in the [tutorial](https://roman-right.github.io/beanie/tutorial/aggregate/) - ### Documentation -- **[Tutorial](https://roman-right.github.io/beanie/tutorial/install/)** - +- **[Doc](https://roman-right.github.io/beanie/)** - Usage examples with descriptions - **[API](https://roman-right.github.io/beanie/api/document/)** - Full list of the classes and methods diff --git a/docs/tutorial/aggregate.md b/docs/tutorial/aggregate.md index 50290d90..f5747e3d 100644 --- a/docs/tutorial/aggregate.md +++ b/docs/tutorial/aggregate.md @@ -1,33 +1,28 @@ # Aggregations -[AggregationQuery](https://roman-right.github.io/beanie/api/queries/#aggregationquery) is used to aggregate data -over the whole collection or the subset selected with -the [FindMany](https://roman-right.github.io/beanie/api/queries/#findmany) query. +You can aggregate and over the whole collection, using `aggregate()` method of the `Document` class, and over search criteria, using `FindMany` instance. -## Preset aggregations +#### Aggregation Methods -[AggregateMethods](https://roman-right.github.io/beanie/api/interfaces/#aggregatemethods) is a list of preset -aggregations, which simplifies some use cases. +`FindMany` and `Document` classes implements [AggregateMethods](https://roman-right.github.io/beanie/api/interfaces/#aggregatemethods) interface with preset methods -```python -class Sample(Document): - category: str - price: int - count: int - - -sum_count = await Sample.find(Sample.price <= 100).sum(Sample.count) +Example of average calculation: -# Or for the whole collection: - -avg_price = await Sample.avg(Sample.count) +*With search criteria* +```python +avg_price = await Product.find( + Product.category.name == "Chocolate" +).avg(Product.price) +``` +*Over the whole collection* +```python +avg_price = await Product.avg(Product.price) ``` -## Aggregate over collection +#### Native syntax -`AggregationQuery` implements async generator pattern - results -are available via `async for` loop +You can use the native PyMongo syntax of the aggregation pipelines to aggregate over the whole collection or over the subset too. `projection_model` parameter is responsible for the output format. It will return dictionaries, if this parameter is not provided. ```python class OutputItem(BaseModel): @@ -35,30 +30,10 @@ class OutputItem(BaseModel): total: int -async for item in Sample.aggregate( - [{"$group": {"_id": "$category", "total": {"$sum": "$count"}}}], - aggregation_model=OutputItem, -): - ... -``` - -or with `to_list` method: - -```python -result = await Sample.aggregate( - [{"$group": {"_id": "$category", "total": {"$sum": "$count"}}}] +result = await Product.find( + Product.category.name == "Chocolate").aggregate( + [{"$group": {"_id": "$category.name", "total": {"$avg": "$price"}}}], + projection_model=OutputItem ).to_list() -``` - -If the `aggregation_model` parameter is not set, it will return dicts. - -## Over subsets - -To aggregate over a specific subset, FindQuery could be used. - -```python -result = await Sample.find(Sample.price < 10).aggregate( - [{"$group": {"_id": "$category", "total": {"$sum": "$count"}}}] -).to_list() -``` +``` \ No newline at end of file diff --git a/docs/tutorial/delete.md b/docs/tutorial/delete.md index 5416076e..b1aa00a9 100644 --- a/docs/tutorial/delete.md +++ b/docs/tutorial/delete.md @@ -1,5 +1,7 @@ # Delete documents +Beanie supports, and single, and batch deletions: + ## Single ```python diff --git a/docs/tutorial/document.md b/docs/tutorial/document.md new file mode 100644 index 00000000..c32a8dde --- /dev/null +++ b/docs/tutorial/document.md @@ -0,0 +1,134 @@ +The `Document` class in Beanie is responsible for mapping and handling the data +from the collection. It is inherited from the `BaseModel` Pydantic class, so it +follows the same data typing and parsing behavior. + +```python +import pymongo +from typing import Optional + +from pydantic import BaseModel + +from beanie import Document +from beanie import Indexed + + +class Category(BaseModel): + name: str + description: str + + +class Product(Document): # This is the model + name: str + description: Optional[str] = None + price: Indexed(float, pymongo.DESCENDING) + category: Category + + class Collection: + name = "products" + indexes = [ + [ + ("name", pymongo.TEXT), + ("description", pymongo.TEXT), + ], + ] + +``` + +## Fields + +As it is mentioned before, the `Document` class is inherited from the Pydantic `BaseModel` class. It uses all the same patterns of `BaseModel`. But also it has special fields and fields types: + +- id +- Indexed + +### id + +`id` field of the `Document` class reflects the unique `_id` field of the MongoDB document. Each object of the `Document` type has this field. The type of this is [PydanticObjectId](https://roman-right.github.io/beanie/api/fields/#pydanticobjectid). + +```python +class Sample(Document): + num: int + description: str + +foo = await Sample.find_one(Sample.num > 5) + +print(foo.id) # This will print id + +bar = await Sample.get(foo.id) # get by id +``` + +### Indexed + +To setup an index over a single field the `Indexed` function can be used to wrap the type: + +```python +from beanie import Indexed + +class Sample(Document): + num: Indexed(int) + description: str +``` + +The `Indexed` function takes an optional argument `index_type`, which may be set to a pymongo index type: +```python +class Sample(Document): + description: Indexed(str, index_type = pymongo.TEXT) +``` + The `Indexed` function supports pymogo `IndexModel` kwargs arguments ([PyMongo Documentation](https://pymongo.readthedocs.io/en/stable/api/pymongo/operations.html#pymongo.operations.IndexModel)). + +For example to create `unique` index: + +```python +class Sample(Document): + name: Indexed(str, unique=True) +``` + +## Collection + +The inner class `Collection` is used to configure: + +- MongoDB collection name +- Indexes + +### Collection name + +To set MongoDB collection name you can use the `name` field of the `Collection` inner class. + +```python +class Sample(Document): + num: int + description: str + + class Collection: + name = "samples" +``` + +### Indexes + +The `indexes` field of the inner `Collection` class is responsible for the indexes setup. It is a list where items could be: + +- single key. Name of the document's field (this is equivalent to using the Indexed function described above) +- list of (key, direction) pairs. Key - string, name of the document's field. Direction - pymongo direction ( + example: `pymongo.ASCENDING`) +- `pymongo.IndexModel` instance - the most flexible + option. [PyMongo Documentation](https://pymongo.readthedocs.io/en/stable/beanie/api/pymongo/operations.html#pymongo.operations.IndexModel) + +```python +class DocumentTestModelWithIndex(Document): + test_int: int + test_list: List[SubDocument] + test_str: str + + class Collection: + indexes = [ + "test_int", + [ + ("test_int", pymongo.ASCENDING), + ("test_str", pymongo.DESCENDING), + ], + IndexModel( + [("test_str", pymongo.DESCENDING)], + name="test_string_index_DESCENDING", + ), + ] +``` \ No newline at end of file diff --git a/docs/tutorial/find.md b/docs/tutorial/find.md index 66bfb387..010f74fc 100644 --- a/docs/tutorial/find.md +++ b/docs/tutorial/find.md @@ -8,7 +8,7 @@ Beanie provides two ways to find documents: On searching for a single document it uses [FindOne](https://roman-right.github.io/beanie/api/queries/#findone) query, many documents - [FindMany](https://roman-right.github.io/beanie/api/queries/#findmany) query. -Next document models will be used for this tutorial: +Next document models will be used for this documentation: ```python from typing import Optional diff --git a/docs/tutorial/indexes.md b/docs/tutorial/indexes.md index 42dcdde4..752cf525 100644 --- a/docs/tutorial/indexes.md +++ b/docs/tutorial/indexes.md @@ -5,17 +5,24 @@ To setup an index over a single field the `Indexed` function can be used to wrap ```python from beanie import Indexed -class DocumentTestModelWithIndex(Document): - test_int: Indexed(int) - test_list: List[SubDocument] - test_str: str +class Sample(Document): + num: Indexed(int) + description: str ``` The `Indexed` function takes an optional argument `index_type`, which may be set to a pymongo index type: ```python -test_str: Indexed(str, index_type = pymongo.TEXT) +class Sample(Document): + description: Indexed(str, index_type = pymongo.TEXT) ``` + The `Indexed` function supports pymogo `IndexModel` kwargs arguments ([PyMongo Documentation](https://pymongo.readthedocs.io/en/stable/api/pymongo/operations.html#pymongo.operations.IndexModel)). + +For example to create `unique` index: +```python +class Sample(Document): + name: Indexed(str, unique=True) +``` ## Complex indexes More complex indexes can be set up by the `indexes` field in a Collection class. It is a list where items could be: @@ -24,7 +31,7 @@ More complex indexes can be set up by the `indexes` field in a Collection class. - list of (key, direction) pairs. Key - string, name of the document's field. Direction - pymongo direction ( example: `pymongo.ASCENDING`) - `pymongo.IndexModel` instance - the most flexible - option. [Documentation](https://pymongo.readthedocs.io/en/stable/beanie/api/pymongo/operations.html#pymongo.operations.IndexModel) + option. [PyMongo Documentation](https://pymongo.readthedocs.io/en/stable/beanie/api/pymongo/operations.html#pymongo.operations.IndexModel) ```python class DocumentTestModelWithIndex(Document): diff --git a/docs/tutorial/init.md b/docs/tutorial/init.md new file mode 100644 index 00000000..e96d69fe --- /dev/null +++ b/docs/tutorial/init.md @@ -0,0 +1,26 @@ +Beanie uses Motor as an async database engine. To init previously created documents, you should provide the Motor database instance and list of your document models to the `init_beanie(...)` function, as it is shown in the example: +```python +from beanie import init_beanie + +class Sample(Document): + name: str + +# Crete Motor client +client = motor.motor_asyncio.AsyncIOMotorClient( + "mongodb://user:pass@host:27017" +) + +# Init beanie with the Product document class +await init_beanie(database=client.db_name, document_models=[Sample]) +``` + +`init_beanie` supports not only list of classes for the document_models parameter, but also strings with the dot separated paths. Example: + +```python +await init_beanie( + database=db, + document_models=[ + "app.models.DemoDocument", + ], + ) +``` \ No newline at end of file diff --git a/docs/tutorial/insert.md b/docs/tutorial/insert.md index 0a797aed..dde1cf69 100644 --- a/docs/tutorial/insert.md +++ b/docs/tutorial/insert.md @@ -1,5 +1,7 @@ # Insert the documents +Beanie supports and single document creation and batch inserts: + ## Insert single ```python diff --git a/docs/tutorial/install.md b/docs/tutorial/install.md deleted file mode 100644 index ec86d7d2..00000000 --- a/docs/tutorial/install.md +++ /dev/null @@ -1,83 +0,0 @@ -## Installation - -### PIP - -```shell -pip install beanie -``` - -### Poetry - -```shell -poetry add beanie -``` - -## Document set up - -```python -import pymongo -from typing import Optional - -from pydantic import BaseModel - -from beanie import Document -from beanie import Indexed - - -class Category(BaseModel): - name: str - description: str - - -class Product(Document): # This is the model - name: str - description: Optional[str] = None - price: Indexed(float, pymongo.DESCENDING) - category: Category - - class Collection: - name = "products" - indexes = [ - [ - ("name", pymongo.TEXT), - ("description", pymongo.TEXT), - ], - ] - -``` -Each document by default has `id` ObjectId field, which reflects `_id` MongoDB document field. It can be used later as an argument for the `get()` method. - -To set up the collection name it uses inner class Collection. - -To set up a simple index [Indexed](https://roman-right.github.io/beanie/api/fields/#indexed) function over the data type can be used - -For the complex cases indexes should be set up in the Collection inner class also. - -More information about indexes could be found [here](https://roman-right.github.io/beanie/tutorial/indexes/) - -## Init - -Beanie uses Motor as an async database engine. To init previously created documents, you should provide the Motor database instance and list of your document models to the `init_beanie(...)` function, as it is shown in the example: -```python -from beanie import init_beanie - -# Crete Motor client -client = motor.motor_asyncio.AsyncIOMotorClient( - "mongodb://user:pass@host:27017" -) - -# Init beanie with the Product document class -await init_beanie(database=client.db_name, document_models=[Product]) -``` - -`init_beanie` supports not only list of classes for the document_models parameter, but also strings with the dot separated paths. Example: - -```python -await init_beanie( - database=db, - document_models=[ - "app.models.DemoDocument", - ], - ) -``` - diff --git a/mkdocs.yml b/mkdocs.yml index 0db1191b..827c5b6e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,14 +1,13 @@ site_name: Documentation nav: - Introduction: index.md - - Tutorial: - - Install and set up: tutorial/install.md - - Indexes: tutorial/indexes.md - - Insert: tutorial/insert.md - - Find: tutorial/find.md - - Update: tutorial/update.md - - Delete: tutorial/delete.md - - Aggregate: tutorial/aggregate.md + - Document: tutorial/document.md + - Init: tutorial/init.md + - Create: tutorial/insert.md + - Find: tutorial/find.md + - Update: tutorial/update.md + - Delete: tutorial/delete.md + - Aggregate: tutorial/aggregate.md - Api: Document: api/document.md Query: api/queries.md diff --git a/pyproject.toml b/pyproject.toml index 9f60c8c3..f253895a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "beanie" -version = "1.0.3" +version = "1.0.4" description = "Asynchronous Python ODM for MongoDB" authors = ["Roman "] license = "Apache-2.0" diff --git a/tests/odm/documents/test_aggregate.py b/tests/odm/documents/test_aggregate.py index 82798033..f2ea23d1 100644 --- a/tests/odm/documents/test_aggregate.py +++ b/tests/odm/documents/test_aggregate.py @@ -44,7 +44,7 @@ class OutputItem(BaseModel): ids = [] async for i in DocumentTestModel.aggregate( [{"$group": {"_id": "$test_str", "total": {"$sum": "$test_int"}}}], - aggregation_model=OutputItem, + projection_model=OutputItem, ): if i.id == "cuatro": assert i.total == 0 diff --git a/tests/test_beanie.py b/tests/test_beanie.py index f0a453ac..b88ec31f 100644 --- a/tests/test_beanie.py +++ b/tests/test_beanie.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == "1.0.3" + assert __version__ == "1.0.4"