diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..72fa056 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,30 @@ +name: Docs +on: + push: + branches: + - master + - main + - 'feature/**' +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force --clean -f mkdocs/mkdocs.yml diff --git a/mkdocs/docs/Testing.md b/mkdocs/docs/Testing.md new file mode 100644 index 0000000..a088d14 --- /dev/null +++ b/mkdocs/docs/Testing.md @@ -0,0 +1,9 @@ + +# Tests + +To run the tests suite: + + - make sure you have a MongoDB database running on `localhost:27017` (you can + spawn one with `docker-compose up -d`); + - install developers requirements with `pip install -r requirements.txt`; + - run `pytest`. diff --git a/mkdocs/docs/compatibility.md b/mkdocs/docs/compatibility.md new file mode 100644 index 0000000..88ea518 --- /dev/null +++ b/mkdocs/docs/compatibility.md @@ -0,0 +1,28 @@ +# Compatibility + +We support all Python and MongoDB versions supported by [PyMongo][pymongo], +namely: + +- CPython 3.7+ and PyPy3.7+ +- MongoDB 3.6, 4.0, 4.2, 4.4, and 5.0. + +As a backend, Mongo-Thingy supports the following libraries: + +- Synchronous: + + * [PyMongo][pymongo] (default) + * [Mongomock][mongomock] + * [MontyDB][montydb] + +- Asynchronous: + + * [Motor][motor] (default when Motor is installed) + * [Motor][motor] with Tornado (default when Motor and Tornado are installed) + * [Mongomock-Motor][mongomock-motor] + +# Install + +```sh +pip install mongo-thingy +``` + diff --git a/mkdocs/docs/discovery.md b/mkdocs/docs/discovery.md new file mode 100644 index 0000000..96b3517 --- /dev/null +++ b/mkdocs/docs/discovery.md @@ -0,0 +1,35 @@ +## Database/collection "discovery" + +### Default behaviour + +```python +>>> class AuthenticationGroup(Thingy): +... pass + +>>> connect("mongodb://localhost/") +>>> AuthenticationGroup.collection +Collection(Database(MongoClient(host=['localhost:27017'], ...), 'authentication'), 'group') +``` + +### Use mismatching names for Thingy class and database collection + +You can either specify the collection name: + +```python +>>> class Foo(Thingy): +... collection_name = "bar" +``` + +or the collection directly: + +```python +>>> class Foo(Thingy): +... collection = db.bar +``` + +You can then check what collection is being used with: + +```python +>>> Foo.collection +Collection(Database(MongoClient('localhost', 27017), 'database'), 'bar') +``` diff --git a/mkdocs/docs/example.md b/mkdocs/docs/example.md new file mode 100644 index 0000000..103ff06 --- /dev/null +++ b/mkdocs/docs/example.md @@ -0,0 +1,53 @@ +# Examples + +## First steps + +### Connect, insert and find thingies + +```python +>>> from mongo_thingy import connect, Thingy +>>> connect("mongodb://localhost/test") + +>>> class User(Thingy): +... pass + +>>> user = User({"name": "Mr. Foo", "age": 42}).save() +>>> User.count_documents() +1 +>>> User.find_one({"age": 42}) +User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42}) +``` + +In an AsyncIO (or Tornado) environment, use the asynchronous class instead: + +```python +>>> from mongo_thingy import connect, AsyncThingy +>>> connect("mongodb://localhost/test") + +>>> class User(AsyncThingy): +... pass + +>>> user = await User({"name": "Mr. Foo", "age": 42}).save() +>>> await User.count_documents() +1 +>>> await User.find_one({"age": 42}) +User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42}) +``` + +To use another backend than the default ones, just pass its client class with +``client_cls``: + +```python +>>> import mongomock +>>> connect(client_cls=mongomock.MongoClient) +``` + +### Update a thingy + +```python +>>> user.age +42 +>>> user.age = 1337 +>>> user.save() +User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337}) +``` diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md new file mode 100644 index 0000000..1c7d8af --- /dev/null +++ b/mkdocs/docs/index.md @@ -0,0 +1,42 @@ +[pymongo]: https://github.com/mongodb/mongo-python-driver +[thingy]: https://github.com/Refty/thingy +[mongomock]: https://github.com/mongomock/mongomock +[montydb]: https://github.com/davidlatwe/montydb +[motor]: https://github.com/mongodb/motor +[mongomock-motor]: https://github.com/michaelkryukov/mongomock_motor + + + +
+ +**_Mongo-Thingy_ is the most idiomatic and friendly-yet-powerful way to use +MongoDB with Python.** + +It is an _"Object-Document Mapper"_ that gives you full advantage of MongoDB +schema-less design by **not** asking you to define schemas in your code. + +What you'll get: + +- a simple and robust pure-Python code base, with 100% coverage and few + dependencies; +- [PyMongo][pymongo] query language - no need to learn yet another one; +- both sync and async support! choose what suits you best; +- [Thingy][thingy] views - control what to show, and create fields based on + other fields; +- swappable backend - wanna use SQLite behind the scenes? well, you can; +- versioning *(optional)* - rollback to any point in any thingy history; +- and more! + + + + diff --git a/mkdocs/docs/indexing.md b/mkdocs/docs/indexing.md new file mode 100644 index 0000000..84313d7 --- /dev/null +++ b/mkdocs/docs/indexing.md @@ -0,0 +1,42 @@ +## Indexes + +### Create an index + +```python +>>> User.create_index("email", sparse=True, unique=True) +``` + +### Add one or more indexes, create later + +```python +>>> User.add_index("email", sparse=True, unique=True) +>>> User.add_index("username") + +>>> User.create_indexes() +``` + +### Create all indexes of all thingies at once + +```python +>>> from mongo_thingy import create_indexes +>>> create_indexes() +``` + +## Dealing with camelCase data + +```python +>>> from mongo_thingy.camelcase import CamelCase + +>>> class SystemUser(CamelCase, Thingy): +... collection_name = "systemUsers" + +>>> user = SystemUser.find_one() +>>> user.view() +{'_id': ObjectId(...), 'firstName': 'John', 'lastName': 'Doe'} + +>>> user.first_name +'John' +>>> user.first_name = "Jonny" +>>> user.save() +SystemUser({'_id': ObjectId(...), firstName: 'Jonny', lastName: 'Doe'}) +``` diff --git a/mkdocs/docs/sponsors.md b/mkdocs/docs/sponsors.md new file mode 100644 index 0000000..712d022 --- /dev/null +++ b/mkdocs/docs/sponsors.md @@ -0,0 +1,10 @@ +# Sponsors + + diff --git a/mkdocs/docs/versioning.md b/mkdocs/docs/versioning.md new file mode 100644 index 0000000..d370daa --- /dev/null +++ b/mkdocs/docs/versioning.md @@ -0,0 +1,28 @@ +## Versioning + +```python +>>> from mongo_thingy.versioned import Versioned + +>>> class Article(Versioned, Thingy): +... pass + +>>> article = Article(content="Cogito ergo sum") +>>> article.version +0 + +>>> article.save() +Article({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'}) +>>> article.version +1 + +>>> article.content = "Sum ergo cogito" +>>> article.save() +Article({'_id': ObjectId('...'), 'content': 'Sum ergo cogito'}) +>>> article.version +2 + +>>> article.revert() +Article({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'}) +>>> article.version +3 +``` diff --git a/mkdocs/docs/views.md b/mkdocs/docs/views.md new file mode 100644 index 0000000..36710a3 --- /dev/null +++ b/mkdocs/docs/views.md @@ -0,0 +1,56 @@ +## Thingy views power + +### Complete information with properties + +```python +>>> class User(Thingy): +... @property +... def username(self): +... return "".join(char for char in self.name if char.isalpha()) + +>>> User.add_view(name="everything", defaults=True, include="username") +>>> user = User.find_one() +>>> user.view("everything") +{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'username': 'MrFoo'} +``` + +### Hide sensitive stuff + +```python +>>> User.add_view(name="public", defaults=True, exclude="password") +>>> user.password = "t0ps3cr3t" +>>> user.view() +{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'password': 't0ps3cr3t'} +>>> user.view("public") +{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337} +``` + +### Only use certain fields/properties + +```python +>>> User.add_view(name="credentials", include=["username", "password"]) +>>> user.view("credentials") +{'username': 'MrFoo', 'password': 't0ps3cr3t'} +``` + +### Apply views on cursors + +```python +>>> cursor = User.find() +>>> for credentials in cursor.view("credentials"): +... print(credentials) +{'username': 'MrFoo', 'password': 't0ps3cr3t'} +{'username': 'MrsBar', 'password': '123456789'} +... +``` + +And if your cursor is already exhausted, you can still apply a view! + +```python +>>> users = User.find().to_list(None) +>>> for credentials in users.view("credentials"): +... print(credentials) +{'username': 'MrFoo', 'password': 't0ps3cr3t'} +{'username': 'MrsBar', 'password': '123456789'} +... +``` diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml new file mode 100644 index 0000000..f5f9abd --- /dev/null +++ b/mkdocs/mkdocs.yml @@ -0,0 +1,21 @@ +site_name: Refty/mongo-thingy +theme: + name: material + palette: + # Palette toggle for light mode + - scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.instant + - navigation.instant.progress + - toc.integerate + - navigation.top + diff --git a/requirements.txt b/requirements.txt index 36b346b..c03bc50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ mongomock==4.0.0 mongomock-motor==0.0.14 montydb==2.4.0 tornado==6.3.3 +mkdocs-material==9.5.8 motor==3.0.0 pytest==7.0.1 pytest-cov==3.0.0