Technical Documentation: msiemens/tinydb
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Api (`docs/api.rst`)
.. _api_docs:
API Documentation
Architecture Overview
TinyDB is organized in three layers: the core API you interact with in
application code, a persistence layer that serializes database state, and
supporting components used internally for caching, updates, and query
hashing.
At the center is :class:~tinydb.database.TinyDB, which owns a
:class:~tinydb.storages.Storage instance and manages one or more
:class:~tinydb.table.Table objects. Each table stores documents as
:class:~tinydb.table.Document instances (dict-like objects that also exposedoc_id) and executes queries built from :class:~tinydb.queries.Query and
:class:~tinydb.queries.QueryInstance.
Persistence is handled by subclasses of :class:~tinydb.storages.Storage such
as :class:~tinydb.storages.JSONStorage and
:class:~tinydb.storages.MemoryStorage. You can wrap a storage with
:class:~tinydb.middlewares.Middleware implementations like
:class:~tinydb.middlewares.CachingMiddleware to change how reads and writes
are processed.
The diagram below summarizes how these pieces relate:
.. image:: /_static/architecture.svg
:alt: TinyDB class diagram showing relationships between TinyDB, Table, Document, Query, Storage, and supporting components
:align: center
Typical data flow
- You call methods on :class:
~tinydb.database.TinyDB(for exampleinsertorsearch). Unless you opened a named table explicitly,
these calls are forwarded to the default table. - The :class:
~tinydb.table.Tableevaluates a :class:~tinydb.queries.QueryInstance
against its documents. Results may be served from the table's
:class:~tinydb.utils.LRUCachequery cache. - When the table changes, it reads and writes the full database state through
its :class:~tinydb.storages.Storageinstance. - For partial document updates, :mod:
tinydb.operationsprovides helper
callables that can be passed to :meth:~tinydb.table.Table.update.
If you plan to extend TinyDB, start with :doc:extend for custom storages and
middlewares, then use the class reference sections below for method-level
details.
``tinydb.database``
.. autoclass:: tinydb.database.TinyDB
:members:
:private-members:
:member-order: bysource
.. _table_api:
``tinydb.table``
.. autoclass:: tinydb.table.Table
:members:
:special-members:
:exclude-members: dict, weakref
:member-order: bysource
.. autoclass:: tinydb.table.Document
:members:
:special-members:
:exclude-members: dict, weakref
:member-order: bysource
.. py:attribute:: doc_id
The document's id``tinydb.queries``
.. autoclass:: tinydb.queries.Query
:members:
:special-members:
:exclude-members: weakref
:member-order: bysource
.. autoclass:: tinydb.queries.QueryInstance
:members:
:special-members:
:exclude-members: weakref
:member-order: bysource
``tinydb.operations``
.. automodule:: tinydb.operations
:members:
:special-members:
:exclude-members: weakref
:member-order: bysource
``tinydb.storage``
.. automodule:: tinydb.storages
:members: JSONStorage, MemoryStorage
:special-members:
:exclude-members: weakref
.. class:: Storage
The abstract base class for all Storages.
A Storage (de)serializes the current state of the database and stores
it in some place (memory, file on disk, ...).
.. method:: read()
Read the last stored state.
.. method:: write(data)
Write the current state of the database to the storage.
.. method:: close()
Optional: Close open file handles, etc.``tinydb.middlewares``
.. automodule:: tinydb.middlewares
:members: CachingMiddleware
:special-members:
:exclude-members: weakref
.. class:: Middleware
The base class for all Middlewares.
Middlewares hook into the read/write process of TinyDB allowing you to
extend the behaviour by adding caching, logging, ...
If ``read()`` or ``write()`` are not overloaded, they will be forwarded
directly to the storage instance.
.. attribute:: storage
:type: :class:`.Storage`
Access to the underlying storage instance.
.. method:: read()
Read the last stored state.
.. method:: write(data)
Write the current state of the database to the storage.
.. method:: close()
Optional: Close open file handles, etc.``tinydb.utils``
.. autoclass:: tinydb.utils.LRUCache
:members:
:special-members:
Changelog (`docs/changelog.rst`)
Changelog
Version Numbering
^^^^^^^^^^^^^^^^^
TinyDB follows the SemVer versioning guidelines. For more information,
see semver.org <http://semver.org/>_
.. note:: When new methods are added to the Query API, this may
result in breaking existing code that uses the property syntax
to access document fields (e.g. Query().some.nested.field)
where the field name is equal to the newly added query method.
Thus, breaking changes may occur in feature releases even though
they don't change the public API in a backwards-incompatible
manner.
To prevent this from happening, one can use the dict access
syntax (``Query()['some']['nested']['field']``) that will
not break even when new methods are added to the ``Query`` API.v4.9.0 (2026-08-06)
^^^^^^^^^^^^^^^^^^^
- Drop official support for Python 3.8 and 3.9. Both versions have
reached their official End of Life (Python 3.8 in October 2024,
Python 3.9 in October 2025). TinyDB now requires Python 3.10 or later - Fix: Make
Table.update(doc_ids=...)andTable.remove(doc_ids=...)
behave consistently withTable.get(doc_id=...)when given document
IDs that don't exist: missing IDs are now silently skipped and the
returned list only contains IDs that were actually updated/removed.
Previously both methods raisedKeyError, andupdatecould leave
the table in a partially-updated state when a mix of existing and
missing IDs was passed
(seeissue 591 <https://github.com/msiemens/tinydb/issues/591>_). - Fix: Prevent
TypeErrorwhen passing unhashable extra arguments
(like lists or dicts) toQuery.test()
(seeissue 517 <https://github.com/msiemens/tinydb/issues/517>_). - Fix: Prevent
FileNotFoundErrorwhen creating a database file in the
current directory without an explicit path prefix
(seepull request 619 <https://github.com/msiemens/tinydb/pull/619>_). - Fix: Correctly handle falsy values (like
0orNone) stored inLRUCache. - Improvement: More precise type hints for
Table.get
(seepull request 602 <https://github.com/msiemens/tinydb/pull/602>_). - Improvement: Correct the type hints for
Table.updateandtinydb.operationsto useMutableMapping, so transform functions
that modify documents in place type-check correctly
(seeissue 598 <https://github.com/msiemens/tinydb/issues/598>_).
v4.8.2 (2024-10-12)
^^^^^^^^^^^^^^^^^^^
- Fix: Correctly update query cache when search results have changed
(seeissue 560 <https://github.com/msiemens/tinydb/issues/560>_).
v4.8.1 (2024-10-07)
^^^^^^^^^^^^^^^^^^^
- Feature: Allow persisting empty tables
(seepull request 518 <https://github.com/msiemens/tinydb/pull/518>_). - Fix: Make replacing
doc_idtype work properly
(seeissue 545 <https://github.com/msiemens/tinydb/issues/545>_).
v4.8.0 (2023-06-12)
^^^^^^^^^^^^^^^^^^^
- Feature: Allow retrieve multiple documents by document ID using
Table.get(doc_ids=[...])
(seepull request 504 <https://github.com/msiemens/tinydb/pull/504>_).
v4.7.1 (2023-01-14)
^^^^^^^^^^^^^^^^^^^
- Improvement: Improve typing annotations
(seepull request 477 <https://github.com/msiemens/tinydb/pull/477>_). - Improvement: Fix some typos in the documentation
(seepull request 479 <https://github.com/msiemens/tinydb/pull/479>_
andpull request 498 <https://github.com/msiemens/tinydb/pull/498>_).
v4.7.0 (2022-02-19)
^^^^^^^^^^^^^^^^^^^
- Feature: Allow inserting
Documentinstances usingTable.insert_multiple
(seepull request 455 <https://github.com/msiemens/tinydb/pull/455>_). - Performance: Only convert document IDs of a table when returning documents.
This improves performance theTable.countandTable.getoperations
and also forTable.searchwhen only returning a few documents
(seepull request 460 <https://github.com/msiemens/tinydb/pull/460>_). - Internal change: Run all
TabletestsJSONStoragein addition toMemoryStorage.
v4.6.1 (2022-01-18)
^^^^^^^^^^^^^^^^^^^
- Fix: Make using callables as queries work again
(seeissue 454 <https://github.com/msiemens/tinydb/issues/454>__)
v4.6.0 (2022-01-17)
^^^^^^^^^^^^^^^^^^^
- Feature: Add
map()query operation to apply a transformation
to a document or field when evaluating a query
(seepull request 445 <https://github.com/msiemens/tinydb/pull/445>_).
Note: This may break code that queries for a field namedmap
using theQueryAPIs property access syntax - Feature: Add support for
typing-extensions <https://pypi.org/project/typing-extensions/>_
v4 - Documentation: Fix a couple of typos in the documentation (see
pull request 446 <https://github.com/msiemens/tinydb/pull/446>,pull request 449 <https://github.com/msiemens/tinydb/pull/449>andpull request 453 <https://github.com/msiemens/tinydb/pull/453>_)
v4.5.2 (2021-09-23)
^^^^^^^^^^^^^^^^^^^
- Fix: Make
Table.delete()'s argument priorities consistent with
other table methods. This means that if you pass bothcondas
well asdoc_idstoTable.delete(), the latter will be preferred
(seeissue 424 <https://github.com/msiemens/tinydb/issues/424>__)
v4.5.1 (2021-07-17)
^^^^^^^^^^^^^^^^^^^
- Fix: Correctly install
typing-extensionson Python 3.7
(seeissue 413 <https://github.com/msiemens/tinydb/issues/413>__)
v4.5.0 (2021-06-25)
^^^^^^^^^^^^^^^^^^^
- Feature: Better type hinting/IntelliSense for PyCharm, VS Code and MyPy
(seeissue 372 <https://github.com/msiemens/tinydb/issues/372>__).
PyCharm and VS Code should work out of the box, for MyPy see
:ref:MyPy Type Checking <mypy_type_checking>
v4.4.0 (2021-02-11)
^^^^^^^^^^^^^^^^^^^
- Feature: Add operation for searching for all documents that match a
dict
fragment (seeissue 300 <https://github.com/msiemens/tinydb/issues/300>_) - Fix: Correctly handle queries that use fields that are also Query methods,
e.g.Query()['test']for searching for documents with atestfield
(seeissue 373 <https://github.com/msiemens/tinydb/issues/373>_)
v4.3.0 (2020-11-14)
^^^^^^^^^^^^^^^^^^^
- Feature: Add operation for updating multiple documents:
update_multiple
(seeissue 346 <https://github.com/msiemens/tinydb/issues/346>_) - Improvement: Expose type information for MyPy typechecking (PEP 561)
(seepull request 352 <https://github.com/msiemens/tinydb/pull/352>_)
v4.2.0 (2020-10-03)
^^^^^^^^^^^^^^^^^^^
- Feature: Add support for specifying document IDs during insertion
(seeissue 303 <https://github.com/msiemens/tinydb/issues/303>_) - Internal change: Use
OrderedDict.move_to_end()in the query cache
(seeissue 338 <https://github.com/msiemens/tinydb/issues/338>_)
v4.1.1 (2020-05-08)
^^^^^^^^^^^^^^^^^^^
- Fix: Don't install dev-dependencies when installing from PyPI (see
issue 315 <https://github.com/msiemens/tinydb/issues/315>_)
v4.1.0 (2020-05-07)
^^^^^^^^^^^^^^^^^^^
- Feature: Add a no-op query
Query().noop()(seeissue 313 <https://github.com/msiemens/tinydb/issues/313>_) - Feature: Add a
access_modeflag toJSONStorageto allow opening
files read-only (seeissue 297 <https://github.com/msiemens/tinydb/issues/297>_) - Fix: Don't drop the first document that's being inserted when inserting
data on an existing database (seeissue 314 <https://github.com/msiemens/tinydb/issues/314>_)
v4.0.0 (2020-05-02)
^^^^^^^^^^^^^^^^^^^
:ref:Upgrade Notes
Breaking Changes
Python 2 support has been removed, see
issue 284 <https://github.com/msiemens/tinydb/issues/284>_
for backgroundAPI changes:
Removed classes:
DataProxy,StorageProxyAttributes removed from
TinyDBin favor of
customizingTinyDB's behavior by subclassing it and overloading__init__(...)andtable(...):DEFAULT_TABLEDEFAULT_TABLE_KWARGSDEFAULT_STORAGE
Arguments removed from
TinyDB(...):default_table: replace withTinyDB.default_table_name = 'name'table_class: replace withTinyDB.table_class = Class
TinyDB.contains(...)'sdoc_idsparameter has been renamed todoc_idand now only takes a single document IDTinyDB.purge_tables(...)has been renamed toTinyDB.drop_tables(...)TinyDB.purge_table(...)has been renamed toTinyDB.drop_table(...)TinyDB.write_back(...)has been removedTinyDB.process_elements(...)has been removedTable.purge()has been renamed toTable.truncate()Evaluating an empty
Query()without any test operators will now result
in an exception, useQuery().noop()(introduced in v4.1.0) instead
ujsonsupport has been removed, seeissue 263 <https://github.com/msiemens/tinydb/issues/263>_ andissue 306 <https://github.com/msiemens/tinydb/issues/306>_ for backgroundThe deprecated Element ID API has been removed (e.g. using the
Element
class oreidsparameter) in favor the Document API, seepull request 158 <https://github.com/msiemens/tinydb/pull/158>_ for details
on the replacement
Improvements
- TinyDB's internal architecture has been reworked to be more simple and
streamlined in order to make it easier to customize TinyDB's behavior - With the new architecture, TinyDB performance will improve for many
applications
Bugfixes
- Don't break the tests when
ujsonis installed (seeissue 262 <https://github.com/msiemens/tinydb/issues/262>_) - Fix performance when reading data (see
issue 250 <https://github.com/msiemens/tinydb/issues/250>_) - Fix inconsistent purge function names (see
issue 103 <https://github.com/msiemens/tinydb/issues/103>_)
v3.15.1 (2019-10-26)
^^^^^^^^^^^^^^^^^^^^
- Internal change: fix missing values handling for
LRUCache
v3.15.0 (2019-10-12)
^^^^^^^^^^^^^^^^^^^^
- Feature: allow setting the parameters of TinyDB's default table
(seeissue 278 <https://github.com/msiemens/tinydb/issues/278>_)
v3.14.2 (2019-09-13)
^^^^^^^^^^^^^^^^^^^^
- Internal change: support correct iteration for
LRUCacheobjects
v3.14.1 (2019-07-03)
^^^^^^^^^^^^^^^^^^^^
- Internal change: fix Query class to permit subclass creation
(seepull request 270 <https://github.com/msiemens/tinydb/pull/270>_)
v3.14.0 (2019-06-18)
^^^^^^^^^^^^^^^^^^^^
- Change: support for
ujsonis now deprecated
(seeissue 263 <https://github.com/msiemens/tinydb/issues/263>_)
v3.13.0 (2019-03-16)
^^^^^^^^^^^^^^^^^^^^
- Feature: direct access to a TinyDB instance's storage
(seeissue 258 <https://github.com/msiemens/tinydb/issues/258>_)
v3.12.2 (2018-12-12)
^^^^^^^^^^^^^^^^^^^^
- Internal change: convert documents to dicts during insertion
(seepull request 256 <https://github.com/msiemens/tinydb/pull/256>_) - Internal change: use tuple literals instead of tuple class/constructor
(seepull request 247 <https://github.com/msiemens/tinydb/pull/247>_) - Infra: ensure YAML tests are run
(seepull request 252 <https://github.com/msiemens/tinydb/pull/252>_)
v3.12.1 (2018-11-09)
^^^^^^^^^^^^^^^^^^^^
- Fix: Don't break when searching the same query multiple times
(seepull request 249 <https://github.com/msiemens/tinydb/pull/249>_) - Internal change: allow
collections.abc.Mutableas valid document types
(seepull request 245 <https://github.com/msiemens/tinydb/pull/245>_)
v3.12.0 (2018-11-06)
^^^^^^^^^^^^^^^^^^^^
- Feature: Add encoding option to
JSONStorage
(seepull request 238 <https://github.com/msiemens/tinydb/pull/238>_) - Internal change: allow
collections.abc.Mutableas valid document types
(seepull request 245 <https://github.com/msiemens/tinydb/pull/245>_)
v3.11.1 (2018-09-13)
^^^^^^^^^^^^^^^^^^^^
- Bugfix: Make path queries (
db.search(where('key))) work again
(seeissue 232 <https://github.com/msiemens/tinydb/issues/232>_) - Improvement: Add custom
reprrepresentations for main classes
(seepull request 229 <https://github.com/msiemens/tinydb/pull/229>_)
v3.11.0 (2018-08-20)
^^^^^^^^^^^^^^^^^^^^
Drop official support for Python 3.3. Python 3.3 has reached its
official End Of Life as of September 29, 2017. It will probably continue
to work, but will not be tested against
(issue 217 <https://github.com/msiemens/tinydb/issues/217>_)Feature: Allow extending TinyDB with a custom storage proxy class
(seepull request 224 <https://github.com/msiemens/tinydb/pull/224>_)Bugfix: Return list of document IDs for upsert when creating a new
document (seeissue 223 <https://github.com/msiemens/tinydb/issues/223>_)
v3.10.0 (2018-07-21)
^^^^^^^^^^^^^^^^^^^^
- Feature: Add support for regex flags
(seepull request 216 <https://github.com/msiemens/tinydb/pull/216>_)
v3.9.0 (2018-04-24)
^^^^^^^^^^^^^^^^^^^
- Feature: Allow setting a table class for single table only
(seeissue 197 <https://github.com/msiemens/tinydb/issues/197>_) - Internal change: call fsync after flushing
JSONStorage
(seeissue 208 <https://github.com/msiemens/tinydb/issues/208>_)
v3.8.1 (2018-03-26)
^^^^^^^^^^^^^^^^^^^
- Bugfix: Don't install tests as a package anymore
(seepull request #195 <https://github.com/msiemens/tinydb/pull/195>_)
v3.8.0 (2018-03-01)
^^^^^^^^^^^^^^^^^^^
- Feature: Allow disabling the query cache with
db.table(name, cache_size=0)
(seepull request #187 <https://github.com/msiemens/tinydb/pull/187>_) - Feature: Add
db.write_back(docs)for replacing documents
(seepull request #184 <https://github.com/msiemens/tinydb/pull/184>_)
v3.7.0 (2017-11-11)
^^^^^^^^^^^^^^^^^^^
- Feature:
one_offor checking if a value is contained in a list
(seeissue 164 <https://github.com/msiemens/tinydb/issues/164>_) - Feature: Upsert (insert if document doesn't exist, otherwise update;
see https://forum.m-siemens.de/d/30-primary-key-well-sort-of) - Internal change: don't read from storage twice during initialization
(see https://forum.m-siemens.de/d/28-reads-the-whole-data-file-twice)
v3.6.0 (2017-10-05)
^^^^^^^^^^^^^^^^^^^
- Allow updating all documents using
db.update(fields)(seeissue #157 <https://github.com/msiemens/tinydb/issues/157>_). - Rename elements to documents. Document IDs now available with
doc.doc_id,
usingdoc.eidis now deprecated
(seepull request #158 <https://github.com/msiemens/tinydb/pull/158>_)
v3.5.0 (2017-08-30)
^^^^^^^^^^^^^^^^^^^
- Expose the table name via
table.name(seeissue #147 <https://github.com/msiemens/tinydb/issues/147>_). - Allow better subclassing of the
TinyDBclass
(seepull request #150 <https://github.com/msiemens/tinydb/pull/150>_).
v3.4.1 (2017-08-23)
^^^^^^^^^^^^^^^^^^^
- Expose TinyDB version via
import tinyb; tinydb.__version__(seeissue #148 <https://github.com/msiemens/tinydb/issues/148>_).
v3.4.0 (2017-08-08)
^^^^^^^^^^^^^^^^^^^
- Add new update operations:
add(key, value),subtract(key, value),
andset(key, value)
(seepull request #145 <https://github.com/msiemens/tinydb/pull/145>_).
v3.3.1 (2017-06-27)
^^^^^^^^^^^^^^^^^^^
- Use relative imports to allow vendoring TinyDB in other packages
(seepull request #142 <https://github.com/msiemens/tinydb/pull/142>_).
v3.3.0 (2017-06-05)
^^^^^^^^^^^^^^^^^^^
- Allow iterating over a database or table yielding all documents
(seepull request #139 <https://github.com/msiemens/tinydb/pull/139>_).
v3.2.3 (2017-04-22)
^^^^^^^^^^^^^^^^^^^
- Fix bug with accidental modifications to the query cache when modifying
the list of search results (seeissue #132 <https://github.com/msiemens/tinydb/issues/132>_).
v3.2.2 (2017-01-16)
^^^^^^^^^^^^^^^^^^^
- Fix the
Queryconstructor to prevent wrong usage
(seeissue #117 <https://github.com/msiemens/tinydb/issues/117>_).
v3.2.1 (2016-06-29)
^^^^^^^^^^^^^^^^^^^
- Fix a bug with queries on documents that have a
pathkey
(seepull request #107 <https://github.com/msiemens/tinydb/pull/107>_). - Don't write to the database file needlessly when opening the database
(seepull request #104 <https://github.com/msiemens/tinydb/pull/104>_).
v3.2.0 (2016-04-25)
^^^^^^^^^^^^^^^^^^^
Add a way to specify the default table name via :ref:
default_table <default_table>
(seepull request #98 <https://github.com/msiemens/tinydb/pull/98>_).Add
db.purge_table(name)to remove a single table
(seepull request #100 <https://github.com/msiemens/tinydb/pull/100>_).- Along the way: celebrating 100 issues and pull requests! Thanks everyone for every single contribution!
Extend API documentation (see
issue #96 <https://github.com/msiemens/tinydb/issues/96>_).
v3.1.3 (2016-02-14)
^^^^^^^^^^^^^^^^^^^
- Fix a bug when using unhashable documents (lists, dicts) with
Query.anyorQuery.allqueries
(seea forum post by karibul <https://forum.m-siemens.de/d/4-error-with-any-and-all-queries>_).
v3.1.2 (2016-01-30)
^^^^^^^^^^^^^^^^^^^
- Fix a bug when using unhashable documents (lists, dicts) with
Query.anyorQuery.allqueries
(seea forum post by karibul <https://forum.m-siemens.de/d/4-error-with-any-and-all-queries>_).
v3.1.1 (2016-01-23)
^^^^^^^^^^^^^^^^^^^
- Inserting a dictionary with data that is not JSON serializable doesn't
lead to corrupt files anymore (seeissue #89 <https://github.com/msiemens/tinydb/issues/89>_). - Fix a bug in the LRU cache that may lead to an invalid query cache
(seeissue #87 <https://github.com/msiemens/tinydb/issues/87>_).
v3.1.0 (2015-12-31)
^^^^^^^^^^^^^^^^^^^
db.update(...)anddb.remove(...)now return affected document IDs
(seeissue #83 <https://github.com/msiemens/tinydb/issues/83>_).- Inserting an invalid document (i.e. not a
dict) now raises an error
instead of corrupting the database (seeissue #74 <https://github.com/msiemens/tinydb/issues/74>_).
v3.0.0 (2015-11-13)
^^^^^^^^^^^^^^^^^^^
Overhauled Query model:
where('...').contains('...')has been renamed towhere('...').search('...').Support for ORM-like usage:
User = Query(); db.search(User.name == 'John').where('foo')is an alias forQuery().foo.where('foo').has('bar')is replaced by eitherwhere('foo').barorQuery().foo.bar.- In case the key is not a valid Python identifier, array
notation can be used:where('a.b.c')is nowQuery()['a.b.c'].
- In case the key is not a valid Python identifier, array
Checking for the existence of a key has to be done explicitly:
where('foo').exists().
Migrations from v1 to v2 have been removed.
SmartCacheTablehas been moved tomsiemens/tinydb-smartcache_.Serialization has been moved to
msiemens/tinydb-serialization_.Empty storages are now expected to return
Noneinstead of raisingValueError.
(seeissue #67 <https://github.com/msiemens/tinydb/issues/67>_.
.. _msiemens/tinydb-smartcache: https://github.com/msiemens/tinydb-smartcache
.. _msiemens/tinydb-serialization: https://github.com/msiemens/tinydb-serialization
v2.4.0 (2015-08-14)
^^^^^^^^^^^^^^^^^^^
- Allow custom parameters for custom test functions
(seeissue #63 <https://github.com/msiemens/tinydb/issues/63>_ andpull request #64 <https://github.com/msiemens/tinydb/pull/64>_).
v2.3.2 (2015-05-20)
^^^^^^^^^^^^^^^^^^^
- Fix a forgotten debug output in the
SerializationMiddleware
(seeissue #55 <https://github.com/msiemens/tinydb/issues/55>_). - Fix an "ignored exception" warning when using the
CachingMiddleware
(seepull request #54 <https://github.com/msiemens/tinydb/pull/54>_) - Fix a problem with symlinks when checking out TinyDB on OSX Yosemite
(seeissue #52 <https://github.com/msiemens/tinydb/issues/52>_).
v2.3.1 (2015-04-30)
^^^^^^^^^^^^^^^^^^^
- Hopefully fix a problem with using TinyDB as a dependency in a
setup.pyscript
(seeissue #51 <https://github.com/msiemens/tinydb/issues/51>_).
v2.3.0 (2015-04-08)
^^^^^^^^^^^^^^^^^^^
- Added support for custom serialization. That way, you can teach TinyDB
to storedatetimeobjects in a JSON file :)
(seeissue #48 <https://github.com/msiemens/tinydb/issues/48>_ andpull request #50 <https://github.com/msiemens/tinydb/pull/50>_) - Fixed a performance regression when searching became slower with every search
(seeissue #49 <https://github.com/msiemens/tinydb/issues/49>_) - Internal code has been cleaned up
v2.2.2 (2015-02-12)
^^^^^^^^^^^^^^^^^^^
- Fixed a data loss when using
CachingMiddlewaretogether withJSONStorage
(seeissue #47 <https://github.com/msiemens/tinydb/issues/47>_)
v2.2.1 (2015-01-09)
^^^^^^^^^^^^^^^^^^^
- Fixed handling of IDs with the JSON backend that converted integers
to strings (seeissue #45 <https://github.com/msiemens/tinydb/issues/45>_)
v2.2.0 (2014-11-10)
^^^^^^^^^^^^^^^^^^^
- Extended
anyandallqueries to take lists as conditions
(seepull request #38 <https://github.com/msiemens/tinydb/pull/38>_) - Fixed an
decode errorwhen installing TinyDB in a non-UTF-8 environment
(seepull request #37 <https://github.com/msiemens/tinydb/pull/37>_) - Fixed some issues with
CachingMiddlewarein combination withJSONStorage(seepull request #39 <https://github.com/msiemens/tinydb/pull/39>_)
v2.1.0 (2014-10-14)
^^^^^^^^^^^^^^^^^^^
- Added
where(...).contains(regex)
(seeissue #32 <https://github.com/msiemens/tinydb/issues/32>_) - Fixed a bug that corrupted data after reopening a database
(seeissue #34 <https://github.com/msiemens/tinydb/issues/34>_)
v2.0.1 (2014-09-22)
^^^^^^^^^^^^^^^^^^^
- Fixed handling of Unicode data in Python 2
(seeissue #28 <https://github.com/msiemens/tinydb/issues/28>_).
v2.0.0 (2014-09-05)
^^^^^^^^^^^^^^^^^^^
:ref:Upgrade Notes
.. warning:: TinyDB changed the way data is stored. You may need to migrate
your databases to the new scheme. Check out the
:ref:Upgrade Notes for details.
The syntax
query in dbhas been removed, usedb.containsinstead.The
ConcurrencyMiddlewarehas been removed due to a insecure implementation
(seeissue #18 <https://github.com/msiemens/tinydb/issues/18>_). Consider
:ref:tinyrecordinstead.Better support for working with :ref:
Document IDs <document_ids>.Added support for
nested comparisons <http://tinydb.readthedocs.io/en/v2.0.0/usage.html#nested-queries>_.Added
allandanycomparisons on lists <http://tinydb.readthedocs.io/en/v2.0.0/usage.html#nested-queries>_.Added optional :http://tinydb.readthedocs.io/en/v2.0.0/usage.html#smart-query-cache`_.
The query cache is now a :ref:
fixed size LRU cache <query_caching>.
v1.4.0 (2014-07-22)
^^^^^^^^^^^^^^^^^^^
- Added
insert_multiplefunction
(seeissue #8 <https://github.com/msiemens/tinydb/issues/8>_).
v1.3.0 (2014-07-02)
^^^^^^^^^^^^^^^^^^^
- Fixed
bug #7 <https://github.com/msiemens/tinydb/issues/7>_: IDs not unique. - Extended the API:
db.count(where(...))anddb.contains(where(...)). - The syntax
query in dbis now deprecated and replaced
bydb.contains.
v1.2.0 (2014-06-19)
^^^^^^^^^^^^^^^^^^^
- Added
updatemethod
(seeissue #6 <https://github.com/msiemens/tinydb/issues/6>_).
v1.1.1 (2014-06-14)
^^^^^^^^^^^^^^^^^^^
- Merged
PR #5 <https://github.com/msiemens/tinydb/pull/5>_: Fix minor
documentation typos and style issues.
v1.1.0 (2014-05-06)
^^^^^^^^^^^^^^^^^^^
- Improved the docs and fixed some typos.
- Refactored some internal code.
- Fixed a bug with multiple
TinyDB?instances.
v1.0.1 (2014-04-26)
^^^^^^^^^^^^^^^^^^^
- Fixed a bug in
JSONStoragethat broke the database when removing entries.
v1.0.0 (2013-07-20)
^^^^^^^^^^^^^^^^^^^
- First official release โ consider TinyDB stable now.
Contribute (`docs/contribute.rst`)
Contribution Guidelines
#######################
Whether reporting bugs, discussing improvements and new ideas or writing
extensions: Contributions to TinyDB are welcome! Here's how to get started:
- Check for open issues or open a fresh issue to start a discussion around
a feature idea or a bug - Fork
the repository <https://github.com/msiemens/tinydb/>_ on Github,
create a new branch off themasterbranch and start making your changes
(known asGitHub Flow <https://guides.github.com/introduction/flow/index.html>_) - Write a test which shows that the bug was fixed or that the feature works
as expected - Send a pull request and bug the maintainer until it gets merged and
published :)
Philosophy of TinyDB
TinyDB aims to be simple and fun to use. Therefore two key values are simplicity
and elegance of interfaces and code. These values will contradict each other
from time to time. In these cases , try using as little magic as possible.
In any case don't forget documenting code that isn't clear at first glance.
Code Conventions
In general the TinyDB source should always follow PEP 8 <http://legacy.python.org/dev/peps/pep-0008/>_.
Exceptions are allowed in well justified and documented cases. However we make
a small exception concerning docstrings:
When using multiline docstrings, keep the opening and closing triple quotes
on their own lines and add an empty line after it.
.. code-block:: python
def some_function():
"""
Documentation ...
"""
# implementation ...Version Numbers
TinyDB follows the SemVer versioning guidelines <http://semver.org/>_.
This implies that backwards incompatible changes in the API will increment
the major version. So think twice before making such changes.
Extend (`docs/extend.rst`)
How to Extend TinyDB
There are three main ways to extend TinyDB and modify its behaviour:
- custom storages,
- custom middlewares,
- use hooks and overrides, and
- subclassing
TinyDBandTable.
Let's look at them in this order.
Write a Custom Storage
First, we have support for custom storages. By default TinyDB comes with an
in-memory storage and a JSON file storage. But of course you can add your own.
Let's look how you could add a YAML <http://yaml.org/>_ storage usingPyYAML <http://pyyaml.org/wiki/PyYAML>_:
.. code-block:: python
import yaml
class YAMLStorage(Storage):
def __init__(self, filename): # (1)
self.filename = filename
def read(self):
with open(self.filename) as handle:
try:
data = yaml.safe_load(handle.read()) # (2)
return data
except yaml.YAMLError:
return None # (3)
def write(self, data):
with open(self.filename, 'w+') as handle:
yaml.dump(data, handle)
def close(self): # (4)
passThere are some things we should look closer at:
The constructor will receive all arguments passed to TinyDB when creating
the database instance (exceptstoragewhich TinyDB itself consumes).
In other words callingTinyDB('something', storage=YAMLStorage)will
pass'something'as an argument toYAMLStorage.
If you accept callables or other executable values in your storage
constructor (or elsewhere), do not derive them from untrusted or
user-controlled input.We use
yaml.safe_loadas recommended by thePyYAML documentation <http://pyyaml.org/wiki/PyYAMLDocumentation#LoadingYAML>_
when processing data from a potentially untrusted source.If the storage is uninitialized, TinyDB expects the storage to return
Noneso it can do any internal initialization that is necessary.If your storage needs any cleanup (like closing file handles) before an
instance is destroyed, you can put it in theclose()method. To run
these, you'll either have to rundb.close()on yourTinyDBinstance
or use it as a context manager, like this:.. code-block:: python
textwith TinyDB('db.yml', storage=YAMLStorage) as db: # ...
Finally, using the YAML storage is very straight-forward:
.. code-block:: python
db = TinyDB('db.yml', storage=YAMLStorage)
# ...Write Custom Middleware
Sometimes you don't want to write a new storage module but rather modify the
behaviour of an existing one. As an example we'll build middleware that filters
out empty items.
Because middleware acts as a wrapper around a storage, they needs a read()
and a write(data) method. In addition, they can access the underlying storage
via self.storage. Before we start implementing we should look at the structure
of the data that the middleware receives. Here's what the data that goes through
the middleware looks like:
.. code-block:: python
{
'_default': {
1: {'key': 'value'},
2: {'key': 'value'},
# other items
},
# other tables
}Thus, we'll need two nested loops:
- Process every table
- Process every item
Now let's implement that:
.. code-block:: python
class RemoveEmptyItemsMiddleware(Middleware):
def __init__(self, storage_cls):
# Any middleware *has* to call the super constructor
# with storage_cls
super().__init__(storage_cls) # (1)
def read(self):
data = self.storage.read()
for table_name in data:
table_data = data[table_name]
for doc_id in table_data:
item = table_data[doc_id]
if item == {}:
del table_data[doc_id]
return data
def write(self, data):
for table_name in data:
table_data = data[table_name]
for doc_id in table_data:
item = table_data[doc_id]
if item == {}:
del table_data[doc_id]
self.storage.write(data)
def close(self):
self.storage.close()Note that the constructor calls the middleware constructor (1) and passes
the storage class to the middleware constructor.
To wrap storage with this new middleware, we use it like this:
.. code-block:: python
db = TinyDB(storage=RemoveEmptyItemsMiddleware(SomeStorageClass))Here SomeStorageClass should be replaced with the storage you want to use.
If you leave it empty, the default storage will be used (which is the JSONStorage).
Use hooks and overrides
.. _extend_hooks:
There are cases when neither creating a custom storage nor using a custom
middleware will allow you to adapt TinyDB in the way you need. In this case
you can modify TinyDB's behavior by using predefined hooks and override points.
For example you can configure the name of the default table by settingTinyDB.default_table_name:
.. code-block:: python
TinyDB.default_table_name = 'my_table_name'Both :class:~tinydb.database.TinyDB and the :class:~tinydb.table.Table
classes allow modifying their behavior using hooks and overrides. To useTable's overrides, you can access the class using TinyDB.table_class:
.. code-block:: python
TinyDB.table_class.default_query_cache_capacity = 100Read the :ref:api_docs for more details on the available hooks and override
points.
Subclassing ``TinyDB`` and ``Table``
Finally, there's the last option to modify TinyDB's behavior. That way you
can change how TinyDB itself works more deeply than using the other extension
mechanisms.
When creating a subclass you can use it by using hooks and overrides to override
the default classes that TinyDB uses:
.. code-block:: python
class MyTable(Table):
# Add your method overrides
...
TinyDB.table_class = MyTable
# Continue using TinyDB as usualTinyDB's source code is documented with extensions in mind, explaining how
everything works even for internal methods and classes. Feel free to dig into
the source and adapt everything you need for your projects.
Extensions (`docs/extensions.rst`)
Extensions
Here are some extensions that might be useful to you.
An extension is listed here if it
- works with TinyDB 4 or newer,
- supports Python 3,
- is published on PyPI, and
- has not been archived by its maintainers.
Extensions that stop meeting these requirements are removed from the list.
tinydb-rust
| Repo: https://github.com/itsmorninghao/tinydb-rust/
| Status: beta
| Description: A drop-in reimplementation of TinyDB that uses Rust for
better performance.
tinydbjoin
| Repo: https://github.com/FerdinaKusumah/tinydb-join
| Status: beta
| Description: Joins between TinyDB tables, even when they live in
different databases or storages: inner and left joins, and
nesting related documents into their parent (one-to-many
or belongs-to).
tinydbstorage
| Repo: https://github.com/FerdinaKusumah/tinydb-storage
| Status: beta
| Description: Redis and S3 storages for TinyDB, including S3-compatible
services such as MinIO and Cloudflare R2. A write that
would overwrite a change made by another process fails
instead.
aiotinydb
| Repo: https://github.com/ASMfreaK/aiotinydb
| Status: stable
| Description: asyncio compatibility shim for TinyDB. Enables usage of
TinyDB in asyncio-aware contexts without slow synchronous
IO.
BetterJSONStorage
| Repo: https://github.com/MrPigss/BetterJSONStorage
| Status: stable
| Description: BetterJSONStorage is a faster 'Storage Type' for TinyDB. It
uses the faster Orjson library for parsing the JSON and BLOSC
for compression.
tinydb-serialization
| Repo: https://github.com/msiemens/tinydb-serialization
| Status: stable
| Description: tinydb-serialization provides serialization for objects
that TinyDB otherwise couldn't handle.
tinydb-smartcache
| Repo: https://github.com/msiemens/tinydb-smartcache
| Status: stable
| Description: tinydb-smartcache provides a smart query cache for
TinyDB. It updates the query cache when
inserting/removing/updating documents so the cache doesn't
get invalidated. It's useful if you perform lots of queries
while the data changes only little.
.. _tinyrecord:
tinyrecord
| Repo: https://github.com/eugene-eeo/tinyrecord
| Status: stable
| Description: Tinyrecord is a library which implements experimental atomic
transaction support for the TinyDB NoSQL database. It uses a
record-first then execute architecture which allows us to
minimize the time that we are within a thread lock.
tinydb-appengine
| Repo: https://github.com/imalento/tinydb-appengine
| Status: inactive
| Description: tinydb-appengine provides TinyDB storage for
App Engine. You can use JSON readonly.
Getting Started (`docs/getting-started.rst`)
:tocdepth: 3
Getting Started
Installing TinyDB
To install TinyDB from PyPI, run::
$ pip install tinydbYou can also grab the latest development version from GitHub_. After downloading
and unpacking it, you can install it using::
$ pip install .Basic Usage
Let's cover the basics before going more into detail. We'll start by setting up
a TinyDB database:
from tinydb import TinyDB, Query
db = TinyDB('db.json')
You now have a TinyDB database that stores its data in db.json.
What about inserting some data? TinyDB expects the data to be Python dict\s:
db.insert({'type': 'apple', 'count': 7})
db.insert({'type': 'peach', 'count': 3})
.. note:: The insert method returns the inserted document's ID. Read more
about it here: :ref:document_ids.
Now you can get all documents stored in the database by running:
db.all()
[{'count': 7, 'type': 'apple'}, {'count': 3, 'type': 'peach'}]
You can also iter over stored documents:
for item in db:
print(item)
{'count': 7, 'type': 'apple'}
{'count': 3, 'type': 'peach'}
Of course you'll also want to search for specific documents. Let's try:
Fruit = Query()
db.search(Fruit.type == 'peach')
[{'count': 3, 'type': 'peach'}]
db.search(Fruit.count > 5)
[{'count': 7, 'type': 'apple'}]
Next we'll update the count field of the apples:
db.update({'count': 10}, Fruit.type == 'apple')
db.all()
[{'count': 10, 'type': 'apple'}, {'count': 3, 'type': 'peach'}]
In the same manner you can also remove documents:
db.remove(Fruit.count < 5)
db.all()
[{'count': 10, 'type': 'apple'}]
And of course you can throw away all data to start with an empty database:
db.truncate()
db.all()
[]
Recap
Before we dive deeper, let's recapitulate the basics:
+-------------------------------+---------------------------------------------------------------+
| Inserting |
+-------------------------------+---------------------------------------------------------------+
| db.insert(...) | Insert a document |
+-------------------------------+---------------------------------------------------------------+
| Getting data |
+-------------------------------+---------------------------------------------------------------+
| db.all() | Get all documents |
+-------------------------------+---------------------------------------------------------------+
| iter(db) | Iter over all documents |
+-------------------------------+---------------------------------------------------------------+
| db.search(query) | Get a list of documents matching the query |
+-------------------------------+---------------------------------------------------------------+
| Updating |
+-------------------------------+---------------------------------------------------------------+
| db.update(fields, query) | Update all documents matching the query to contain fields |
+-------------------------------+---------------------------------------------------------------+
| Removing |
+-------------------------------+---------------------------------------------------------------+
| db.remove(query) | Remove all documents matching the query |
+-------------------------------+---------------------------------------------------------------+
| db.truncate() | Remove all documents |
+-------------------------------+---------------------------------------------------------------+
| Querying |
+-------------------------------+---------------------------------------------------------------+
| Query() | Create a new query object |
+-------------------------------+---------------------------------------------------------------+
| Query().field == 2 | Match any document that has a key field with value |
| | == 2 (also possible: !=, >, >=, <, <=)|
+-------------------------------+---------------------------------------------------------------+
.. note::
Query comparisons only support literal values on the right-hand side.
Field-to-field comparisons like ``Query().a == Query().b`` are not
supported. Use a callable predicate like
``db.search(lambda doc: doc.get('a') == doc.get('b'))`` for custom logic... note::
Callables passed to query APIs (e.g. ``lambda`` predicates or ``Query().map``)
execute in-process and must **never** be derived from untrusted or user-controlled
input... References
.. _GitHub: http://github.com/msiemens/tinydb/
Index (`docs/index.rst`)
Welcome to TinyDB!
Welcome to TinyDB, your tiny, document oriented database optimized for your
happiness :)
from tinydb import TinyDB, Query
db = TinyDB('path/to/db.json')
User = Query()
db.insert({'name': 'John', 'age': 22})
db.search(User.name == 'John')
[{'name': 'John', 'age': 22}]
User's Guide
.. toctree::
:maxdepth: 2
intro
getting-started
usage
Extending TinyDB
.. toctree::
:maxdepth: 2
Extending TinyDB
TinyDB Extensions
API Reference
.. toctree::
:maxdepth: 2
api
Additional Notes
.. toctree::
:maxdepth: 2
contribute
changelog
Upgrade Notes
Intro (`docs/intro.rst`)
Introduction
Great that you've taken time to check out the TinyDB docs! Before we begin
looking at TinyDB itself, let's take some time to see whether you should use
TinyDB.
Why Use TinyDB?
tiny: The current source code has 1800 lines of code (with about 40%
documentation) and 1600 lines tests.document oriented: Like MongoDB_, you can store any document
(represented asdict) in TinyDB.optimized for your happiness: TinyDB is designed to be simple and
fun to use by providing a simple and clean API.written in pure Python: TinyDB neither needs an external server (as
e.g.PyMongo <https://pymongo.readthedocs.io/en/stable/>_) nor any dependencies
from PyPI.works on Python 3.5+ and PyPy: TinyDB works on all modern versions of Python
and PyPy.powerfully extensible: You can easily extend TinyDB by writing new
storages or modify the behaviour of storages with Middlewares.100% test coverage: No explanation needed.
In short: If you need a simple database with a clean API that just works
without lots of configuration, TinyDB might be the right choice for you.
Why **Not** Use TinyDB?
- You need advanced features like:
- access from multiple processes or threads (e.g. when using Flask!),
- creating indexes for tables,
- an HTTP server,
- managing relationships between tables or similar,
ACID guarantees <https://en.wikipedia.org/wiki/ACID>_.
- You are really concerned about performance and need a high speed
database.
To put it plainly: If you need advanced features or high performance, TinyDB
is the wrong database for you โ consider using databases like SQLite_, Buzhug_,
CodernityDB_ or MongoDB_.
.. References
.. _Buzhug: https://buzhug.sourceforge.net/
.. _CodernityDB: http://labs.codernity.com/codernitydb/
.. _MongoDB: https://mongodb.org/
.. _SQLite: https://www.sqlite.org/
Upgrade (`docs/upgrade.rst`)
Upgrading to Newer Releases
Version 4.0
.. _upgrade_v4_0:
- API changes:
- Replace
TinyDB.purge_tables(...)withTinyDB.drop_tables(...) - Replace
TinyDB.purge_table(...)withTinyDB.drop_table(...) - Replace
Table.purge()withTable.truncate() - Replace
TinyDB(default_table='name')withTinyDB.default_table_name = 'name' - Replace
TinyDB(table_class=Class)withTinyDB.table_class = Class - If you were using
TinyDB.DEFAULT_TABLE,TinyDB.DEFAULT_TABLE_KWARGS,
orTinyDB.DEFAULT_STORAGE: Use the new methods for customizing TinyDB
described in :ref:How to Extend TinyDB <extend_hooks>
- Replace
Version 3.0
.. _upgrade_v3_0:
Breaking API Changes
^^^^^^^^^^^^^^^^^^^^
Querying (see
Issue #62 <https://github.com/msiemens/tinydb/issues/62>_):where('...').contains('...')has been renamed towhere('...').search('...').where('foo').has('bar')is replaced by eitherwhere('foo').barorQuery().foo.bar.- In case the key is not a valid Python identifier, array
notation can be used:where('a.b.c')is nowQuery()['a.b.c'].
- In case the key is not a valid Python identifier, array
Checking for the existence of a key has to be done explicitly:
where('foo').exists().SmartCacheTablehas been moved tomsiemens/tinydb-smartcache_.Serialization has been moved to
msiemens/tinydb-serialization_.Empty storages are now expected to return
Noneinstead of raisingValueError(seeIssue #67 <https://github.com/msiemens/tinydb/issues/67>_).
.. _msiemens/tinydb-smartcache: https://github.com/msiemens/tinydb-smartcache
.. _msiemens/tinydb-serialization: https://github.com/msiemens/tinydb-serialization
.. _upgrade_v2_0:
Version 2.0
Breaking API Changes
^^^^^^^^^^^^^^^^^^^^
- The syntax
query in dbis not supported any more. Usedb.contains(...)
instead. - The
ConcurrencyMiddlewarehas been removed due to a insecure implementation
(seeIssue #18 <https://github.com/msiemens/tinydb/issues/18>_). Consider
:ref:tinyrecordinstead.
Apart from that the API remains compatible to v1.4 and prior.
For migration from v1 to v2, check out the v2.0 documentation <http://tinydb.readthedocs.io/en/v2.0/upgrade.html#upgrade-v2-0>_
Usage (`docs/usage.rst`)
:tocdepth: 3
.. toctree::
:maxdepth: 2
Advanced Usage
Remarks on Storage
Before we dive deeper into the usage of TinyDB, we should stop for a moment
and discuss how TinyDB stores data.
To convert your data to a format that is writable to disk TinyDB uses thePython JSON <http://docs.python.org/2/library/json.html>_ module by default.
It's great when only simple data types are involved but it cannot handle more
complex data types like custom classes. On Python 2 it also converts strings to
Unicode strings upon reading
(described here <http://stackoverflow.com/q/956867/997063>_).
If that causes problems, you can write
:doc:your own storage <extend>, that uses a more powerful (but also slower)
library like pickle <http://docs.python.org/library/pickle.html>_ orPyYAML <http://pyyaml.org/>_.
.. hint:: Opening multiple TinyDB instances on the same data (e.g. with the
JSONStorage) may result in unexpected behavior due to query caching.
See query_caching_ on how to disable the query cache.
Queries
With that out of the way, let's start with TinyDB's rich set of queries.
There are two main ways to construct queries. The first one resembles the
syntax of popular ORM tools:
from tinydb import Query
User = Query()
db.search(User.name == 'John')
As you can see, we first create a new Query object and then use it to specify
which fields to check. Searching for nested fields is just as easy:
db.search(User.birthday.year == 1990)
Not all fields can be accessed this way if the field name is not a valid Python
identifier. In this case, you can switch to dict access notation:
This would be invalid Python syntax:
db.search(User.country-code == 'foo')
Use this instead:
db.search(User['country-code'] == 'foo')
In addition, you can use arbitrary transform function where a field would be,
for example:
from unidecode import unidecode
db.search(User.name.map(unidecode) == 'Jose')will match 'Josรฉ' etc.
The second, traditional way of constructing queries is as follows:
from tinydb import where
db.search(where('field') == 'value')
Using where('field') is a shorthand for the following code:
db.search(Query()['field'] == 'value')
Accessing nested fields with this syntax can be achieved like this:
db.search(where('birthday').year == 1900)
db.search(where('birthday')['year'] == 1900)
Advanced queries
................
In the :doc:getting-started you've learned about the basic comparisons
(==, <, >, ...). In addition to these TinyDB supports the following
queries:
Existence of a field:
db.search(User.name.exists())
Regex:
Full item has to match the regex:
db.search(User.name.matches('[aZ]*'))
Case insensitive search for 'John':
import re
db.search(User.name.matches('John', flags=re.IGNORECASE))Any part of the item has to match the regex:
db.search(User.name.search('b+'))
Custom test:
test_func = lambda s: s == 'John'
db.search(User.name.test(test_func))
Custom test with parameters:
def test_func(val, m, n):
return m <= val <= n
db.search(User.age.test(test_func, 0, 21))
db.search(User.age.test(test_func, 21, 99))
Another case is if you have a dict where you want to find all documents
that match this dict. We call this searching for a fragment:
db.search(Query().fragment({'foo': True, 'bar': False}))
[{'foo': True, 'bar': False, 'foobar: 'yes!'}]
You also can search for documents where a specific field matches the fragment:
db.search(Query().field.fragment({'foo': True, 'bar': False}))
[{'field': {'foo': True, 'bar': False, 'foobar: 'yes!'}]
When a field contains a list, you also can use the any and all methods.
There are two ways to use them: with lists of values and with nested queries.
Let's start with the first one. Assuming we have a user object with a groups list
like this:
db.insert({'name': 'user1', 'groups': ['user']})
db.insert({'name': 'user2', 'groups': ['admin', 'user']})
db.insert({'name': 'user3', 'groups': ['sudo', 'user']})
Now we can use the following queries:
User's groups include at least one value from ['admin', 'sudo']
db.search(User.groups.any(['admin', 'sudo']))
[{'name': 'user2', 'groups': ['admin', 'user']},
{'name': 'user3', 'groups': ['sudo', 'user']}]User's groups include all values from ['admin', 'user']
db.search(User.groups.all(['admin', 'user']))
[{'name': 'user2', 'groups': ['admin', 'user']}]
In some cases you may want to have more complex any/all queries.
This is where nested queries come in as helpful. Let's set up a table like this:
Group = Query()
Permission = Query()
groups = db.table('groups')
groups.insert({
'name': 'user',
'permissions': [{'type': 'read'}]})
groups.insert({
'name': 'sudo',
'permissions': [{'type': 'read'}, {'type': 'sudo'}]})
groups.insert({
'name': 'admin',
'permissions': [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]})
Now let's search this table using nested any/all queries:
Group has a permission with type 'read'
groups.search(Group.permissions.any(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]},
{'name': 'sudo', 'permissions': [{'type': 'read'}, {'type': 'sudo'}]},
{'name': 'admin', 'permissions':
[{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]}]Group has ONLY permission 'read'
groups.search(Group.permissions.all(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]}]
As you can see, any tests if there is at least one document matching
the query while all ensures all documents match the query.
The opposite operation, checking if a single item is contained in a list,
is also possible using one_of:
db.search(User.name.one_of(['jane', 'john']))
Query modifiers
...............
TinyDB also allows you to use logical operations to modify and combine
queries:
Negate a query:
db.search(~ (User.name == 'John'))
Logical AND:
db.search((User.name == 'John') & (User.age <= 30))
Logical OR:
db.search((User.name == 'John') | (User.name == 'Bob'))
.. note::
When using ``&`` or ``|``, make sure you wrap the conditions on both sides
with parentheses or Python will mess up the comparison.
Also, when using negation (``~``) you'll have to wrap the query you want
to negate in parentheses.
The reason for these requirements is that Python's binary operators that are
used for query modifiers have a higher operator precedence than comparison
operators. Simply put, ``~ User.name == 'John'`` is parsed by Python as
``(~User.name) == 'John'`` instead of ``~(User.name == 'John')``. See also the
Python `docs on operator precedence
<https://docs.python.org/3/reference/expressions.html#operator-precedence>`_
for details.
You can compose queries dynamically by using the no-op query ``Query().noop()``.
Comparisons only support literal values on the right-hand side. Field-to-field
comparisons like ``Query().a == Query().b`` are not supported. Use a callable
predicate like ``db.search(lambda doc: doc.get('a') == doc.get('b'))`` for
custom logic... note::
Callables passed to query APIs (e.g. predicates, Query().map,
Query().test) execute in-process and must never be derived from
untrusted or user-controlled input.
Recap
.....
Let's review the query operations we've learned:
+-------------------------------------+---------------------------------------------------------------------+
| Queries |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.exists() | Match any document where a field called field exists |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.matches(regex) | Match any document with the whole field matching the |
| | regular expression |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.search(regex) | Match any document with a substring of the field matching |
| | the regular expression |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.test(func, *args) | Matches any document for which the function returns |
| | True |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.all(query | list) | If given a query, matches all documents where all documents |
| | in the list field match the query. |
| | If given a list, matches all documents where all documents |
| | in the list field are a member of the given list |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.any(query | list) | If given a query, matches all documents where at least one |
| | document in the list field match the query. |
| | If given a list, matches all documents where at least one |
| | documents in the list field are a member of the given |
| | list |
+-------------------------------------+---------------------------------------------------------------------+
| Query().field.one_of(list) | Match if the field is contained in the list |
+-------------------------------------+---------------------------------------------------------------------+
| Logical operations on queries |
+-------------------------------------+---------------------------------------------------------------------+
| ~ (query) | Match documents that don't match the query (logical NOT) |
+-------------------------------------+---------------------------------------------------------------------+
| (query1) & (query2) | Match documents that match both queries (logical AND) |
+-------------------------------------+---------------------------------------------------------------------+
| (query1) | (query2) | Match documents that match at least one of the queries (logical OR) |
+-------------------------------------+---------------------------------------------------------------------+
Handling Data
Next, let's look at some more ways to insert, update and retrieve data from
your database.
Inserting data
..............
As already described you can insert a document using db.insert(...).
In case you want to insert multiple documents, you can use db.insert_multiple(...):
db.insert_multiple([
{'name': 'John', 'age': 22},
{'name': 'John', 'age': 37}])
db.insert_multiple({'int': 1, 'value': i} for i in range(2))
Also in some cases it may be useful to specify the document ID yourself when
inserting data. You can do that by using the :class:~tinydb.table.Document
class:
db.insert(Document({'name': 'John', 'age': 22}, doc_id=12))
12
The same is possible when using db.insert_multiple(...):
db.insert_multiple([
Document({'name': 'John', 'age': 22}, doc_id=12),
Document({'name': 'Jane', 'age': 24}, doc_id=14),
])
[12, 14]
.. note::
Inserting a Document with an ID that already exists will result
in a ValueError being raised.
Updating data
.............
Sometimes you want to update all documents in your database. In this case, you
can leave out the query argument:
db.update({'foo': 'bar'})
When passing a dict to db.update(fields, query), it only allows you to
update a document by adding or overwriting its values. But sometimes you may
need to e.g. remove one field or increment its value. In that case you can
pass a function instead of fields:
from tinydb.operations import delete
db.update(delete('key1'), User.name == 'John')
This will remove the key key1 from all matching documents. TinyDB comes
with these operations:
delete(key): delete a key from the documentincrement(key): increment the value of a keydecrement(key): decrement the value of a keyadd(key, value): addvalueto the value of a key (also works for strings)subtract(key, value): subtractvaluefrom the value of a keyset(key, value): setkeytovalue
Of course you also can write your own operations:
def your_operation(your_arguments):
... def transform(doc):
... # do something with the document
... # ...
... return transform
...
db.update(your_operation(arguments), query)
In order to perform multiple update operations at once, you can use theupdate_multiple method like this:
db.update_multiple([
... ({'int': 2}, where('char') == 'a'),
... ({'int': 4}, where('char') == 'b'),
... ])
You also can mix normal updates with update operations:
db.update_multiple([
... ({'int': 2}, where('char') == 'a'),
... (delete('int'), where('char') == 'b'),
... ])
Data access and modification
Upserting data
..............
In some cases you'll need a mix of both update and insert: upsert.
This operation is provided a document and a query. If it finds any documents
matching the query, they will be updated with the data from the provided document.
On the other hand, if no matching document is found, it inserts the provided
document into the table:
db.upsert({'name': 'John', 'logged-in': True}, User.name == 'John')
This will update all users with the name John to have logged-in set to True.
If no matching user is found, a new document is inserted with both the name set
and the logged-in flag.
To use the ID of the document as matching criterion a :class:~tinydb.table.Document
with doc_id is passed instead of a query:
db.upsert(Document({'name': 'John', 'logged-in': True}, doc_id=12))
Retrieving data
...............
There are several ways to retrieve data from your database. For instance you
can get the number of stored documents:
len(db)
3
.. hint::
This will return the number of documents in the default table
(see the notes on the :ref:default table <default_table>).
Then of course you can use db.search(...) as described in the :doc:getting-started
section. But sometimes you want to get only one matching document. Instead of using
try:
... result = db.search(User.name == 'John')[0]
... except IndexError:
... pass
you can use db.get(...):
db.get(User.name == 'John')
{'name': 'John', 'age': 22}
db.get(User.name == 'Bobby')
None
.. caution::
If multiple documents match the query, the first match in insertion order
is returned (not a random document).Often you don't want to search for documents but only know whether they are
stored in the database. In this case db.contains(...) is your friend:
db.contains(User.name == 'John')
In a similar manner you can look up the number of documents matching a query:
db.count(User.name == 'John')
2
Recap
^^^^^
Let's summarize the ways to handle data:
+-------------------------------+---------------------------------------------------------------+
| Inserting data |
+-------------------------------+---------------------------------------------------------------+
| db.insert_multiple(...) | Insert multiple documents |
+-------------------------------+---------------------------------------------------------------+
| Updating data |
+-------------------------------+---------------------------------------------------------------+
| db.update(operation, ...) | Update all matching documents with a special operation |
+-------------------------------+---------------------------------------------------------------+
| Retrieving data |
+-------------------------------+---------------------------------------------------------------+
| len(db) | Get the number of documents in the database |
+-------------------------------+---------------------------------------------------------------+
| db.get(query) | Get one document matching the query |
+-------------------------------+---------------------------------------------------------------+
| db.contains(query) | Check if the database contains a matching document |
+-------------------------------+---------------------------------------------------------------+
| db.count(query) | Get the number of matching documents |
+-------------------------------+---------------------------------------------------------------+
.. note::
This was a new feature in v3.6.0.. _document_ids:
Using Document IDs
Internally TinyDB associates an ID with every document you insert. It's returned
after inserting a document:
db.insert({'name': 'John', 'age': 22})
3
db.insert_multiple([{...}, {...}, {...}])
[4, 5, 6]
In addition you can get the ID of already inserted documents usingdocument.doc_id. This works both with get and all:
el = db.get(User.name == 'John')
el.doc_id
3
el = db.all()[0]
el.doc_id
1
el = db.all()[-1]
el.doc_id
12
Different TinyDB methods also work with IDs, namely: update, remove,contains and get. The first two also return a list of affected IDs.
db.update({'value': 2}, doc_ids=[1, 2])
db.contains(doc_id=1)
True
db.remove(doc_ids=[1, 2])
db.get(doc_id=3)
{...}
db.get(doc_ids=[1, 2])
[{...}, {...}]
Using doc_id/doc_ids instead of Query() again is slightly faster
in operation.
Moving documents between databases
..................................
Documents returned by TinyDB are :class:~tinydb.table.Document instances.
They look like plain dicts, but they also carry the document ID assigned by
the table they came from. If you insert such a Document into another
table or database, TinyDB reuses that ID. When the target table already has
a document with the same ID, insertion fails with ValueError: Document with ID ... already exists.
This often shows up when moving documents from one database to another:
db1 = TinyDB('db1.json')
db2 = TinyDB('db2.json')
document = db1.get(doc_id=1)
db2.insert(document) # reuses doc_id=1 from db1
db1.remove(doc_ids=[1])
To copy the document's data and let the target table assign a new ID, convert
the Document to a plain dict first:
db2.insert(dict(document))
The same applies when moving documents between named tables in a single
database.
Recap
.....
Let's sum up the way TinyDB supports working with IDs:
+-------------------------------------+------------------------------------------------------------+
| Getting a document's ID |
+-------------------------------------+------------------------------------------------------------+
| db.insert(...) | Returns the inserted document's ID |
+-------------------------------------+------------------------------------------------------------+
| db.insert_multiple(...) | Returns the inserted documents' ID |
+-------------------------------------+------------------------------------------------------------+
| document.doc_id | Get the ID of a document fetched from the db |
+-------------------------------------+------------------------------------------------------------+
| Working with IDs |
+-------------------------------------+------------------------------------------------------------+
| db.get(doc_id=...) | Get the document with the given ID |
+-------------------------------------+------------------------------------------------------------+
| db.contains(doc_id=...) | Check if the db contains a document with the given |
| | IDs |
+-------------------------------------+------------------------------------------------------------+
| db.update({...}, doc_ids=[...]) | Update all documents with the given IDs |
+-------------------------------------+------------------------------------------------------------+
| db.remove(doc_ids=[...]) | Remove all documents with the given IDs |
+-------------------------------------+------------------------------------------------------------+
Tables
TinyDB supports working with multiple tables. They behave just the same as
the TinyDB class. To create and use a table, use db.table(name).
table = db.table('table_name')
table.insert({'value': True})
table.all()
[{'value': True}]
for row in table:
print(row)
{'value': True}
To remove a table from a database, use:
db.drop_table('table_name')
If on the other hand you want to remove all tables, use the counterpart:
db.drop_tables()
Finally, you can get a list with the names of all tables in your database:
db.tables()
{'_default', 'table_name'}
.. _default_table:
Default Table
.............
TinyDB uses a table named _default as the default table. All operations
on the database object (like db.insert(...)) operate on this table.
The name of this table can be modified by setting the default_table_name
class variable to modify the default table name for all instances:
#1: for a single instance only
db = TinyDB(storage=SomeStorage)
db.default_table_name = 'my-default'
#2: for all instances
TinyDB.default_table_name = 'my-default'
.. _query_caching:
Query Caching
.............
TinyDB caches query result for performance. That way re-running a query won't
have to read the data from the storage as long as the database hasn't been
modified. You can optimize the query cache size by passing the cache_size
to the table(...) function:
table = db.table('table_name', cache_size=30)
.. hint:: You can set cache_size to None to make the cache unlimited in
size. Also, you can set cache_size to 0 to disable it.
.. hint:: A repeated search with the same query is served from the cache.
The returned list is a fresh copy, but the documents inside it are the
same objects the cache holds โ treat them as read-only. Mutating a
returned document (a key or a nested value) corrupts what a later
identical search returns.
.. hint:: It's not possible to open the same table multiple times with different
settings. After the first invocation, all the subsequent calls will return
the same table with the same settings as the first one.
.. hint:: The TinyDB query cache doesn't check if the underlying storage
that the database uses has been modified by an external process. In this
case the query cache may return outdated results. To clear the cache and
read data from the storage again you can use db.clear_cache().
.. hint:: When using an unlimited cache size and test() queries, TinyDB
will store a reference to the test function. As a result of that behavior
long-running applications that use lambda functions as a test function
may experience memory leaks.
Storage & Middleware
Storage Types
.............
TinyDB comes with two storage types: JSON and in-memory. By
default TinyDB stores its data in JSON files so you have to specify the path
where to store it:
from tinydb import TinyDB, where
db = TinyDB('path/to/db.json')
To use the in-memory storage, use:
from tinydb.storages import MemoryStorage
db = TinyDB(storage=MemoryStorage)
.. hint::
All arguments except for the storage argument are forwarded to the
underlying storage. For the JSON storage you can use this to pass
additional keyword arguments to Python's
json.dumps(...) <https://docs.python.org/3/library/json.html#json.dumps>_
method. For example, you can set it to create prettified JSON files like
this:
>>> db = TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': ')).. note::
JSONStorage forwards **kwargs to json.dumps. Never pass
user-controlled values for callable arguments (e.g. default or
cls) as they are executed in-process on every write operation.
To modify the default storage for all TinyDB instances, set thedefault_storage_class class variable:
TinyDB.default_storage_class = MemoryStorage
In case you need to access the storage instance directly, you can use thestorage property of your TinyDB instance. This may be useful to call
method directly on the storage or middleware:
db = TinyDB(storage=CachingMiddleware(MemoryStorage))
<tinydb.middlewares.CachingMiddleware at 0x10991def0>
db.storage.flush()
Middleware
..........
Middleware wraps around existing storage allowing you to customize their
behaviour.
from tinydb.storages import JSONStorage
from tinydb.middlewares import CachingMiddleware
db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))
.. hint::
You can nest middleware:
>>> db = TinyDB('/path/to/db.json',
storage=FirstMiddleware(SecondMiddleware(JSONStorage)))CachingMiddleware
^^^^^^^^^^^^^^^^^
The CachingMiddleware improves speed by reducing disk I/O. It caches all
read operations and writes data to disk after a configured number of
write operations.
To make sure that all data is safely written when closing the table, use one
of these ways:
.. code-block:: python
# Using a context manager:
with database as db:
# Your operations.. code-block:: python
# Using the close function
db.close().. _mypy_type_checking:
MyPy Type Checking
TinyDB comes with type annotations that MyPy can use to make sure you're using
the API correctly. Unfortunately, MyPy doesn't understand all code patterns
that TinyDB uses. For that reason TinyDB ships a MyPy plugin that helps
correctly type checking code that uses TinyDB. To use it, add it to the
plugins list in the MyPy configuration file <https://mypy.readthedocs.io/en/latest/config_file.html>_
(typically located in setup.cfg or mypy.ini):
.. code-block:: ini
[mypy]
plugins = tinydb.mypy_pluginWhat's next
Congratulations, you've made through the user guide! Now go and build something
awesome or dive deeper into TinyDB with these resources:
- Want to learn how to customize TinyDB (storages, middlewares) and what
extensions exist? Check out :doc:extendand :doc:extensions. - Want to study the API in detail? Read :doc:
api. - Interested in contributing to the TinyDB development guide? Go on to the
:doc:contribute.
--- METRICS ---
- Files Extracted: 10
- Estimated Token Budget: ~20031 tokens
- Canonical Reference: https://codewiki.google/github.com/msiemens/tinydb