Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ instance/

# Sphinx documentation
docs/_build/
docsrc/

# PyBuilder
target/
Expand Down
327 changes: 49 additions & 278 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,311 +1,82 @@

# mongodol
MongoDB Data Object Layer.

Tools to create data abstractions over mongoDB data.

To install: ```pip install mongodol```

And of course, you need to [install MongoDB](https://www.mongodb.com/docs/manual/installation/)


# The base objects


```python
from mongodol import (
MongoClientReader,
MongoDbReader,
MongoCollectionReaderBase,
MongoCollectionReader,
MongoCollectionPersister,
)
```

`MongoClientReader` gives you access to the databases for a mongoDB host (default is localhost).
The keys are database names...


```python
client = MongoClientReader()
list(client)
```




['admin', 'config', 'local', 'py2store', 'py2store_tests', 'yf']



... and the values are db objects.
The keys of db objects are collection names...


```python
db = client["py2store"]
list(db)
```




['tmp', 'test', 'annots_example']



... and the values are collection objects.


```python
mgc = db["test"]
len(mgc)
```




0



The collection is empty. Let's get a collection object that we can actually write with.

Here, we show how you can write by appending data:


```python
writable_mgc = MongoCollectionPersister(mgc)
writable_mgc.append({"mongo": "uses", "json": "data"})
```




<pymongo.results.InsertOneResult at 0x1203f9940>



See that we have data in the collection now:


```python
keys = list(mgc)
keys
```




[{'_id': ObjectId('60359a2993b7670664918663')}]



But that's just showing the key, let's see the value under that key:


```python
k = keys[0]
mgc[k]
```




<pymongo.cursor.Cursor at 0x120fc1be0>



Oh... you get a cursor back. It's okay, a cursor is the object that will provide you with the data you requested if and when you want it.

Let's say you want it now. Just "consume" the cursor. If you're expecting just one item under that key, do this:


```python
v = next(
mgc[k], None
) # the None is there as a sentinel -- it will be used to indicate if mgc[k] has no data for you.
v
```



Access MongoDB through a `Mapping` (dict-like) interface.

{'mongo': 'uses', 'json': 'data'}
`mongodol` wraps `pymongo` collections as `Mapping`/`MutableMapping` objects (readers and
persisters), so you can read and write mongo data with normal `dict`-like syntax, and
compose your own key/value transforms with [`dol`](https://github.com/i2mint/dol) wrappers
instead of writing backend-specific boilerplate.

To install:


So indeed it worked.

You can also use extend to write in bulk.


```python
writable_mgc.extend(
[
{"kind": "example", "data": 2},
{"kind": "example", "data": [1, 2, 3]},
{"kind": "example", "data": {"nested": "dict"}},
]
)
```




<pymongo.results.InsertManyResult at 0x11e520200>




```python
list(mgc)
pip install mongodol
```

And of course, you need a running MongoDB -- see the
[installation instructions](https://www.mongodb.com/docs/manual/installation/).

<!-- epythet:agentic-readme:start -->
## For AI agents

`mongodol` publishes its documentation in forms made for coding agents. If you are one, start here.

[{'_id': ObjectId('60359a2993b7670664918663')},
{'_id': ObjectId('60359ac193b7670664918664')},
{'_id': ObjectId('60359ac193b7670664918665')},
{'_id': ObjectId('60359ac193b7670664918666')}]

**The documentation, machine-readable**: [`llms.txt`](https://i2mint.github.io/mongodol/llms.txt) indexes every page; [`mongodol.md`](https://i2mint.github.io/mongodol/mongodol.md) is the whole documentation in one file; every page has a `.md` twin; [`objects.inv`](https://i2mint.github.io/mongodol/objects.inv) maps symbols to URLs.

If you are a control freak, the rest of this README is written for you, starting at [Quick start](#quick-start).
<!-- epythet:agentic-readme:end -->

## Quick start

```python
from mongodol import MongoCollectionPersister, mk_dflt_mgc

```

So far, MongoDB gave us an id. MongoDB will make it's own id if we don't ask for a particular one.

But you can also write data to a key of your choice. With the base persister which we're demoing now, with it's base defaults, you need to specify your key as a `{'_id': YOUR_CHOICE_OF_ID}`.


```python
writable_mgc[{"_id": "my_id"}] = {"my": "data"}
list(mgc)
```




[{'_id': ObjectId('60359a2993b7670664918663')},
{'_id': ObjectId('60359ac193b7670664918664')},
{'_id': ObjectId('60359ac193b7670664918665')},
{'_id': ObjectId('60359ac193b7670664918666')},
{'_id': 'my_id'}]




```python
mgc[{"_id": "my_id"}]
```




{'my': 'data'}



You can delete data given a key:


```python
del writable_mgc[{"_id": "my_id"}]
```
# mk_dflt_mgc() gives you a pymongo collection to play with (mongodol/mongodol_test by default)
mgc = mk_dflt_mgc()
mgc.delete_many({}) # start from an empty collection (skip this to keep what's already there)
s = MongoCollectionPersister(mgc, getitem_projection={'_id': False})

len(s)
# 0

```python
list(mgc)
```




[{'_id': ObjectId('60359a2993b7670664918663')},
{'_id': ObjectId('60359ac193b7670664918664')},
{'_id': ObjectId('60359ac193b7670664918665')},
{'_id': ObjectId('60359ac193b7670664918666')}]



So far, we've seen the base classes.

So far, you have no reason what-so-ever to use `mongodol`. Might as well use `pymongo` (which it wraps) directly.

The real reason for using `mongodol` is that it is a gateway to enabling all the `py2store` goodies to create the key-value perspectives that make sense to **you**, without all the backend-dependent boilerplate over the business logic.

So let's show one example of how to do that.


# The real reason you want to use mongodol (an example)

Let's say we have the collection we just made above, but
- We want to access data by doing `s['60359a2993b7670664918663']` instead of the (annoying) `s[{'_id': ObjectId('60359a2993b7670664918663')}]`
- We'd like our values to to come in the form of actual ready to use data. Namely, we want to automatically ask the cursor for it's first element (assuming it's unique for that key), and we'd like to extract the 'data' field from that result.
- We'd like to peruse only part of the mongo collection; only if there's a 'kind' field and it's equal to 'example'.

Here's how it can be done:


```python
from bson import ObjectId
from py2store import wrap_kvs
from mongodol import MongoCollectionReaderBase


@wrap_kvs(
id_of_key=lambda x: {"_id": ObjectId(x)},
key_of_id=lambda x: str(x["_id"]),
obj_of_data=lambda doc: next(doc, None)["data"],
)
class MyStore(MongoCollectionReaderBase):
"""my special store"""
```


```python
s = MyStore(
mgc=mgc, key_fields=("_id",), data_fields=("data",), filt={"kind": "example"}
)
```


```python
k = {'_id': 'my_id'}
s[k] = {'mongo': 'uses', 'json': 'data'}
list(s)
# [{'_id': 'my_id'}]
```




['60359ac193b7670664918664',
'60359ac193b7670664918665',
'60359ac193b7670664918666']



Since the base reader is a thin, low-level wrapper, `s[k]` returns a `pymongo.cursor.Cursor`
(a key may match zero, one, or many docs), so you fetch the value(s) explicitly:

```python
s["60359ac193b7670664918664"]
```




2
next(s[k])
# {'mongo': 'uses', 'json': 'data'}

del s[k]
len(s)
# 0
```

## Beyond the base classes

The base `MongoCollectionReader`/`MongoCollectionPersister` classes always return cursors
and never validate uniqueness. For the common case of "one key maps to one doc", use one
of the `*UniqueDoc*`/`*FirstDoc*` reader and persister classes instead:

```python
list(s.values())
from mongodol import MongoCollectionUniqueDocReader
```

`MongoCollectionUniqueDocReader` gives you `s[k]` as a plain `dict` (not a cursor), and
raises `KeyNotUniqueError` if more than one doc matches `k`. See its docstring for a
runnable example.

For custom key/value shapes, business logic, or connecting `mongodol` stores to the rest
of the [`dol`](https://github.com/i2mint/dol) ecosystem (caching, serialization,
key transforms, etc.), wrap a `mongodol` store with `dol.wrap_kvs` like you would any
other `dol` store.

## More

[2, [1, 2, 3], {'nested': 'dict'}]

See the [package documentation](https://i2mint.github.io/mongodol/) and the flat
[`mongodol.md`](https://i2mint.github.io/mongodol/mongodol.md) aggregate for the full API.
Loading
Loading