├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── README.rst ├── mongocapsule ├── __init__.py ├── capsule.py ├── documents.py └── queryset.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_basic.py /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | pep8: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - python 9 | ratings: 10 | paths: 11 | - "**.py" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # pycharm 7 | .idea/ 8 | .idea 9 | 10 | # Packages 11 | *.egg 12 | *.egg-info 13 | build 14 | eggs 15 | parts 16 | bin 17 | var 18 | sdist 19 | develop-eggs 20 | .installed.cfg 21 | lib 22 | lib64 23 | dist 24 | 25 | # Installer logs 26 | pip-log.txt 27 | 28 | # Unit test / coverage reports 29 | .coverage 30 | .tox 31 | nosetests.xml 32 | 33 | # Complexity 34 | output/*.html 35 | output/*/index.html 36 | 37 | # Sphinx 38 | docs/_build 39 | 40 | # Cookiecutter 41 | output/ 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.5' 5 | - '3.7' 6 | - '3.8' 7 | script: 8 | - nosetests --with-coverage 9 | - codeclimate-test-reporter 10 | notifications: 11 | email: 12 | recipients: prashant@ducic.ac.in 13 | on_success: change 14 | on_failure: change 15 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | ## [0.1.2] - 2016-06-28 5 | ### Fixed 6 | - Readme 7 | 8 | ## [0.1.1] - 2016-06-28 9 | ### Added 10 | - Added additional test for multiple db usage 11 | - Added DocStrings 12 | ### Changed 13 | - Uses the content of `README.rst` in `setup.py` 14 | 15 | ## [0.0.1] - 2016-06-28 16 | ### Added 17 | - MongoEngine wrapper 18 | - Nosetests 19 | - Test coverage 20 | - Project skeleton 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Prashant Sinha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include requirements.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MongoCapsule 2 | 3 | [![Build Status](https://img.shields.io/travis/prashnts/mongocapsule/master.svg)](https://travis-ci.org/prashnts/mongocapsule) [![Code Climate](https://img.shields.io/codeclimate/github/prashnts/mongocapsule.svg)](https://codeclimate.com/github/prashnts/mongocapsule) [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/prashnts/mongocapsule.svg)](https://codeclimate.com/github/prashnts/mongocapsule) 4 | 5 | [![PyPI](https://img.shields.io/pypi/v/mongocapsule.svg)](https://pypi.python.org/pypi/mongocapsule) [![Requirements Status](https://requires.io/github/prashnts/mongocapsule/requirements.svg?branch=master)](https://requires.io/github/prashnts/mongocapsule/requirements/?branch=master) 6 | 7 | ## Overview 8 | MongoCapsule is a very thin wrapper around MongoEngine built for your happiness. It encapsulates MongoEngine attributes under a single namespace and hence allows explicit declaration without context switches. 9 | 10 | In addition to that, MongoCapsule adds pagination support to all the query results. 11 | 12 | MongoEngine is a great ORM for using MongoDB in any Python project. However, since MongoEngine works in "contexts", using multiple databases requires trickery such as `db_alias` and `switch_db`. MongoCapsule solves this by attaching references to MongoEngine attributes to itself. 13 | 14 | 15 | ## Quickstart 16 | 17 | If you are familiar with MongoEngine, you can use MongoCapsule already! Create the database object and use it to define your document and fields. 18 | 19 | ```python 20 | from mongocapsule import MongoCapsule 21 | 22 | db = MongoCapsule('test_db') 23 | 24 | class Fruits(db.Document): 25 | name = db.StringField() 26 | ``` 27 | 28 | Refer to [MongoEngine Docs](http://docs.mongoengine.org/index.html) for details. 29 | 30 | ## Installation 31 | 32 | To install use pip: 33 | 34 | ```bash 35 | pip install mongocapsule 36 | ``` 37 | 38 | Or clone the repo: 39 | 40 | ```bash 41 | git clone https://github.com/prashnts/mongocapsule.git 42 | python setup.py install 43 | ``` 44 | 45 | ## Additional API 46 | 47 | MongoCapsule adds Pagination support to the MongoEngine `QuerySet` object. It returns 10 objects per page, however, this can be changed. 48 | 49 | ```python 50 | 51 | # Obtain nth Page of any arbitrary query: 52 | 53 | query_results = Document.objects(...).sort(...) 54 | 55 | result_page = query_results.page(2) # Obtain second page 56 | 57 | total_pages = query_results.page_count 58 | 59 | # Update number of items returned per page: 60 | 61 | db.QuerySet.set_page_limit(20) 62 | 63 | ``` 64 | 65 | ## Contributing 66 | 67 | Code Patches, suggestions and bug reports welcome! Please use GitHub issues for the same. 68 | 69 | ## Rant 70 | I wrote this module because the examples in official MongoEngine documentation encourages using `from mongoengine import *` which not only pollutes the local namespace, but makes class definitions implicit. Of course, cherrypicked imports are possible, however that requires a lot of extra imports in each files. 71 | 72 | The biggest problem, however, comes when you're using multiple databases or hosts -- in those cases, you need to use context switches or ugly `meta` attributes in the declaration. 73 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | MongoCapsule 2 | ============ 3 | 4 | |Build Status| 5 | 6 | |Code Climate| 7 | 8 | |Test Coverage| 9 | 10 | |PyPI| 11 | 12 | |PyPI| 13 | 14 | Overview 15 | -------- 16 | 17 | MongoCapsule is a very thin wrapper around MongoEngine built for your 18 | happiness. It encapsulates MongoEngine attributes under a single 19 | namespace and hence allows explicit declaration without context 20 | switches. 21 | 22 | In addition to that, MongoCapsule adds pagination support to all the 23 | query results. 24 | 25 | MongoEngine is a great ORM for using MongoDB in any Python project. 26 | However, since MongoEngine works in “contexts”, using multiple databases 27 | requires trickery such as ``db_alias`` and ``switch_db``. MongoCapsule 28 | solves this by attaching references to MongoEngine attributes to itself. 29 | 30 | Quickstart 31 | ---------- 32 | 33 | If you are familiar with MongoEngine, you can use MongoCapsule already! 34 | Create the database object and use it to define your document and 35 | fields. 36 | 37 | .. code:: python 38 | 39 | from mongocapsule import MongoCapsule 40 | 41 | db = MongoCapsule('test_db') 42 | 43 | class Fruits(db.Document): 44 | name = db.StringField() 45 | 46 | Refer to `MongoEngine Docs`_ for details. 47 | 48 | Installation 49 | ------------ 50 | 51 | To install use pip: 52 | 53 | .. code:: bash 54 | 55 | pip install mongocapsule 56 | 57 | Or clone the repo: 58 | 59 | .. code:: bash 60 | 61 | git clone https://github.com/prashnts/mongocapsule.git 62 | python setup.py install 63 | 64 | Additional API 65 | -------------- 66 | 67 | MongoCapsule adds Pagination support to the MongoEngine ``QuerySet`` 68 | object. It returns 10 objects per page, however, this can be changed. 69 | 70 | .. code:: python 71 | 72 | 73 | # Obtain nth Page of any arbitrary query: 74 | 75 | query_results = Document.objects(...).sort(...) 76 | 77 | result_page = query_results.page(2) # Obtain second page 78 | 79 | total_pages = query_results.page_count 80 | 81 | # Update number of items returned per page: 82 | 83 | db.QuerySet.set_page_limit(20) 84 | 85 | Contributing 86 | ------------ 87 | 88 | Code Patches, suggestions and bug reports welcome! Please use GitHub 89 | issues for the same. 90 | 91 | Rant 92 | ---- 93 | 94 | I wrote this module because the examples in official MongoEngine 95 | documentation encourages using ``from mongoengine import *`` which not 96 | only pollutes the local namespace, but makes class definitions implicit. 97 | Of course, cherrypicked imports are possible, however that requires a 98 | lot of extra imports in each files. 99 | 100 | The biggest problem, however, comes when you’re using multiple databases 101 | or hosts – in those cases, you need to use context switches or ugly 102 | ``meta`` attributes in the declaration. 103 | 104 | .. _MongoEngine Docs: http://docs.mongoengine.org/index.html 105 | 106 | .. |Build Status| image:: https://img.shields.io/travis/prashnts/mongocapsule/master.svg 107 | :target: https://travis-ci.org/prashnts/mongocapsule 108 | .. |Code Climate| image:: https://img.shields.io/codeclimate/github/prashnts/mongocapsule.svg 109 | :target: https://codeclimate.com/github/prashnts/mongocapsule 110 | .. |Test Coverage| image:: https://img.shields.io/codeclimate/coverage/github/prashnts/mongocapsule.svg 111 | :target: https://codeclimate.com/github/prashnts/mongocapsule 112 | .. |PyPI| image:: https://img.shields.io/pypi/pyversions/mongocapsule.svg?maxAge=2592000 113 | :target: https://pypi.python.org/pypi/mongocapsule 114 | .. |PyPI| image:: https://img.shields.io/pypi/v/mongocapsule.svg?maxAge=2592000 115 | :target: https://pypi.python.org/pypi/mongocapsule -------------------------------------------------------------------------------- /mongocapsule/__init__.py: -------------------------------------------------------------------------------- 1 | from .capsule import MongoCapsule 2 | -------------------------------------------------------------------------------- /mongocapsule/capsule.py: -------------------------------------------------------------------------------- 1 | import mongoengine 2 | from .documents import Document, DynamicDocument 3 | from .queryset import QuerySet 4 | 5 | 6 | class MongoCapsule(object): 7 | '''Connect to the database and encapsulate mongoengine attributes 8 | 9 | Additional parameters are passed to mongoengine.connect. 10 | ''' 11 | 12 | def __init__(self, db, **kwa): 13 | self.__include_mongoengine__() 14 | self.connection = mongoengine.connect(db, **kwa) 15 | self.Document = Document 16 | self.DynamicDocument = DynamicDocument 17 | self.QuerySet = QuerySet 18 | 19 | def __include_mongoengine__(self): 20 | for module in mongoengine, mongoengine.fields: 21 | for key in module.__all__: 22 | if not hasattr(self, key): 23 | setattr(self, key, getattr(module, key)) 24 | -------------------------------------------------------------------------------- /mongocapsule/documents.py: -------------------------------------------------------------------------------- 1 | import mongoengine 2 | from .queryset import QuerySet 3 | 4 | 5 | class Document(mongoengine.Document): 6 | meta = { 7 | 'abstract': True, 8 | 'queryset_class': QuerySet 9 | } 10 | 11 | 12 | class DynamicDocument(mongoengine.DynamicDocument): 13 | meta = { 14 | 'abstract': True, 15 | 'queryset_class': QuerySet 16 | } 17 | -------------------------------------------------------------------------------- /mongocapsule/queryset.py: -------------------------------------------------------------------------------- 1 | import math 2 | import mongoengine 3 | 4 | 5 | class QuerySet(mongoengine.QuerySet): 6 | LIMIT = 10 7 | 8 | @staticmethod 9 | def set_page_limit(value): 10 | QuerySet.LIMIT = value 11 | 12 | def page(self, page=1): 13 | if not page >= 1: 14 | raise ValueError('Page number should be a Natural number.') 15 | skip = (page - 1) * self.LIMIT 16 | return self.skip(skip).limit(self.LIMIT) 17 | 18 | @property 19 | def page_count(self): 20 | return math.ceil(self.count() / self.LIMIT) 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mongoengine>=0.8.0 2 | mongomock>=3.4.0 3 | nose>=1.3.7 4 | rednose>=1.2.1 5 | coverage>=4.1 6 | codeclimate-test-reporter>=0.1.2 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description-file=README.md 6 | 7 | [nosetests] 8 | rednose = 1 9 | verbosity = 2 10 | detailed-errors = 1 11 | cover-erase = 1 12 | cover-branches = 1 13 | cover-package = mongocapsule 14 | tests = tests 15 | 16 | [pep8] 17 | ignore = E111,E121 18 | max-line-length = 120 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | 5 | __version__ = '0.1.2' 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | # Get the long description from the README file 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | 14 | install_requires = ['mongoengine>=0.8.0'] 15 | test_requirements = ['nose', 'rednose', 'coverage', 'mongomock'] 16 | 17 | 18 | setup( 19 | name='mongocapsule', 20 | version=__version__, 21 | description='Encapsulated MongoEngine.', 22 | long_description=long_description, 23 | url='https://github.com/prashnts/mongocapsule', 24 | download_url='https://github.com/prashnts/mongocapsule/tarball/' + __version__, 25 | license='MIT', 26 | classifiers=[ 27 | 'Development Status :: 3 - Alpha', 28 | 'Environment :: Web Environment', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Operating System :: OS Independent', 32 | 'Programming Language :: Python', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.6', 35 | 'Programming Language :: Python :: 2.7', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.2', 38 | 'Programming Language :: Python :: 3.3', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 41 | 'Topic :: Software Development :: Libraries :: Python Modules' 42 | ], 43 | keywords='', 44 | packages=['mongocapsule'], 45 | include_package_data=True, 46 | author='Prashant Sinha', 47 | install_requires=install_requires, 48 | tests_require=test_requirements, 49 | author_email='prashant@ducic.ac.in' 50 | ) 51 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prashnts/mongocapsule/f6e4e3539f2b14658698ef4c1bdd23fe075c5e0f/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import nose 2 | 3 | from mongocapsule import MongoCapsule 4 | 5 | 6 | class TestCapsule: 7 | def __init__(self): 8 | self.db = MongoCapsule('test', host='mongomock://localhost') 9 | self.set_up(self.db) 10 | 11 | def set_up(self, db): 12 | class Doc(db.Document): 13 | val = db.StringField() 14 | 15 | self.Doc = Doc 16 | 17 | def test_basic(self): 18 | self.Doc.drop_collection() 19 | obj = self.Doc(val='test') 20 | obj.save() 21 | 22 | obj_test = self.Doc.objects.get() 23 | 24 | assert obj_test.val == 'test' 25 | 26 | def test_paginate(self): 27 | self.Doc.drop_collection() 28 | for i in range(100): 29 | self.Doc(val=str(i)).save() 30 | 31 | for i, doc in enumerate(self.Doc.objects.page()): 32 | assert doc.val == str(i) 33 | 34 | for i, doc in enumerate(self.Doc.objects.page(2)): 35 | assert doc.val == str(i + 10) 36 | 37 | @nose.tools.raises(ValueError) 38 | def test_exceptions(self): 39 | self.Doc.objects.page(0) 40 | 41 | def test_update_page_limit(self): 42 | self.Doc.drop_collection() 43 | 44 | for i in range(100): 45 | self.Doc(val=str(i)).save() 46 | 47 | assert self.Doc.objects.page_count == 10 48 | 49 | self.db.QuerySet.set_page_limit(20) 50 | 51 | assert self.Doc.objects.page_count == 5 52 | 53 | 54 | def test_multiple(): 55 | db1 = MongoCapsule('test', host='mongomock://localhost') 56 | db2 = MongoCapsule('test2', host='mongomock://localhost') 57 | 58 | class Doc1(db1.Document): 59 | val = db1.StringField() 60 | 61 | class Doc2(db2.Document): 62 | val = db2.StringField() 63 | 64 | o1 = Doc1(val="db1") 65 | o1.save() 66 | 67 | o2 = Doc2(val="db2") 68 | o2.save() 69 | 70 | assert Doc1.objects.get().val == 'db1' 71 | assert Doc2.objects.get().val == 'db2' 72 | --------------------------------------------------------------------------------