[
  {
    "path": ".coveragerc",
    "content": "[run]\nbranch = True\n\n[report]\nexclude_lines =\n    pragma: no cover\n    raise NotImplementedError.*\n    warnings\\.warn.*\n    def __repr__\n    def __str__\n    def main()\n    if __name__ == .__main__.:\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 30\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - bug\n  - pinned\n  - contributions-welcome\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Feel\n  free to reopen this if needed. Thank you for your contributions :heart:\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/ci-workflow.yml",
    "content": "name: Python CI\n\non:\n  push: {}\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version:\n        - \"3.10\"\n        - \"3.11\"\n        - \"3.12\"\n        - \"3.13\"\n        - \"3.14\"\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        include:\n        - python-version: \"pypy-3.9\"\n          os: ubuntu-latest\n        - python-version: \"pypy-3.10\"\n          os: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v4\n      with:\n        python-version: ${{ matrix.python-version }}\n        allow-prereleases: true\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        uv sync --group dev\n    - name: Run test suite\n      run: |\n        uv run py.test -v --cov=tinydb\n    - name: Perform type check\n      run: |\n        uv run pytest --mypy -m mypy tinydb tests\n      if: ${{ contains(matrix.python-version, '3.14') }}\n    - name: Verify dist package format\n      run: |\n        uv build\n        uv run --with twine twine check dist/*\n      if: ${{ contains(matrix.python-version, '3.14') }}\n    - name: Upload coverage result\n      if: ${{ matrix.os != 'windows-latest' }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        COVERALLS_FLAG_NAME: ${{ matrix.os }}-py${{ matrix.python-version }}\n        COVERALLS_PARALLEL: true\n      run: |\n         uv run coveralls\n\n  coveralls:\n    name: Indicate completion to coveralls.io\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n    - name: Install coveralls\n      run: pip3 install --upgrade coveralls\n    - name: Finished\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: coveralls --finish\n"
  },
  {
    "path": ".github/workflows/publish-workflow.yml",
    "content": "name: Upload Python Package\n\non:\n  push:\n    tags:\n      - v*.*.*\n\njobs:\n  publish:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python\n      uses: actions/setup-python@v2\n      with:\n        python-version: '3.x'\n    - name: Set up uv\n      uses: astral-sh/setup-uv@v5\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        uv sync --group dev\n    - name: Publish package\n      env:\n        TWINE_USERNAME: __token__\n        TWINE_PASSWORD: ${{ secrets.POETRY_PYPI_TOKEN_PYPI }}\n      run: |\n        uv build\n        uv run --with twine twine upload dist/*\n    - name: Create Release\n      uses: actions/create-release@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        tag_name: ${{ github.ref }}\n        release_name: ${{ github.ref }}\n        draft: false\n        prerelease: false\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n__pycache__\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n.pytest_cache/\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Pycharm\n.idea\n\n*.db.yml\n\n.DS_Store"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\n\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.12\"\n\nsphinx:\n  configuration: docs/conf.py\n\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - docs\n\nformats: all\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "Contribution Guidelines\n#######################\n\nWhether reporting bugs, discussing improvements and new ideas or writing\nextensions: Contributions to TinyDB are welcome! Here's how to get started:\n\n1. Check for open issues or open a fresh issue to start a discussion around\n   a feature idea or a bug\n2. Fork `the repository <https://github.com/msiemens/tinydb/>`_ on GitHub,\n   create a new branch off the `master` branch and start making your changes\n   (known as `GitHub Flow <https://guides.github.com/introduction/flow/index.html>`_)\n3. Write a test which shows that the bug was fixed or that the feature works\n   as expected\n4. Send a pull request and bug the maintainer until it gets merged and\n   published :)\n\nPhilosophy of TinyDB\n********************\n\nTinyDB aims to be simple and fun to use. Therefore two key values are simplicity\nand elegance of interfaces and code. These values will contradict each other\nfrom time to time. In these cases , try using as little magic as possible.\nIn any case don't forget documenting code that isn't clear at first glance.\n\nCode Conventions\n****************\n\nIn general the TinyDB source should always follow `PEP 8 <http://legacy.python.org/dev/peps/pep-0008/>`_.\nExceptions are allowed in well justified and documented cases. However we make\na small exception concerning docstrings:\n\nWhen using multiline docstrings, keep the opening and closing triple quotes\non their own lines and add an empty line after it.\n\n.. code-block:: python\n\n    def some_function():\n        \"\"\"\n        Documentation ...\n        \"\"\"\n\n        # implementation ...\n\nVersion Numbers\n***************\n\nTinyDB follows the `SemVer versioning guidelines <http://semver.org/>`_.\nThis implies that backwards incompatible changes in the API will increment\nthe major version. So think twice before making such changes.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2013 Markus Siemens <markus@m-siemens.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\nrecursive-include tests *.py"
  },
  {
    "path": "README.rst",
    "content": ".. image:: https://raw.githubusercontent.com/msiemens/tinydb/master/artwork/logo.png\n    :height: 150px\n\n|Build Status| |Coverage| |Version|\n\nQuick Links\n***********\n\n- `Example Code`_\n- `Supported Python Versions`_\n- `Documentation <http://tinydb.readthedocs.org/>`_\n- `Changelog <https://tinydb.readthedocs.io/en/latest/changelog.html>`_\n- `Extensions <https://tinydb.readthedocs.io/en/latest/extensions.html>`_\n- `Contributing`_\n\nIntroduction\n************\n\nTinyDB is a lightweight document oriented database optimized for your happiness :)\nIt's written in pure Python and has no external dependencies. The target are\nsmall apps that would be blown away by a SQL-DB or an external database server.\n\nTinyDB is:\n\n- **tiny:** The current source code has 1800 lines of code (with about 40%\n  documentation) and 1600 lines tests.\n\n- **document oriented:** Like MongoDB_, you can store any document\n  (represented as ``dict``) in TinyDB.\n\n- **optimized for your happiness:** TinyDB is designed to be simple and\n  fun to use by providing a simple and clean API.\n\n- **written in pure Python:** TinyDB neither needs an external server (as\n  e.g. `PyMongo <https://pymongo.readthedocs.io/en/stable/>`_) nor any dependencies\n  from PyPI.\n\n- **works on Python 3.8+ and PyPy3:** TinyDB works on all modern versions of Python\n  and PyPy.\n\n- **powerfully extensible:** You can easily extend TinyDB by writing new\n  storages or modify the behaviour of storages with Middlewares.\n\n- **100% test coverage:** No explanation needed.\n\nTo dive straight into all the details, head over to the `TinyDB docs\n<https://tinydb.readthedocs.io/>`_. You can also discuss everything related\nto TinyDB like general development, extensions or showcase your TinyDB-based\nprojects on the `discussion forum <http://forum.m-siemens.de/.>`_.\n\nSupported Python Versions\n*************************\n\nTinyDB has been tested with Python 3.8 - 3.13 and PyPy3.\n\nProject Status\n**************\n\nThis project is in maintenance mode. It has reached a mature, stable state\nwhere significant new features or architectural changes are not planned. That\nsaid, there will still be releases for bugfixes or features contributed by\nthe community. Read more about what this means in particular\n`here <https://github.com/msiemens/tinydb/discussions/572>`_.\n\nExample Code\n************\n\n.. code-block:: python\n\n    >>> from tinydb import TinyDB, Query\n    >>> db = TinyDB('/path/to/db.json')\n    >>> db.insert({'int': 1, 'char': 'a'})\n    >>> db.insert({'int': 1, 'char': 'b'})\n\nQuery Language\n==============\n\n.. code-block:: python\n\n    >>> User = Query()\n    >>> # Search for a field value\n    >>> db.search(User.name == 'John')\n    [{'name': 'John', 'age': 22}, {'name': 'John', 'age': 37}]\n\n    >>> # Combine two queries with logical and\n    >>> db.search((User.name == 'John') & (User.age <= 30))\n    [{'name': 'John', 'age': 22}]\n\n    >>> # Combine two queries with logical or\n    >>> db.search((User.name == 'John') | (User.name == 'Bob'))\n    [{'name': 'John', 'age': 22}, {'name': 'John', 'age': 37}, {'name': 'Bob', 'age': 42}]\n\n    >>> # Negate a query with logical not\n    >>> db.search(~(User.name == 'John'))\n    [{'name': 'Megan', 'age': 27}, {'name': 'Bob', 'age': 42}]\n\n    >>> # Apply transformation to field with `map`\n    >>> db.search((User.age.map(lambda x: x + x) == 44))\n    >>> [{'name': 'John', 'age': 22}]\n\n    >>> # More possible comparisons:  !=  <  >  <=  >=\n    >>> # More possible checks: where(...).matches(regex), where(...).test(your_test_func)\n\nTables\n======\n\n.. code-block:: python\n\n    >>> table = db.table('name')\n    >>> table.insert({'value': True})\n    >>> table.all()\n    [{'value': True}]\n\nUsing Middlewares\n=================\n\n.. code-block:: python\n\n    >>> from tinydb.storages import JSONStorage\n    >>> from tinydb.middlewares import CachingMiddleware\n    >>> db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))\n\n\nContributing\n************\n\nWhether reporting bugs, discussing improvements and new ideas or writing\nextensions: Contributions to TinyDB are welcome! Here's how to get started:\n\n1. Check for open issues or open a fresh issue to start a discussion around\n   a feature idea or a bug\n2. Fork `the repository <https://github.com/msiemens/tinydb/>`_ on Github,\n   create a new branch off the `master` branch and start making your changes\n   (known as `GitHub Flow <https://docs.github.com/en/get-started/using-github/github-flow>`_)\n3. Write a test which shows that the bug was fixed or that the feature works\n   as expected\n4. Send a pull request and bug the maintainer until it gets merged and\n   published ☺\n\n.. |Build Status| image:: https://img.shields.io/azure-devops/build/msiemens/3e5baa75-12ec-43ac-9728-89823ee8c7e2/2.svg?style=flat-square\n   :target: https://dev.azure.com/msiemens/github/_build?definitionId=2\n.. |Coverage| image:: http://img.shields.io/coveralls/msiemens/tinydb.svg?style=flat-square\n   :target: https://coveralls.io/r/msiemens/tinydb\n.. |Version| image:: http://img.shields.io/pypi/v/tinydb.svg?style=flat-square\n   :target: https://pypi.python.org/pypi/tinydb/\n.. _Buzhug: http://buzhug.sourceforge.net/\n.. _CodernityDB: https://github.com/perchouli/codernitydb\n.. _MongoDB: http://mongodb.org/\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version               | Supported          |\n| --------------------- | ------------------ |\n| Latest TinyDB release | :white_check_mark: |\n| All prior versions    | :x:                |\n\n## Reporting a Vulnerability\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nIf you believe you've found a security vulnerability in TinyDB, please report it by using GitHub's private vulnerability reporting feature.\n\nPlease include:\n\n- A clear description of the vulnerability\n- A realistic attack scenario demonstrating how untrusted external input leads to the security impact\n- Steps to reproduce\n- Your assessment of severity and impact\n\nI aim to respond within 7 days and will work with you on a fix and coordinated disclosure on a mutually agreed timeline if the issue is valid.\n\n## Scope: What Constitutes a TinyDB Vulnerability\n\nThis security policy applies to the TinyDB core library. Third-party extensions and plugins are not covered by this policy.\n\nFor a report to be considered a valid TinyDB vulnerability, it must demonstrate:\n\n1. **A realistic attack chain** where untrusted external data (user input, network data, file contents, etc.) causes unintended security impact through TinyDB's code\n2. **TinyDB as the root cause**, not merely a component downstream of an existing application-level vulnerability\n\n### Explicitly Out of Scope\n\nSecurity reports must demonstrate that TinyDB itself is the source of the vulnerability, not simply present in a vulnerable application.\n\nThe following are **not** considered TinyDB vulnerabilities:\n\n- **Passing malicious callables to TinyDB APIs.** TinyDB accepts callables (for queries, serialization, etc.) by design. If an attacker can inject arbitrary Python callables into your application, you already have an arbitrary code execution vulnerability unrelated to TinyDB. This is an application-level concern.\n\n- **Unsafe deserialization in application code.** If your application uses `eval()`, `pickle.loads()`, or similar on untrusted input and passes the result to TinyDB, the vulnerability is in your application's deserialization, not TinyDB.\n\n- **Local file access.** TinyDB reads and writes to local files specified by the application developer. If an attacker has filesystem access or can control file paths, this represents a broader system compromise.\n\n- **Denial of service via large data.** TinyDB is not designed for adversarial multi-tenant environments. Applications should validate data before storage. _However_, DoS issues **may** be considered in-scope if they are caused by TinyDB internals (e.g., algorithmic complexity or pathological performance triggered by small, valid inputs), rather than by unbounded application data.\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "_build/"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/TinyDB.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/TinyDB.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/TinyDB\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/TinyDB\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/_templates/links.html",
    "content": "<h3>Useful Links</h3>\n<ul>\n  <li><a href=\"https://pypi.python.org/pypi/tinydb\">TinyDB on PyPI</a></li>\n  <li><a href=\"https://github.com/msiemens/tinydb\">TinyDB on GitHub</a></li>\n  <li><a href=\"https://github.com/msiemens/tinydb/issues\">Issue Tracker</a></li>\n  <li><a href=\"https://github.com/msiemens/tinydb/discussions\">Discussion Forum</a></li>\n</ul>\n"
  },
  {
    "path": "docs/_templates/sidebarlogo.html",
    "content": "<p class=\"logo\"><a href=\"{{ pathto(master_doc) }}\">\n<img class=\"logo\" src=\"{{ pathto('_static/logo.png', 1) }}\" alt=\"Logo\"/>\n</a></p>\n"
  },
  {
    "path": "docs/_themes/.gitignore",
    "content": "*.pyc\n*.pyo\n.DS_Store\n"
  },
  {
    "path": "docs/_themes/LICENSE",
    "content": "Copyright (c) 2010 by Armin Ronacher.\n\nSome rights reserved.\n\nRedistribution and use in source and binary forms of the theme, with or\nwithout modification, are permitted provided that the following conditions\nare met:\n\n* Redistributions of source code must retain the above copyright\n  notice, this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above\n  copyright notice, this list of conditions and the following\n  disclaimer in the documentation and/or other materials provided\n  with the distribution.\n\n* The names of the contributors may not be used to endorse or\n  promote products derived from this software without specific\n  prior written permission.\n\nWe kindly ask you to only use these themes in an unmodified manner just\nfor Flask and Flask-related products, not for unrelated projects.  If you\nlike the visual style and want to use it for your own projects, please\nconsider making some larger changes to the themes (such as changing\nfont faces, sizes, colors or margins).\n\nTHIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "docs/_themes/README",
    "content": "Flask Sphinx Styles\n===================\n\nThis repository contains sphinx styles for Flask and Flask related\nprojects.  To use this style in your Sphinx documentation, follow\nthis guide:\n\n1. put this folder as _themes into your docs folder.  Alternatively\n   you can also use git submodules to check out the contents there.\n2. add this to your conf.py:\n\n   sys.path.append(os.path.abspath('_themes'))\n   html_theme_path = ['_themes']\n   html_theme = 'flask'\n\nThe following themes exist:\n\n- 'flask' - the standard flask documentation theme for large\n  projects\n- 'flask_small' - small one-page theme.  Intended to be used by\n  very small addon libraries for flask.\n\nThe following options exist for the flask_small theme:\n\n   [options]\n   index_logo = ''              filename of a picture in _static\n                                to be used as replacement for the\n                                h1 in the index.rst file.\n   index_logo_height = 120px    height of the index logo\n   github_fork = ''             repository name on github for the\n                                \"fork me\" badge\n"
  },
  {
    "path": "docs/_themes/flask/layout.html",
    "content": "{%- extends \"basic/layout.html\" %}\n\n{%- block extrahead %}\n  {{ super() }}\n  {% if theme_touch_icon %}\n  <link rel=\"apple-touch-icon\" href=\"{{ pathto('_static/' ~ theme_touch_icon, 1) }}\" />\n  <link rel=\"icon\" href=\"{{ pathto('_static/' ~ theme_touch_icon, 1) }}\" type=\"image/png\" />\n  {% endif %}\n  <link media=\"only screen and (max-width: 920px)\" href=\"{{\n    pathto('_static/small_flask.css', 1) }}\" type= \"text/css\" rel=\"stylesheet\" />\n{% endblock %}\n\n{%- block relbar2 %}{% endblock %}\n\n{% block header %}\n  {{ super() }}\n  {% if pagename == 'index' %}\n  <div class=indexwrapper>\n  {% endif %}\n{% endblock %}\n\n      {%- block footer %}\n  <div class=\"footer\">\n    &copy; Copyright {{ copyright }}.\n    Created using <a href=\"http://sphinx.pocoo.org/\">Sphinx</a>.\n  </div>\n  {% if pagename == 'index' %}\n  </div>\n  {% endif %}\n{%- endblock %}\n"
  },
  {
    "path": "docs/_themes/flask/page.html",
    "content": "{%- extends \"basic/page.html\" %}\n\n{% block body %}\n    {{ super() }}\n\n    {%- if prev or next and pagename != 'index' %}\n        <p>\n        {%- if prev %}\n          <a href=\"{{ prev.link|e }}\" title=\"{{ _('previous chapter')\n            }}\">&laquo; {{ prev.title }}</a> {% if next %}|{% endif %}\n        {%- endif %}\n        {%- if next %}\n          <a href=\"{{ next.link|e }}\" title=\"{{ _('next chapter')\n            }}\">{{ next.title }} &raquo;</a>\n        {%- endif %}\n        </p>\n    {%- endif %}\n{% endblock %}\n"
  },
  {
    "path": "docs/_themes/flask/relations.html",
    "content": "<h3>Navigation</h3>\n<ul>\n  {%- for parent in parents %}\n  <li><a href=\"{{ parent.link|e }}\">{{ parent.title }}</a><ul>\n  {%- endfor %}\n    {%- if prev %}\n      <li>Previous: <a href=\"{{ prev.link|e }}\" title=\"{{ _('previous chapter')\n        }}\">{{ prev.title }}</a></li>\n    {%- endif %}\n    {%- if next %}\n      <li>Next: <a href=\"{{ next.link|e }}\" title=\"{{ _('next chapter')\n        }}\">{{ next.title }}</a></li>\n    {%- endif %}\n  {%- for parent in parents %}\n  </ul></li>\n  {%- endfor %}\n</ul>\n"
  },
  {
    "path": "docs/_themes/flask/static/flasky.css_t",
    "content": "/*\n * flasky.css_t\n * ~~~~~~~~~~~~\n *\n * :copyright: Copyright 2010 by Armin Ronacher.\n * :license: Flask Design License, see LICENSE for details.\n */\n\n{% set page_width = '940px' %}\n{% set sidebar_width = '220px' %}\n{% set font_family = \"'Open Sans', sans-serif\" %}\n{% set monospace_font_family = \"'Source Code Pro', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace\" %}\n{% set accent_color = '#2d4e84' %}{# original: #004B6B #}\n{% set accent_color_alternate = '#2069e1' %}{# original: #6D4100 #}\n\n@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic|Source+Code+Pro);\n@import url(\"basic.css\");\n\n/* -- page layout ----------------------------------------------------------- */\n\nhtml {\n    overflow-y: scroll;\n}\n\nbody {\n    font-family: {{ font_family }};\n    font-size: 17px;\n    background-color: white;\n    color: #000;\n    margin: 0;\n    padding: 0;\n}\n\ndiv.document {\n    width: {{ page_width }};\n    margin: 30px auto 0 auto;\n}\n\ndiv.documentwrapper {\n    float: left;\n    width: 100%;\n}\n\ndiv.bodywrapper {\n    margin: 0 0 0 {{ sidebar_width }};\n}\n\ndiv.sphinxsidebar {\n    width: {{ sidebar_width }};\n}\n\nhr {\n    border: 1px solid #B1B4B6;\n}\n\ndiv.body {\n    background-color: #ffffff;\n    color: #3E4349;\n    padding: 0 30px 0 30px;\n}\n\nimg.floatingflask {\n    padding: 0 0 10px 10px;\n    float: right;\n}\n\ndiv.footer {\n    width: {{ page_width }};\n    margin: 20px auto 30px auto;\n    font-size: 14px;\n    color: #888;\n    text-align: right;\n}\n\ndiv.footer a {\n    color: #888;\n}\n\ndiv.related {\n    display: none;\n}\n\ndiv.sphinxsidebar a {\n    color: #444;\n    text-decoration: none;\n    border-bottom: 1px dotted #999;\n}\n\ndiv.sphinxsidebar a:hover {\n    border-bottom: 1px solid #999;\n}\n\ndiv.sphinxsidebar {\n    font-size: 14px;\n    line-height: 1.5;\n}\n\ndiv.sphinxsidebarwrapper {\n    padding: 18px 10px;\n}\n\ndiv.sphinxsidebarwrapper p.logo {\n    padding: 0 0 20px 0;\n    margin: 0;\n    text-align: center;\n}\n\ndiv.sphinxsidebar h3,\ndiv.sphinxsidebar h4 {\n    font-family: {{ font_family }};\n    color: #444;\n    font-size: 24px;\n    font-weight: normal;\n    margin: 0 0 5px 0;\n    padding: 0;\n}\n\ndiv.sphinxsidebar h4 {\n    font-size: 20px;\n}\n\ndiv.sphinxsidebar h3 a {\n    color: #444;\n}\n\ndiv.sphinxsidebar p.logo a,\ndiv.sphinxsidebar h3 a,\ndiv.sphinxsidebar p.logo a:hover,\ndiv.sphinxsidebar h3 a:hover {\n    border: none;\n}\n\ndiv.sphinxsidebar p {\n    color: #555;\n    margin: 10px 0;\n}\n\ndiv.sphinxsidebar ul {\n    margin: 10px 0;\n    padding: 0;\n    color: #000;\n}\n\ndiv.sphinxsidebar input {\n    border: 1px solid #ccc;\n    font-family: {{ font_family }};\n    font-size: 1em;\n}\n\n/* -- body styles ----------------------------------------------------------- */\n\na {\n    color: {{ accent_color }};\n    text-decoration: underline;\n}\n\na:hover {\n    color: {{ accent_color_alternate }};\n    text-decoration: underline;\n}\n\ndiv.body h1,\ndiv.body h2,\ndiv.body h3,\ndiv.body h4,\ndiv.body h5,\ndiv.body h6 {\n    font-family: {{ font_family }};\n    font-weight: normal;\n    margin: 30px 0px 10px 0px;\n    padding: 0;\n}\n\n{% if theme_index_logo %}\ndiv.indexwrapper h1 {\n    text-indent: -999999px;\n    background: url({{ theme_index_logo }}) no-repeat center center;\n    height: {{ theme_index_logo_height }};\n}\n{% endif %}\ndiv.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }\ndiv.body h2 { font-size: 180%; }\ndiv.body h3 { font-size: 150%; }\ndiv.body h4 { font-size: 130%; }\ndiv.body h5 { font-size: 100%; }\ndiv.body h6 { font-size: 100%; }\n\na.headerlink {\n    color: #ddd;\n    padding: 0 4px;\n    text-decoration: none;\n}\n\na.headerlink:hover {\n    color: #444;\n    background: #eaeaea;\n}\n\ndiv.body p, div.body dd, div.body li {\n    line-height: 1.4em;\n}\n\ndiv.admonition {\n    background: #fafafa;\n    margin: 20px -30px;\n    padding: 10px 30px;\n    border-top: 1px solid #ccc;\n    border-bottom: 1px solid #ccc;\n}\n\ndiv.admonition tt.xref, div.admonition a tt {\n    border-bottom: 1px solid #fafafa;\n}\n\ndd div.admonition {\n    margin-left: -60px;\n    padding-left: 60px;\n}\n\ndiv.admonition p.admonition-title {\n    font-family: {{ font_family }};\n    font-weight: normal;\n    font-size: 24px;\n    margin: 0 0 10px 0;\n    padding: 0;\n    line-height: 1;\n}\n\ndiv.admonition p.last {\n    margin-bottom: 0;\n}\n\ndiv.highlight {\n    background-color: white;\n}\n\ndt:target, .highlight {\n    background: #FAF3E8;\n}\n\ndiv.note {\n    background-color: #eee;\n    border: 1px solid #ccc;\n}\n\ndiv.seealso {\n    background-color: #ffc;\n    border: 1px solid #ff6;\n}\n\ndiv.topic {\n    background-color: #eee;\n}\n\np.admonition-title {\n    display: inline;\n}\n\np.admonition-title:after {\n    content: \":\";\n}\n\npre, tt {\n    font-family: {{ monospace_font_family }};\n    font-size: 0.9em;\n}\n\nimg.screenshot {\n}\n\ntt.descname, tt.descclassname {\n    font-size: 0.95em;\n}\n\ntt.descname {\n    padding-right: 0.08em;\n}\n\nimg.screenshot {\n    -moz-box-shadow: 2px 2px 4px #eee;\n    -webkit-box-shadow: 2px 2px 4px #eee;\n    box-shadow: 2px 2px 4px #eee;\n}\n\ntable.docutils {\n    border: 1px solid #888;\n    -moz-box-shadow: 2px 2px 4px #eee;\n    -webkit-box-shadow: 2px 2px 4px #eee;\n    box-shadow: 2px 2px 4px #eee;\n}\n\ntable.docutils td, table.docutils th {\n    border: 1px solid #888;\n    padding: 0.25em 0.7em;\n}\n\ntable.field-list, table.footnote {\n    border: none;\n    -moz-box-shadow: none;\n    -webkit-box-shadow: none;\n    box-shadow: none;\n}\n\ntable.footnote {\n    margin: 15px 0;\n    width: 100%;\n    border: 1px solid #eee;\n    background: #fdfdfd;\n    font-size: 0.9em;\n}\n\ntable.footnote + table.footnote {\n    margin-top: -15px;\n    border-top: none;\n}\n\ntable.field-list th {\n    padding: 0 0.8em 0 0;\n}\n\ntable.field-list td {\n    padding: 0;\n}\n\ntable.footnote td.label {\n    width: 0px;\n    padding: 0.3em 0 0.3em 0.5em;\n}\n\ntable.footnote td {\n    padding: 0.3em 0.5em;\n}\n\ndl {\n    margin: 0;\n    padding: 0;\n}\n\ndl dd {\n    margin-left: 30px;\n}\n\nblockquote {\n    margin: 0 0 0 30px;\n    padding: 0;\n}\n\nul, ol {\n    margin: 10px 0 10px 30px;\n    padding: 0;\n}\n\npre {\n    background: #eee;\n    padding: 7px 30px;\n    margin: 15px -30px;\n    line-height: 1.3em;\n}\n\ndl pre, blockquote pre, li pre {\n    margin-left: -60px;\n    padding-left: 60px;\n}\n\ndl dl pre {\n    margin-left: -90px;\n    padding-left: 90px;\n}\n\ntt {\n    background-color: #ecf0f3;\n    color: #222;\n    /* padding: 1px 2px; */\n}\n\ntt.xref, a tt {\n    background-color: #FBFBFB;\n    border-bottom: 1px solid white;\n}\n\na.reference {\n    text-decoration: none;\n    border-bottom: 1px dotted {{ accent_color }};\n}\n\na.reference:hover {\n    border-bottom: 1px solid {{ accent_color_alternate }};\n}\n\na.footnote-reference {\n    text-decoration: none;\n    font-size: 0.7em;\n    vertical-align: top;\n    border-bottom: 1px dotted {{ accent_color }};\n}\n\na.footnote-reference:hover {\n    border-bottom: 1px solid {{ accent_color_alternate }};\n}\n\na:hover tt {\n    background: #EEE;\n}\n\n\n@media screen and (max-width: 870px) {\n\n    div.sphinxsidebar {\n        display: none;\n    }\n\n    div.document {\n       width: 100%;\n\n    }\n\n    div.documentwrapper {\n        margin-left: 0;\n        margin-top: 0;\n        margin-right: 0;\n        margin-bottom: 0;\n    }\n\n    div.bodywrapper {\n        margin-top: 0;\n        margin-right: 0;\n        margin-bottom: 0;\n        margin-left: 0;\n    }\n\n    ul {\n        margin-left: 0;\n    }\n\n    .document {\n        width: auto;\n    }\n\n    .footer {\n        width: auto;\n    }\n\n    .bodywrapper {\n        margin: 0;\n    }\n\n    .footer {\n        width: auto;\n    }\n\n    .github {\n        display: none;\n    }\n\n\n\n}\n\n\n\n@media screen and (max-width: 875px) {\n\n    body {\n        margin: 0;\n        padding: 20px 30px;\n    }\n\n    div.documentwrapper {\n        float: none;\n        background: white;\n    }\n\n    div.sphinxsidebar {\n        display: block;\n        float: none;\n        width: 102.5%;\n        margin: 50px -30px -20px -30px;\n        padding: 10px 20px;\n        background: #333;\n        color: white;\n    }\n\n    div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,\n    div.sphinxsidebar h3 a {\n        color: white;\n    }\n\n    div.sphinxsidebar a {\n        color: #aaa;\n    }\n\n    div.sphinxsidebar p.logo {\n        display: none;\n    }\n\n    div.document {\n        width: 100%;\n        margin: 0;\n    }\n\n    div.related {\n        display: block;\n        margin: 0;\n        padding: 10px 0 20px 0;\n    }\n\n    div.related ul,\n    div.related ul li {\n        margin: 0;\n        padding: 0;\n    }\n\n    div.footer {\n        display: none;\n    }\n\n    div.bodywrapper {\n        margin: 0;\n    }\n\n    div.body {\n        min-height: 0;\n        padding: 0;\n    }\n\n    .rtd_doc_footer {\n        display: none;\n    }\n\n    .document {\n        width: auto;\n    }\n\n    .footer {\n        width: auto;\n    }\n\n    .footer {\n        width: auto;\n    }\n\n    .github {\n        display: none;\n    }\n}\n\n\n/* scrollbars */\n\n::-webkit-scrollbar {\n    width: 6px;\n    height: 6px;\n}\n\n::-webkit-scrollbar-button:start:decrement,\n::-webkit-scrollbar-button:end:increment {\n    display: block;\n    height: 10px;\n}\n\n::-webkit-scrollbar-button:vertical:increment {\n    background-color: #fff;\n}\n\n::-webkit-scrollbar-track-piece {\n    background-color: #eee;\n    -webkit-border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:vertical {\n    height: 50px;\n    background-color: #ccc;\n    -webkit-border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:horizontal {\n    width: 50px;\n    background-color: #ccc;\n    -webkit-border-radius: 3px;\n}\n\n/* misc. */\n\n.revsys-inline {\n    display: none!important;\n}\n\n\n.admonition.warning {\n    background-color: #F5CDCD;\n    border-color: #7B1B1B;\n}\n"
  },
  {
    "path": "docs/_themes/flask/theme.conf",
    "content": "[theme]\ninherit = basic\nstylesheet = flasky.css\npygments_style = flask_theme_support.FlaskyStyle\n"
  },
  {
    "path": "docs/_themes/flask_theme_support.py",
    "content": "# flasky extensions.  flasky pygments style based on tango style\nfrom pygments.style import Style\nfrom pygments.token import Keyword, Name, Comment, String, Error, \\\n    Number, Operator, Generic, Whitespace, Punctuation, Other, Literal\n\n\nclass FlaskyStyle(Style):\n    background_color = \"#f8f8f8\"\n    default_style = \"\"\n\n    styles = {\n        # No corresponding class for the following:\n        # Text:                     \"\", # class:  ''\n        Whitespace: \"underline #f8f8f8\",  # class: 'w'\n        Error: \"#a40000 border:#ef2929\",  # class: 'err'\n        Other: \"#000000\",  # class 'x'\n\n        Comment: \"italic #8f5902\",  # class: 'c'\n        Comment.Preproc: \"noitalic\",  # class: 'cp'\n\n        Keyword: \"bold #004461\",  # class: 'k'\n        Keyword.Constant: \"bold #004461\",  # class: 'kc'\n        Keyword.Declaration: \"bold #004461\",  # class: 'kd'\n        Keyword.Namespace: \"bold #004461\",  # class: 'kn'\n        Keyword.Pseudo: \"bold #004461\",  # class: 'kp'\n        Keyword.Reserved: \"bold #004461\",  # class: 'kr'\n        Keyword.Type: \"bold #004461\",  # class: 'kt'\n\n        Operator: \"#582800\",  # class: 'o'\n        Operator.Word: \"bold #004461\",  # class: 'ow' - like keywords\n\n        Punctuation: \"bold #000000\",  # class: 'p'\n\n        # because special names such as Name.Class, Name.Function, etc.\n        # are not recognized as such later in the parsing, we choose them\n        # to look the same as ordinary variables.\n        Name: \"#000000\",  # class: 'n'\n        Name.Attribute: \"#c4a000\",  # class: 'na' - to be revised\n        Name.Builtin: \"#004461\",  # class: 'nb'\n        Name.Builtin.Pseudo: \"#3465a4\",  # class: 'bp'\n        Name.Class: \"#000000\",  # class: 'nc' - to be revised\n        Name.Constant: \"#000000\",  # class: 'no' - to be revised\n        Name.Decorator: \"#888\",  # class: 'nd' - to be revised\n        Name.Entity: \"#ce5c00\",  # class: 'ni'\n        Name.Exception: \"bold #cc0000\",  # class: 'ne'\n        Name.Function: \"#000000\",  # class: 'nf'\n        Name.Property: \"#000000\",  # class: 'py'\n        Name.Label: \"#f57900\",  # class: 'nl'\n        Name.Namespace: \"#000000\",  # class: 'nn' - to be revised\n        Name.Other: \"#000000\",  # class: 'nx'\n        Name.Tag: \"bold #004461\",  # class: 'nt' - like a keyword\n        Name.Variable: \"#000000\",  # class: 'nv' - to be revised\n        Name.Variable.Class: \"#000000\",  # class: 'vc' - to be revised\n        Name.Variable.Global: \"#000000\",  # class: 'vg' - to be revised\n        Name.Variable.Instance: \"#000000\",  # class: 'vi' - to be revised\n\n        Number: \"#990000\",  # class: 'm'\n\n        Literal: \"#000000\",  # class: 'l'\n        Literal.Date: \"#000000\",  # class: 'ld'\n\n        String: \"#4e9a06\",  # class: 's'\n        String.Backtick: \"#4e9a06\",  # class: 'sb'\n        String.Char: \"#4e9a06\",  # class: 'sc'\n        String.Doc: \"italic #8f5902\",  # class: 'sd' - like a comment\n        String.Double: \"#4e9a06\",  # class: 's2'\n        String.Escape: \"#4e9a06\",  # class: 'se'\n        String.Heredoc: \"#4e9a06\",  # class: 'sh'\n        String.Interpol: \"#4e9a06\",  # class: 'si'\n        String.Other: \"#4e9a06\",  # class: 'sx'\n        String.Regex: \"#4e9a06\",  # class: 'sr'\n        String.Single: \"#4e9a06\",  # class: 's1'\n        String.Symbol: \"#4e9a06\",  # class: 'ss'\n\n        Generic: \"#000000\",  # class: 'g'\n        Generic.Deleted: \"#a40000\",  # class: 'gd'\n        Generic.Emph: \"italic #000000\",  # class: 'ge'\n        Generic.Error: \"#ef2929\",  # class: 'gr'\n        Generic.Heading: \"bold #000080\",  # class: 'gh'\n        Generic.Inserted: \"#00A000\",  # class: 'gi'\n        Generic.Output: \"#888\",  # class: 'go'\n        Generic.Prompt: \"#745334\",  # class: 'gp'\n        Generic.Strong: \"bold #000000\",  # class: 'gs'\n        Generic.Subheading: \"bold #800080\",  # class: 'gu'\n        Generic.Traceback: \"bold #a40000\",  # class: 'gt'\n    }\n"
  },
  {
    "path": "docs/api.rst",
    "content": ".. _api_docs:\n\nAPI Documentation\n=================\n\n``tinydb.database``\n-------------------\n\n.. autoclass:: tinydb.database.TinyDB\n    :members:\n    :private-members:\n    :member-order: bysource\n\n.. _table_api:\n\n``tinydb.table``\n----------------\n\n.. autoclass:: tinydb.table.Table\n    :members:\n    :special-members:\n    :exclude-members: __dict__, __weakref__\n    :member-order: bysource\n\n.. autoclass:: tinydb.table.Document\n    :members:\n    :special-members:\n    :exclude-members: __dict__, __weakref__\n    :member-order: bysource\n\n    .. py:attribute:: doc_id\n\n        The document's id\n\n``tinydb.queries``\n------------------\n\n.. autoclass:: tinydb.queries.Query\n    :members:\n    :special-members:\n    :exclude-members: __weakref__\n    :member-order: bysource\n\n.. autoclass:: tinydb.queries.QueryInstance\n    :members:\n    :special-members:\n    :exclude-members: __weakref__\n    :member-order: bysource\n\n``tinydb.operations``\n---------------------\n\n.. automodule:: tinydb.operations\n    :members:\n    :special-members:\n    :exclude-members: __weakref__\n    :member-order: bysource\n\n``tinydb.storage``\n------------------\n\n.. automodule:: tinydb.storages\n    :members: JSONStorage, MemoryStorage\n    :special-members:\n    :exclude-members: __weakref__\n\n    .. class:: Storage\n\n        The abstract base class for all Storages.\n\n        A Storage (de)serializes the current state of the database and stores\n        it in some place (memory, file on disk, ...).\n\n        .. method:: read()\n\n            Read the last stored state.\n\n        .. method:: write(data)\n\n            Write the current state of the database to the storage.\n\n        .. method:: close()\n\n            Optional: Close open file handles, etc.\n\n``tinydb.middlewares``\n----------------------\n\n.. automodule:: tinydb.middlewares\n    :members: CachingMiddleware\n    :special-members:\n    :exclude-members: __weakref__\n\n    .. class:: Middleware\n\n        The base class for all Middlewares.\n\n        Middlewares hook into the read/write process of TinyDB allowing you to\n        extend the behaviour by adding caching, logging, ...\n\n        If ``read()`` or ``write()`` are not overloaded, they will be forwarded\n        directly to the storage instance.\n\n        .. attribute:: storage\n\n            :type: :class:`.Storage`\n\n            Access to the underlying storage instance.\n\n        .. method:: read()\n\n            Read the last stored state.\n\n        .. method:: write(data)\n\n            Write the current state of the database to the storage.\n\n        .. method:: close()\n\n            Optional: Close open file handles, etc.\n\n``tinydb.utils``\n----------------\n\n.. autoclass:: tinydb.utils.LRUCache\n    :members:\n    :special-members:\n"
  },
  {
    "path": "docs/changelog.rst",
    "content": "Changelog\n=========\n\nVersion Numbering\n^^^^^^^^^^^^^^^^^\n\nTinyDB follows the SemVer versioning guidelines. For more information,\nsee `semver.org <http://semver.org/>`_\n\n.. note:: When new methods are added to the ``Query`` API, this may\n          result in breaking existing code that uses the property syntax\n          to access document fields (e.g. ``Query().some.nested.field``)\n          where the field name is equal to the newly added query method.\n          Thus, breaking changes may occur in feature releases even though\n          they don't change the public API in a backwards-incompatible\n          manner.\n\n          To prevent this from happening, one can use the dict access\n          syntax (``Query()['some']['nested']['field']``) that will\n          not break even when new methods are added to the ``Query`` API.\n\nunreleased\n^^^^^^^^^^\n\n- *nothing yet*\n\nv4.8.2 (2024-10-12)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix: Correctly update query cache when search results have changed\n  (see `issue 560 <https://github.com/msiemens/tinydb/issues/560>`_).\n\nv4.8.1 (2024-10-07)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Allow persisting empty tables\n  (see `pull request 518 <https://github.com/msiemens/tinydb/pull/518>`_).\n- Fix: Make replacing ``doc_id`` type work properly\n  (see `issue 545 <https://github.com/msiemens/tinydb/issues/545>`_).\n\nv4.8.0 (2023-06-12)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Allow retrieve multiple documents by document ID using\n  ``Table.get(doc_ids=[...])``\n  (see `pull request 504 <https://github.com/msiemens/tinydb/pull/504>`_).\n\nv4.7.1 (2023-01-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Improvement: Improve typing annotations\n  (see `pull request 477 <https://github.com/msiemens/tinydb/pull/477>`_).\n- Improvement: Fix some typos in the documentation\n  (see `pull request 479 <https://github.com/msiemens/tinydb/pull/479>`_\n  and `pull request 498 <https://github.com/msiemens/tinydb/pull/498>`_).\n\nv4.7.0 (2022-02-19)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Allow inserting ``Document`` instances using ``Table.insert_multiple``\n  (see `pull request 455 <https://github.com/msiemens/tinydb/pull/455>`_).\n- Performance: Only convert document IDs of a table when returning documents.\n  This improves performance the ``Table.count`` and ``Table.get`` operations\n  and also for ``Table.search`` when only returning a few documents\n  (see `pull request 460 <https://github.com/msiemens/tinydb/pull/460>`_).\n- Internal change: Run all ``Table`` tests ``JSONStorage`` in addition to\n  ``MemoryStorage``.\n\nv4.6.1 (2022-01-18)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix: Make using callables as queries work again\n  (see `issue 454 <https://github.com/msiemens/tinydb/issues/454>`__)\n\nv4.6.0 (2022-01-17)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add `map()` query operation to apply a transformation\n  to a document or field when evaluating a query\n  (see `pull request 445 <https://github.com/msiemens/tinydb/pull/445>`_).\n  **Note**: This may break code that queries for a field named ``map``\n  using the ``Query`` APIs property access syntax\n- Feature: Add support for `typing-extensions <https://pypi.org/project/typing-extensions/>`_\n  v4\n- Documentation: Fix a couple of typos in the documentation (see\n  `pull request 446 <https://github.com/msiemens/tinydb/pull/446>`_,\n  `pull request 449 <https://github.com/msiemens/tinydb/pull/449>`_ and\n  `pull request 453 <https://github.com/msiemens/tinydb/pull/453>`_)\n\nv4.5.2 (2021-09-23)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix: Make ``Table.delete()``'s argument priorities consistent with\n  other table methods. This means that if you pass both ``cond`` as\n  well as ``doc_ids`` to ``Table.delete()``, the latter will be preferred\n  (see `issue 424 <https://github.com/msiemens/tinydb/issues/424>`__)\n\nv4.5.1 (2021-07-17)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix: Correctly install ``typing-extensions`` on Python 3.7\n  (see `issue 413 <https://github.com/msiemens/tinydb/issues/413>`__)\n\nv4.5.0 (2021-06-25)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Better type hinting/IntelliSense for PyCharm, VS Code and MyPy\n  (see `issue 372 <https://github.com/msiemens/tinydb/issues/372>`__).\n  PyCharm and VS Code should work out of the box, for MyPy see\n  :ref:`MyPy Type Checking <mypy_type_checking>`\n\nv4.4.0 (2021-02-11)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add operation for searching for all documents that match a ``dict``\n  fragment (see `issue 300 <https://github.com/msiemens/tinydb/issues/300>`_)\n- Fix: Correctly handle queries that use fields that are also Query methods,\n  e.g. ``Query()['test']`` for searching for documents with a ``test`` field\n  (see `issue 373 <https://github.com/msiemens/tinydb/issues/373>`_)\n\nv4.3.0 (2020-11-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add operation for updating multiple documents: ``update_multiple``\n  (see `issue 346 <https://github.com/msiemens/tinydb/issues/346>`_)\n- Improvement: Expose type information for MyPy typechecking (PEP 561)\n  (see `pull request 352 <https://github.com/msiemens/tinydb/pull/352>`_)\n\nv4.2.0 (2020-10-03)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add support for specifying document IDs during insertion\n  (see `issue 303 <https://github.com/msiemens/tinydb/issues/303>`_)\n- Internal change: Use ``OrderedDict.move_to_end()`` in the query cache\n  (see `issue 338 <https://github.com/msiemens/tinydb/issues/338>`_)\n\nv4.1.1 (2020-05-08)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix: Don't install dev-dependencies when installing from PyPI (see\n  `issue 315 <https://github.com/msiemens/tinydb/issues/315>`_)\n\nv4.1.0 (2020-05-07)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add a no-op query ``Query().noop()`` (see\n  `issue 313 <https://github.com/msiemens/tinydb/issues/313>`_)\n- Feature: Add a ``access_mode`` flag to ``JSONStorage`` to allow opening\n  files read-only (see `issue 297 <https://github.com/msiemens/tinydb/issues/297>`_)\n- Fix: Don't drop the first document that's being inserted when inserting\n  data on an existing database (see `issue 314\n  <https://github.com/msiemens/tinydb/issues/314>`_)\n\nv4.0.0 (2020-05-02)\n^^^^^^^^^^^^^^^^^^^\n\n:ref:`Upgrade Notes <upgrade_v4_0>`\n\nBreaking Changes\n----------------\n\n- Python 2 support has been removed, see `issue 284\n  <https://github.com/msiemens/tinydb/issues/284>`_\n  for background\n- API changes:\n\n    - Removed classes: ``DataProxy``, ``StorageProxy``\n    - Attributes removed from ``TinyDB`` in favor of\n      customizing ``TinyDB``'s behavior by subclassing it and overloading\n      ``__init__(...)`` and ``table(...)``:\n\n        - ``DEFAULT_TABLE``\n        - ``DEFAULT_TABLE_KWARGS``\n        - ``DEFAULT_STORAGE``\n\n    - Arguments removed from ``TinyDB(...)``:\n\n        - ``default_table``: replace with ``TinyDB.default_table_name = 'name'``\n        - ``table_class``: replace with ``TinyDB.table_class = Class``\n\n    - ``TinyDB.contains(...)``'s ``doc_ids`` parameter has been renamed to\n      ``doc_id`` and now only takes a single document ID\n    - ``TinyDB.purge_tables(...)`` has been renamed to ``TinyDB.drop_tables(...)``\n    - ``TinyDB.purge_table(...)`` has been renamed to ``TinyDB.drop_table(...)``\n    - ``TinyDB.write_back(...)`` has been removed\n    - ``TinyDB.process_elements(...)`` has been removed\n    - ``Table.purge()`` has been renamed to ``Table.truncate()``\n    - Evaluating an empty ``Query()`` without any test operators will now result\n      in an exception, use ``Query().noop()`` (introduced in v4.1.0) instead\n\n- ``ujson`` support has been removed, see `issue 263\n  <https://github.com/msiemens/tinydb/issues/263>`_ and `issue 306\n  <https://github.com/msiemens/tinydb/issues/306>`_ for background\n- The deprecated Element ID API has been removed (e.g. using the ``Element``\n  class or ``eids`` parameter) in favor the Document API, see\n  `pull request 158 <https://github.com/msiemens/tinydb/pull/158>`_ for details\n  on the replacement\n\nImprovements\n------------\n\n- TinyDB's internal architecture has been reworked to be more simple and\n  streamlined in order to make it easier to customize TinyDB's behavior\n- With the new architecture, TinyDB performance will improve for many\n  applications\n\nBugfixes\n--------\n\n- Don't break the tests when ``ujson`` is installed (see `issue 262\n  <https://github.com/msiemens/tinydb/issues/262>`_)\n- Fix performance when reading data (see `issue 250\n  <https://github.com/msiemens/tinydb/issues/250>`_)\n- Fix inconsistent purge function names (see `issue 103\n  <https://github.com/msiemens/tinydb/issues/103>`_)\n\nv3.15.1 (2019-10-26)\n^^^^^^^^^^^^^^^^^^^^\n\n- Internal change: fix missing values handling for ``LRUCache``\n\nv3.15.0 (2019-10-12)\n^^^^^^^^^^^^^^^^^^^^\n\n- Feature: allow setting the parameters of TinyDB's default table\n  (see `issue 278 <https://github.com/msiemens/tinydb/issues/278>`_)\n\nv3.14.2 (2019-09-13)\n^^^^^^^^^^^^^^^^^^^^\n\n- Internal change: support correct iteration for ``LRUCache`` objects\n\nv3.14.1 (2019-07-03)\n^^^^^^^^^^^^^^^^^^^^\n\n- Internal change: fix Query class to permit subclass creation\n  (see `pull request 270 <https://github.com/msiemens/tinydb/pull/270>`_)\n\nv3.14.0 (2019-06-18)\n^^^^^^^^^^^^^^^^^^^^\n\n- Change: support for ``ujson`` is now deprecated\n  (see `issue 263 <https://github.com/msiemens/tinydb/issues/263>`_)\n\nv3.13.0 (2019-03-16)\n^^^^^^^^^^^^^^^^^^^^\n\n- Feature: direct access to a TinyDB instance's storage\n  (see `issue 258 <https://github.com/msiemens/tinydb/issues/258>`_)\n\nv3.12.2 (2018-12-12)\n^^^^^^^^^^^^^^^^^^^^\n\n- Internal change: convert documents to dicts during insertion\n  (see `pull request 256 <https://github.com/msiemens/tinydb/pull/256>`_)\n- Internal change: use tuple literals instead of tuple class/constructor\n  (see `pull request 247 <https://github.com/msiemens/tinydb/pull/247>`_)\n- Infra: ensure YAML tests are run\n  (see `pull request 252 <https://github.com/msiemens/tinydb/pull/252>`_)\n\nv3.12.1 (2018-11-09)\n^^^^^^^^^^^^^^^^^^^^\n\n- Fix: Don't break when searching the same query multiple times\n  (see `pull request 249 <https://github.com/msiemens/tinydb/pull/249>`_)\n- Internal change: allow ``collections.abc.Mutable`` as valid document types\n  (see `pull request 245 <https://github.com/msiemens/tinydb/pull/245>`_)\n\nv3.12.0 (2018-11-06)\n^^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add encoding option to ``JSONStorage``\n  (see `pull request 238 <https://github.com/msiemens/tinydb/pull/238>`_)\n- Internal change: allow ``collections.abc.Mutable`` as valid document types\n  (see `pull request 245 <https://github.com/msiemens/tinydb/pull/245>`_)\n\nv3.11.1 (2018-09-13)\n^^^^^^^^^^^^^^^^^^^^\n\n- Bugfix: Make path queries (``db.search(where('key))``) work again\n  (see `issue 232 <https://github.com/msiemens/tinydb/issues/232>`_)\n- Improvement: Add custom ``repr`` representations for main classes\n  (see `pull request 229 <https://github.com/msiemens/tinydb/pull/229>`_)\n\nv3.11.0 (2018-08-20)\n^^^^^^^^^^^^^^^^^^^^\n\n- **Drop official support for Python 3.3**. Python 3.3 has reached its\n  official End Of Life as of September 29, 2017. It will probably continue\n  to work, but will not be tested against\n  (`issue 217 <https://github.com/msiemens/tinydb/issues/217>`_)\n\n- Feature: Allow extending TinyDB with a custom storage proxy class\n  (see `pull request 224 <https://github.com/msiemens/tinydb/pull/224>`_)\n- Bugfix: Return list of document IDs for upsert when creating a new\n  document (see `issue 223 <https://github.com/msiemens/tinydb/issues/223>`_)\n\nv3.10.0 (2018-07-21)\n^^^^^^^^^^^^^^^^^^^^\n\n- Feature: Add support for regex flags\n  (see `pull request 216 <https://github.com/msiemens/tinydb/pull/216>`_)\n\nv3.9.0 (2018-04-24)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Allow setting a table class for single table only\n  (see `issue 197 <https://github.com/msiemens/tinydb/issues/197>`_)\n- Internal change: call fsync after flushing ``JSONStorage``\n  (see `issue 208 <https://github.com/msiemens/tinydb/issues/208>`_)\n\nv3.8.1 (2018-03-26)\n^^^^^^^^^^^^^^^^^^^\n\n- Bugfix: Don't install tests as a package anymore\n  (see `pull request #195 <https://github.com/msiemens/tinydb/pull/195>`_)\n\nv3.8.0 (2018-03-01)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: Allow disabling the query cache with ``db.table(name, cache_size=0)``\n  (see `pull request #187 <https://github.com/msiemens/tinydb/pull/187>`_)\n- Feature: Add ``db.write_back(docs)`` for replacing documents\n  (see `pull request #184 <https://github.com/msiemens/tinydb/pull/184>`_)\n\nv3.7.0 (2017-11-11)\n^^^^^^^^^^^^^^^^^^^\n\n- Feature: ``one_of`` for checking if a value is contained in a list\n  (see `issue 164 <https://github.com/msiemens/tinydb/issues/164>`_)\n- Feature: Upsert (insert if document doesn't exist, otherwise update;\n  see https://forum.m-siemens.de/d/30-primary-key-well-sort-of)\n- Internal change: don't read from storage twice during initialization\n  (see https://forum.m-siemens.de/d/28-reads-the-whole-data-file-twice)\n\nv3.6.0 (2017-10-05)\n^^^^^^^^^^^^^^^^^^^\n\n- Allow updating all documents using ``db.update(fields)`` (see\n  `issue #157 <https://github.com/msiemens/tinydb/issues/157>`_).\n- Rename elements to documents. Document IDs now available with ``doc.doc_id``,\n  using ``doc.eid`` is now deprecated\n  (see `pull request #158 <https://github.com/msiemens/tinydb/pull/158>`_)\n\nv3.5.0 (2017-08-30)\n^^^^^^^^^^^^^^^^^^^\n\n- Expose the table name via ``table.name`` (see\n  `issue #147 <https://github.com/msiemens/tinydb/issues/147>`_).\n- Allow better subclassing of the ``TinyDB`` class\n  (see `pull request #150 <https://github.com/msiemens/tinydb/pull/150>`_).\n\nv3.4.1 (2017-08-23)\n^^^^^^^^^^^^^^^^^^^\n\n- Expose TinyDB version via ``import tinyb; tinydb.__version__`` (see\n  `issue #148 <https://github.com/msiemens/tinydb/issues/148>`_).\n\nv3.4.0 (2017-08-08)\n^^^^^^^^^^^^^^^^^^^\n\n- Add new update operations: ``add(key, value)``, ``subtract(key, value)``,\n  and ``set(key, value)``\n  (see `pull request #145 <https://github.com/msiemens/tinydb/pull/145>`_).\n\nv3.3.1 (2017-06-27)\n^^^^^^^^^^^^^^^^^^^\n\n- Use relative imports to allow vendoring TinyDB in other packages\n  (see `pull request #142 <https://github.com/msiemens/tinydb/pull/142>`_).\n\nv3.3.0 (2017-06-05)\n^^^^^^^^^^^^^^^^^^^\n\n- Allow iterating over a database or table yielding all documents\n  (see `pull request #139 <https://github.com/msiemens/tinydb/pull/139>`_).\n\nv3.2.3 (2017-04-22)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix bug with accidental modifications to the query cache when modifying\n  the list of search results (see `issue #132 <https://github.com/msiemens/tinydb/issues/132>`_).\n\nv3.2.2 (2017-01-16)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix the ``Query`` constructor to prevent wrong usage\n  (see `issue #117 <https://github.com/msiemens/tinydb/issues/117>`_).\n\nv3.2.1 (2016-06-29)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix a bug with queries on documents that have a ``path`` key\n  (see `pull request #107 <https://github.com/msiemens/tinydb/pull/107>`_).\n- Don't write to the database file needlessly when opening the database\n  (see `pull request #104 <https://github.com/msiemens/tinydb/pull/104>`_).\n\nv3.2.0 (2016-04-25)\n^^^^^^^^^^^^^^^^^^^\n\n- Add a way to specify the default table name via :ref:`default_table <default_table>`\n  (see `pull request #98 <https://github.com/msiemens/tinydb/pull/98>`_).\n- Add ``db.purge_table(name)`` to remove a single table\n  (see `pull request #100 <https://github.com/msiemens/tinydb/pull/100>`_).\n\n  - Along the way: celebrating 100 issues and pull requests! Thanks everyone for every single contribution!\n\n- Extend API documentation (see `issue #96 <https://github.com/msiemens/tinydb/issues/96>`_).\n\nv3.1.3 (2016-02-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix a bug when using unhashable documents (lists, dicts) with\n  ``Query.any`` or ``Query.all`` queries\n  (see `a forum post by karibul <https://forum.m-siemens.de/d/4-error-with-any-and-all-queries>`_).\n\nv3.1.2 (2016-01-30)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix a bug when using unhashable documents (lists, dicts) with\n  ``Query.any`` or ``Query.all`` queries\n  (see `a forum post by karibul <https://forum.m-siemens.de/d/4-error-with-any-and-all-queries>`_).\n\nv3.1.1 (2016-01-23)\n^^^^^^^^^^^^^^^^^^^\n\n- Inserting a dictionary with data that is not JSON serializable doesn't\n  lead to corrupt files anymore (see `issue #89 <https://github.com/msiemens/tinydb/issues/89>`_).\n- Fix a bug in the LRU cache that may lead to an invalid query cache\n  (see `issue #87 <https://github.com/msiemens/tinydb/issues/87>`_).\n\nv3.1.0 (2015-12-31)\n^^^^^^^^^^^^^^^^^^^\n\n- ``db.update(...)`` and ``db.remove(...)`` now return affected document IDs\n  (see `issue #83 <https://github.com/msiemens/tinydb/issues/83>`_).\n- Inserting an invalid document (i.e. not a ``dict``) now raises an error\n  instead of corrupting the database (see\n  `issue #74 <https://github.com/msiemens/tinydb/issues/74>`_).\n\nv3.0.0 (2015-11-13)\n^^^^^^^^^^^^^^^^^^^\n\n-  Overhauled Query model:\n\n   -  ``where('...').contains('...')`` has been renamed to\n      ``where('...').search('...')``.\n   -  Support for ORM-like usage:\n      ``User = Query(); db.search(User.name == 'John')``.\n   -  ``where('foo')`` is an alias for ``Query().foo``.\n   -  ``where('foo').has('bar')`` is replaced by either\n      ``where('foo').bar`` or ``Query().foo.bar``.\n\n      -  In case the key is not a valid Python identifier, array\n         notation can be used: ``where('a.b.c')`` is now\n         ``Query()['a.b.c']``.\n\n   -  Checking for the existence of a key has to be done explicitly:\n      ``where('foo').exists()``.\n\n-  Migrations from v1 to v2 have been removed.\n-  ``SmartCacheTable`` has been moved to `msiemens/tinydb-smartcache`_.\n-  Serialization has been moved to `msiemens/tinydb-serialization`_.\n- Empty storages are now expected to return ``None`` instead of raising ``ValueError``.\n  (see `issue #67 <https://github.com/msiemens/tinydb/issues/67>`_.\n\n.. _msiemens/tinydb-smartcache: https://github.com/msiemens/tinydb-smartcache\n.. _msiemens/tinydb-serialization: https://github.com/msiemens/tinydb-serialization\n\nv2.4.0 (2015-08-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Allow custom parameters for custom test functions\n  (see `issue #63 <https://github.com/msiemens/tinydb/issues/63>`_ and\n  `pull request #64 <https://github.com/msiemens/tinydb/pull/64>`_).\n\nv2.3.2 (2015-05-20)\n^^^^^^^^^^^^^^^^^^^\n\n- Fix a forgotten debug output in the ``SerializationMiddleware``\n  (see `issue #55 <https://github.com/msiemens/tinydb/issues/55>`_).\n- Fix an \"ignored exception\" warning when using the ``CachingMiddleware``\n  (see `pull request #54 <https://github.com/msiemens/tinydb/pull/54>`_)\n- Fix a problem with symlinks when checking out TinyDB on OSX Yosemite\n  (see `issue #52 <https://github.com/msiemens/tinydb/issues/52>`_).\n\nv2.3.1 (2015-04-30)\n^^^^^^^^^^^^^^^^^^^\n\n- Hopefully fix a problem with using TinyDB as a dependency in a ``setup.py`` script\n  (see `issue #51 <https://github.com/msiemens/tinydb/issues/51>`_).\n\nv2.3.0 (2015-04-08)\n^^^^^^^^^^^^^^^^^^^\n\n- Added support for custom serialization. That way, you can teach TinyDB\n  to store ``datetime`` objects in a JSON file :)\n  (see `issue #48 <https://github.com/msiemens/tinydb/issues/48>`_ and\n  `pull request #50 <https://github.com/msiemens/tinydb/pull/50>`_)\n- Fixed a performance regression when searching became slower with every search\n  (see `issue #49 <https://github.com/msiemens/tinydb/issues/49>`_)\n- Internal code has been cleaned up\n\nv2.2.2 (2015-02-12)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed a data loss when using ``CachingMiddleware`` together with ``JSONStorage``\n  (see `issue #47 <https://github.com/msiemens/tinydb/issues/47>`_)\n\nv2.2.1 (2015-01-09)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed handling of IDs with the JSON backend that converted integers\n  to strings (see `issue #45 <https://github.com/msiemens/tinydb/issues/45>`_)\n\nv2.2.0 (2014-11-10)\n^^^^^^^^^^^^^^^^^^^\n\n- Extended ``any`` and ``all`` queries to take lists as conditions\n  (see `pull request #38 <https://github.com/msiemens/tinydb/pull/38>`_)\n- Fixed an ``decode error`` when installing TinyDB in a non-UTF-8 environment\n  (see `pull request #37 <https://github.com/msiemens/tinydb/pull/37>`_)\n- Fixed some issues with ``CachingMiddleware`` in combination with\n  ``JSONStorage`` (see `pull request #39 <https://github.com/msiemens/tinydb/pull/39>`_)\n\nv2.1.0 (2014-10-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Added ``where(...).contains(regex)``\n  (see `issue #32 <https://github.com/msiemens/tinydb/issues/32>`_)\n- Fixed a bug that corrupted data after reopening a database\n  (see `issue #34 <https://github.com/msiemens/tinydb/issues/34>`_)\n\nv2.0.1 (2014-09-22)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed handling of Unicode data in Python 2\n  (see `issue #28 <https://github.com/msiemens/tinydb/issues/28>`_).\n\nv2.0.0 (2014-09-05)\n^^^^^^^^^^^^^^^^^^^\n\n:ref:`Upgrade Notes <upgrade_v2_0>`\n\n.. warning:: TinyDB changed the way data is stored. You may need to migrate\n             your databases to the new scheme. Check out the\n             :ref:`Upgrade Notes <upgrade_v2_0>` for details.\n\n- The syntax ``query in db`` has been removed, use ``db.contains`` instead.\n- The ``ConcurrencyMiddleware`` has been removed due to a insecure implementation\n  (see `issue #18 <https://github.com/msiemens/tinydb/issues/18>`_).  Consider\n  :ref:`tinyrecord` instead.\n\n- Better support for working with :ref:`Document IDs <document_ids>`.\n- Added support for `nested comparisons <http://tinydb.readthedocs.io/en/v2.0.0/usage.html#nested-queries>`_.\n- Added ``all`` and ``any`` `comparisons on lists <http://tinydb.readthedocs.io/en/v2.0.0/usage.html#nested-queries>`_.\n- Added optional :<http://tinydb.readthedocs.io/en/v2.0.0/usage.html#smart-query-cache>`_.\n- The query cache is now a :ref:`fixed size LRU cache <query_caching>`.\n\nv1.4.0 (2014-07-22)\n^^^^^^^^^^^^^^^^^^^\n\n- Added ``insert_multiple`` function\n  (see `issue #8 <https://github.com/msiemens/tinydb/issues/8>`_).\n\nv1.3.0 (2014-07-02)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed `bug #7 <https://github.com/msiemens/tinydb/issues/7>`_: IDs not unique.\n- Extended the API: ``db.count(where(...))`` and ``db.contains(where(...))``.\n- The syntax ``query in db`` is now **deprecated** and replaced\n  by ``db.contains``.\n\nv1.2.0 (2014-06-19)\n^^^^^^^^^^^^^^^^^^^\n\n- Added ``update`` method\n  (see `issue #6 <https://github.com/msiemens/tinydb/issues/6>`_).\n\nv1.1.1 (2014-06-14)\n^^^^^^^^^^^^^^^^^^^\n\n- Merged `PR #5 <https://github.com/msiemens/tinydb/pull/5>`_: Fix minor\n  documentation typos and style issues.\n\nv1.1.0 (2014-05-06)\n^^^^^^^^^^^^^^^^^^^\n\n- Improved the docs and fixed some typos.\n- Refactored some internal code.\n- Fixed a bug with multiple ``TinyDB?`` instances.\n\nv1.0.1 (2014-04-26)\n^^^^^^^^^^^^^^^^^^^\n\n- Fixed a bug in ``JSONStorage`` that broke the database when removing entries.\n\nv1.0.0 (2013-07-20)\n^^^^^^^^^^^^^^^^^^^\n\n- First official release – consider TinyDB stable now.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# TinyDB documentation build configuration file, created by\n# sphinx-quickstart on Sat Jul 13 20:14:55 2013.\n#\n# This file is execfile()d with the current directory set to its containing\n# dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport sys\n\nfrom importlib import metadata\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n# sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration ----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage',\n              'sphinx.ext.viewcode', 'sphinx.ext.intersphinx',\n              'sphinx.ext.todo', 'sphinx.ext.extlinks']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'TinyDB'\ncopyright = u'2021, Markus Siemens'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n\ntry:\n    release = metadata.version('tinydb')\nexcept metadata.PackageNotFoundError:\n    print('To build the documentation, The distribution information of TinyDB')\n    print('has to be available. Either install the package into your')\n    print('development environment or run \"pip install -e .\" to setup the')\n    print('metadata. A virtualenv is recommended!')\n    sys.exit(1)\n\nif 'dev' in release:\n    release = release.split('dev')[0] + 'dev'\nversion = '.'.join(release.split('.')[:2])\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n# keep_warnings = False\n\n\n# -- Options for HTML output --------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n# html_theme = 'default'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\nhtml_sidebars = {\n    'index': ['sidebarlogo.html', 'links.html', 'searchbox.html'],\n    '**': ['sidebarlogo.html', 'localtoc.html', 'links.html',\n           'searchbox.html']\n}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\nhtml_show_sourcelink = False\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'TinyDBdoc'\n\n# -- Options for LaTeX output -------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    # 'papersize': 'letterpaper',\n\n    # The font size ('10pt', '11pt' or '12pt').\n    # 'pointsize': '10pt',\n\n    # Additional stuff for the LaTeX preamble.\n    # 'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass\n# [howto/manual]).\nlatex_documents = [\n    ('index', 'TinyDB.tex', u'TinyDB Documentation',\n     u'Markus Siemens', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output -------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'tinydb', u'TinyDB Documentation',\n     [u'Markus Siemens'], 1)\n]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output -----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    ('index', 'TinyDB', u'TinyDB Documentation',\n     u'Markus Siemens', 'TinyDB', 'One line description of project.',\n     'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n# texinfo_no_detailmenu = False\n\nextlinks = {'issue': ('https://https://github.com/msiemens/tinydb/issues/%s',\n                      'issue ')}\n\nsys.path.append(os.path.abspath('_themes'))\nhtml_theme_path = ['_themes']\nhtml_theme = 'flask'\n\ntodo_include_todos = True\n"
  },
  {
    "path": "docs/contribute.rst",
    "content": "Contribution Guidelines\n#######################\n\nWhether reporting bugs, discussing improvements and new ideas or writing\nextensions: Contributions to TinyDB are welcome! Here's how to get started:\n\n1. Check for open issues or open a fresh issue to start a discussion around\n   a feature idea or a bug\n2. Fork `the repository <https://github.com/msiemens/tinydb/>`_ on Github,\n   create a new branch off the `master` branch and start making your changes\n   (known as `GitHub Flow <https://guides.github.com/introduction/flow/index.html>`_)\n3. Write a test which shows that the bug was fixed or that the feature works\n   as expected\n4. Send a pull request and bug the maintainer until it gets merged and\n   published :)\n\nPhilosophy of TinyDB\n********************\n\nTinyDB aims to be simple and fun to use. Therefore two key values are simplicity\nand elegance of interfaces and code. These values will contradict each other\nfrom time to time. In these cases , try using as little magic as possible.\nIn any case don't forget documenting code that isn't clear at first glance.\n\nCode Conventions\n****************\n\nIn general the TinyDB source should always follow `PEP 8 <http://legacy.python.org/dev/peps/pep-0008/>`_.\nExceptions are allowed in well justified and documented cases. However we make\na small exception concerning docstrings:\n\nWhen using multiline docstrings, keep the opening and closing triple quotes\non their own lines and add an empty line after it.\n\n.. code-block:: python\n\n    def some_function():\n        \"\"\"\n        Documentation ...\n        \"\"\"\n\n        # implementation ...\n\nVersion Numbers\n***************\n\nTinyDB follows the `SemVer versioning guidelines <http://semver.org/>`_.\nThis implies that backwards incompatible changes in the API will increment\nthe major version. So think twice before making such changes.\n"
  },
  {
    "path": "docs/extend.rst",
    "content": "How to Extend TinyDB\n====================\n\nThere are three main ways to extend TinyDB and modify its behaviour:\n\n1. custom storages,\n2. custom middlewares,\n3. use hooks and overrides, and\n4. subclassing ``TinyDB`` and ``Table``.\n\nLet's look at them in this order.\n\nWrite a Custom Storage\n----------------------\n\nFirst, we have support for custom storages. By default TinyDB comes with an\nin-memory storage and a JSON file storage. But of course you can add your own.\nLet's look how you could add a `YAML <http://yaml.org/>`_ storage using\n`PyYAML <http://pyyaml.org/wiki/PyYAML>`_:\n\n.. code-block:: python\n\n    import yaml\n\n    class YAMLStorage(Storage):\n        def __init__(self, filename):  # (1)\n            self.filename = filename\n\n        def read(self):\n            with open(self.filename) as handle:\n                try:\n                    data = yaml.safe_load(handle.read())  # (2)\n                    return data\n                except yaml.YAMLError:\n                    return None  # (3)\n\n        def write(self, data):\n            with open(self.filename, 'w+') as handle:\n                yaml.dump(data, handle)\n\n        def close(self):  # (4)\n            pass\n\nThere are some things we should look closer at:\n\n1. The constructor will receive all arguments passed to TinyDB when creating\n   the database instance (except ``storage`` which TinyDB itself consumes).\n   In other words calling ``TinyDB('something', storage=YAMLStorage)`` will\n   pass ``'something'`` as an argument to ``YAMLStorage``.\n   If you accept callables or other executable values in your storage\n   constructor (or elsewhere), do not derive them from untrusted or\n   user-controlled input.\n2. We use ``yaml.safe_load`` as recommended by the\n   `PyYAML documentation <http://pyyaml.org/wiki/PyYAMLDocumentation#LoadingYAML>`_\n   when processing data from a potentially untrusted source.\n3. If the storage is uninitialized, TinyDB expects the storage to return\n   ``None`` so it can do any internal initialization that is necessary.\n4. If your storage needs any cleanup (like closing file handles) before an\n   instance is destroyed, you can put it in the ``close()`` method. To run\n   these, you'll either have to run ``db.close()`` on your ``TinyDB`` instance\n   or use it as a context manager, like this:\n\n   .. code-block:: python\n\n        with TinyDB('db.yml', storage=YAMLStorage) as db:\n            # ...\n\nFinally, using the YAML storage is very straight-forward:\n\n.. code-block:: python\n\n    db = TinyDB('db.yml', storage=YAMLStorage)\n    # ...\n\n\nWrite Custom Middleware\n-------------------------\n\nSometimes you don't want to write a new storage module but rather modify the\nbehaviour of an existing one. As an example we'll build middleware that filters\nout empty items.\n\nBecause middleware acts as a wrapper around a storage, they needs a ``read()``\nand a ``write(data)`` method. In addition, they can access the underlying storage\nvia ``self.storage``. Before we start implementing we should look at the structure\nof the data that the middleware receives. Here's what the data that goes through\nthe middleware looks like:\n\n.. code-block:: python\n\n    {\n        '_default': {\n            1: {'key': 'value'},\n            2: {'key': 'value'},\n            # other items\n        },\n        # other tables\n    }\n\nThus, we'll need two nested loops:\n\n1. Process every table\n2. Process every item\n\nNow let's implement that:\n\n.. code-block:: python\n\n    class RemoveEmptyItemsMiddleware(Middleware):\n        def __init__(self, storage_cls):\n            # Any middleware *has* to call the super constructor\n            # with storage_cls\n            super().__init__(storage_cls)  # (1)\n\n        def read(self):\n            data = self.storage.read()\n\n            for table_name in data:\n                table_data = data[table_name]\n\n                for doc_id in table_data:\n                    item = table_data[doc_id]\n\n                    if item == {}:\n                        del table_data[doc_id]\n\n            return data\n\n        def write(self, data):\n            for table_name in data:\n                table_data = data[table_name]\n\n                for doc_id in table_data:\n                    item = table_data[doc_id]\n\n                    if item == {}:\n                        del table_data[doc_id]\n\n            self.storage.write(data)\n\n        def close(self):\n            self.storage.close()\n\n\nNote that the constructor calls the middleware constructor (1) and passes\nthe storage class to the middleware constructor.\n\nTo wrap storage with this new middleware, we use it like this:\n\n.. code-block:: python\n\n    db = TinyDB(storage=RemoveEmptyItemsMiddleware(SomeStorageClass))\n\nHere ``SomeStorageClass`` should be replaced with the storage you want to use.\nIf you leave it empty, the default storage will be used (which is the ``JSONStorage``).\n\nUse hooks and overrides\n-----------------------\n\n.. _extend_hooks:\n\nThere are cases when neither creating a custom storage nor using a custom\nmiddleware will allow you to adapt TinyDB in the way you need. In this case\nyou can modify TinyDB's behavior by using predefined hooks and override points.\nFor example you can configure the name of the default table by setting\n``TinyDB.default_table_name``:\n\n.. code-block:: python\n\n    TinyDB.default_table_name = 'my_table_name'\n\nBoth :class:`~tinydb.database.TinyDB` and the :class:`~tinydb.table.Table`\nclasses allow modifying their behavior using hooks and overrides. To use\n``Table``'s overrides, you can access the class using ``TinyDB.table_class``:\n\n.. code-block:: python\n\n    TinyDB.table_class.default_query_cache_capacity = 100\n\nRead the :ref:`api_docs` for more details on the available hooks and override\npoints.\n\nSubclassing ``TinyDB`` and ``Table``\n------------------------------------\n\nFinally, there's the last option to modify TinyDB's behavior. That way you\ncan change how TinyDB itself works more deeply than using the other extension\nmechanisms.\n\nWhen creating a subclass you can use it by using hooks and overrides to override\nthe default classes that TinyDB uses:\n\n.. code-block:: python\n\n    class MyTable(Table):\n        # Add your method overrides\n        ...\n\n    TinyDB.table_class = MyTable\n\n    # Continue using TinyDB as usual\n\nTinyDB's source code is documented with extensions in mind, explaining how\neverything works even for internal methods and classes. Feel free to dig into\nthe source and adapt everything you need for your projects.\n"
  },
  {
    "path": "docs/extensions.rst",
    "content": "Extensions\n==========\n\nHere are some extensions that might be useful to you:\n\n``tinydb-rust``\n**************\n\n| **Repo:**        https://github.com/itsmorninghao/tinydb-rust/\n| **Status:**      *beta*\n| **Description:** A drop-in reimplementation of TinyDB that uses Rust for\n                   better performance.\n\n``aiotinydb``\n*************\n\n| **Repo:**        https://github.com/ASMfreaK/aiotinydb\n| **Status:**      *stable*\n| **Description:** asyncio compatibility shim for TinyDB. Enables usage of\n                   TinyDB in asyncio-aware contexts without slow synchronous\n                   IO.\n\n\n``BetterJSONStorage``\n*********************\n\n| **Repo:**        https://github.com/MrPigss/BetterJSONStorage\n| **Status:**      *stable*\n| **Description:** BetterJSONStorage is a faster 'Storage Type' for TinyDB. It\n                   uses the faster Orjson library for parsing the JSON and BLOSC\n                   for compression.\n\n\n``tinydb-serialization``\n************************\n\n| **Repo:**        https://github.com/msiemens/tinydb-serialization\n| **Status:**      *stable*\n| **Description:** ``tinydb-serialization`` provides serialization for objects\n                   that TinyDB otherwise couldn't handle.\n\n\n``tinydb-smartcache``\n*********************\n\n| **Repo:**        https://github.com/msiemens/tinydb-smartcache\n| **Status:**      *stable*\n| **Description:** ``tinydb-smartcache`` provides a smart query cache for\n                   TinyDB. It updates the query cache when\n                   inserting/removing/updating documents so the cache doesn't\n                   get invalidated. It's useful if you perform lots of queries\n                   while the data changes only little.\n\n\n.. _tinyrecord:\n\n``tinyrecord``\n**************\n\n| **Repo:**        https://github.com/eugene-eeo/tinyrecord\n| **Status:**      *stable*\n| **Description:** Tinyrecord is a library which implements experimental atomic\n                   transaction support for the TinyDB NoSQL database. It uses a\n                   record-first then execute architecture which allows us to\n                   minimize the time that we are within a thread lock.\n\n\n``tinydb-appengine``\n********************\n\n| **Repo:**        https://github.com/imalento/tinydb-appengine\n| **Status:**      *inactive*\n| **Description:** ``tinydb-appengine`` provides TinyDB storage for\n                   App Engine. You can use JSON readonly.\n\n\n``TinyDBTimestamps``\n********************\n\n| **Repo:**        https://github.com/pachacamac/TinyDBTimestamps\n| **Status:**      *inactive*\n| **Description:** Automatically add create at/ update at timestamps to TinyDB\n                   documents.\n\n\n``tinyindex``\n*************\n\n| **Repo:**        https://github.com/eugene-eeo/tinyindex\n| **Status:**      *inactive*\n| **Description:** Document indexing for TinyDB. Basically ensures deterministic\n                   (as long as there aren't any changes to the table) yielding\n                   of documents.\n\n\n``tinymongo``\n*************\n\n| **Repo:**        https://github.com/schapman1974/tinymongo\n| **Status:**      *inactive*\n| **Description:** A simple wrapper that allows to use TinyDB as a flat file\n                   drop-in replacement for MongoDB.\n\n\n``TinyMP``\n*************\n\n| **Repo:**        https://github.com/alshapton/TinyMP\n| **Status:**      *inactive*\n| **Description:** A MessagePack-based storage extension to tinydb using\n                   http://msgpack.org\n"
  },
  {
    "path": "docs/getting-started.rst",
    "content": ":tocdepth: 3\n\nGetting Started\n===============\n\nInstalling TinyDB\n-----------------\n\nTo install TinyDB from PyPI, run::\n\n    $ pip install tinydb\n\nYou can also grab the latest development version from GitHub_. After downloading\nand unpacking it, you can install it using::\n\n    $ pip install .\n\n\nBasic Usage\n-----------\n\nLet's cover the basics before going more into detail. We'll start by setting up\na TinyDB database:\n\n>>> from tinydb import TinyDB, Query\n>>> db = TinyDB('db.json')\n\nYou now have a TinyDB database that stores its data in ``db.json``.\nWhat about inserting some data? TinyDB expects the data to be Python ``dict``\\s:\n\n>>> db.insert({'type': 'apple', 'count': 7})\n>>> db.insert({'type': 'peach', 'count': 3})\n\n.. note:: The ``insert`` method returns the inserted document's ID. Read more\n          about it here: :ref:`document_ids`.\n\n\nNow you can get all documents stored in the database by running:\n\n>>> db.all()\n[{'count': 7, 'type': 'apple'}, {'count': 3, 'type': 'peach'}]\n\nYou can also iter over stored documents:\n\n>>> for item in db:\n>>>     print(item)\n{'count': 7, 'type': 'apple'}\n{'count': 3, 'type': 'peach'}\n\nOf course you'll also want to search for specific documents. Let's try:\n\n>>> Fruit = Query()\n>>> db.search(Fruit.type == 'peach')\n[{'count': 3, 'type': 'peach'}]\n>>> db.search(Fruit.count > 5)\n[{'count': 7, 'type': 'apple'}]\n\n\nNext we'll update the ``count`` field of the apples:\n\n>>> db.update({'count': 10}, Fruit.type == 'apple')\n>>> db.all()\n[{'count': 10, 'type': 'apple'}, {'count': 3, 'type': 'peach'}]\n\n\nIn the same manner you can also remove documents:\n\n>>> db.remove(Fruit.count < 5)\n>>> db.all()\n[{'count': 10, 'type': 'apple'}]\n\nAnd of course you can throw away all data to start with an empty database:\n\n>>> db.truncate()\n>>> db.all()\n[]\n\n\nRecap\n*****\n\nBefore we dive deeper, let's recapitulate the basics:\n\n+-------------------------------+---------------------------------------------------------------+\n| **Inserting**                                                                                 |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.insert(...)``            | Insert a document                                             |\n+-------------------------------+---------------------------------------------------------------+\n| **Getting data**                                                                              |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.all()``                  | Get all documents                                             |\n+-------------------------------+---------------------------------------------------------------+\n| ``iter(db)``                  | Iter over all documents                                       |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.search(query)``          | Get a list of documents matching the query                    |\n+-------------------------------+---------------------------------------------------------------+\n| **Updating**                                                                                  |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.update(fields, query)``  | Update all documents matching the query to contain ``fields`` |\n+-------------------------------+---------------------------------------------------------------+\n| **Removing**                                                                                  |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.remove(query)``          | Remove all documents matching the query                       |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.truncate()``             | Remove all documents                                          |\n+-------------------------------+---------------------------------------------------------------+\n| **Querying**                                                                                  |\n+-------------------------------+---------------------------------------------------------------+\n| ``Query()``                   | Create a new query object                                     |\n+-------------------------------+---------------------------------------------------------------+\n| ``Query().field == 2``        | Match any document that has a key ``field`` with value        |\n|                               | ``== 2`` (also possible: ``!=``, ``>``, ``>=``, ``<``, ``<=``)|\n+-------------------------------+---------------------------------------------------------------+\n\n.. note::\n\n    Query comparisons only support literal values on the right-hand side.\n    Field-to-field comparisons like ``Query().a == Query().b`` are not\n    supported. Use a callable predicate like\n    ``db.search(lambda doc: doc.get('a') == doc.get('b'))`` for custom logic.\n\n.. note::\n\n    Callables passed to query APIs (e.g. ``lambda`` predicates or ``Query().map``)\n    execute in-process and must **never** be derived from untrusted or user-controlled\n    input.\n\n.. References\n.. _GitHub: http://github.com/msiemens/tinydb/\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to TinyDB!\n==================\n\nWelcome to TinyDB, your tiny, document oriented database optimized for your\nhappiness :)\n\n>>> from tinydb import TinyDB, Query\n>>> db = TinyDB('path/to/db.json')\n>>> User = Query()\n>>> db.insert({'name': 'John', 'age': 22})\n>>> db.search(User.name == 'John')\n[{'name': 'John', 'age': 22}]\n\nUser's Guide\n------------\n\n.. toctree::\n   :maxdepth: 2\n\n   intro\n   getting-started\n   usage\n\nExtending TinyDB\n----------------\n\n.. toctree::\n   :maxdepth: 2\n\n   Extending TinyDB <extend>\n   TinyDB Extensions <extensions>\n\nAPI Reference\n-------------\n\n.. toctree::\n   :maxdepth: 2\n\n   api\n\nAdditional Notes\n----------------\n\n.. toctree::\n   :maxdepth: 2\n\n   contribute\n   changelog\n   Upgrade Notes <upgrade>\n"
  },
  {
    "path": "docs/intro.rst",
    "content": "Introduction\n============\n\nGreat that you've taken time to check out the TinyDB docs! Before we begin\nlooking at TinyDB itself, let's take some time to see whether you should use\nTinyDB.\n\nWhy Use TinyDB?\n---------------\n\n- **tiny:** The current source code has 1800 lines of code (with about 40%\n  documentation) and 1600 lines tests.\n\n- **document oriented:** Like MongoDB_, you can store any document\n  (represented as ``dict``) in TinyDB.\n\n- **optimized for your happiness:** TinyDB is designed to be simple and\n  fun to use by providing a simple and clean API.\n\n- **written in pure Python:** TinyDB neither needs an external server (as\n  e.g. `PyMongo <https://pymongo.readthedocs.io/en/stable/>`_) nor any dependencies\n  from PyPI.\n\n- **works on Python 3.5+ and PyPy:** TinyDB works on all modern versions of Python\n  and PyPy.\n\n- **powerfully extensible:** You can easily extend TinyDB by writing new\n  storages or modify the behaviour of storages with Middlewares.\n\n- **100% test coverage:** No explanation needed.\n\nIn short: If you need a simple database with a clean API that just works\nwithout lots of configuration, TinyDB might be the right choice for you.\n\n\nWhy **Not** Use TinyDB?\n-----------------------\n\n- You need **advanced features** like:\n    - access from multiple processes or threads (e.g. when using Flask!),\n    - creating indexes for tables,\n    - an HTTP server,\n    - managing relationships between tables or similar,\n    - `ACID guarantees <https://en.wikipedia.org/wiki/ACID>`_.\n- You are really concerned about **performance** and need a high speed\n  database.\n\nTo put it plainly: If you need advanced features or high performance, TinyDB\nis the wrong database for you – consider using databases like SQLite_, Buzhug_,\nCodernityDB_ or MongoDB_.\n\n.. References\n.. _Buzhug: https://buzhug.sourceforge.net/\n.. _CodernityDB: http://labs.codernity.com/codernitydb/\n.. _MongoDB: https://mongodb.org/\n.. _SQLite: https://www.sqlite.org/\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  xml        to make Docutils-native XML files\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\n\n%SPHINXBUILD% 2> nul\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\TinyDB.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\TinyDB.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdf\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdfja\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf-ja\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"xml\" (\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\n\tgoto end\n)\n\nif \"%1\" == \"pseudoxml\" (\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/upgrade.rst",
    "content": "Upgrading to Newer Releases\n===========================\n\nVersion 4.0\n-----------\n\n.. _upgrade_v4_0:\n\n- API changes:\n    - Replace ``TinyDB.purge_tables(...)`` with ``TinyDB.drop_tables(...)``\n    - Replace ``TinyDB.purge_table(...)`` with ``TinyDB.drop_table(...)``\n    - Replace ``Table.purge()`` with ``Table.truncate()``\n    - Replace ``TinyDB(default_table='name')`` with ``TinyDB.default_table_name = 'name'``\n    - Replace ``TinyDB(table_class=Class)`` with ``TinyDB.table_class = Class``\n    - If you were using ``TinyDB.DEFAULT_TABLE``, ``TinyDB.DEFAULT_TABLE_KWARGS``,\n      or ``TinyDB.DEFAULT_STORAGE``: Use the new methods for customizing TinyDB\n      described in :ref:`How to Extend TinyDB <extend_hooks>`\n\nVersion 3.0\n-----------\n\n.. _upgrade_v3_0:\n\nBreaking API Changes\n^^^^^^^^^^^^^^^^^^^^\n\n-  Querying (see `Issue #62 <https://github.com/msiemens/tinydb/issues/62>`_):\n\n   -  ``where('...').contains('...')`` has been renamed to\n      ``where('...').search('...')``.\n   -  ``where('foo').has('bar')`` is replaced by either\n      ``where('foo').bar`` or ``Query().foo.bar``.\n\n      -  In case the key is not a valid Python identifier, array\n         notation can be used: ``where('a.b.c')`` is now\n         ``Query()['a.b.c']``.\n\n  -  Checking for the existence of a key has to be done explicitly:\n     ``where('foo').exists()``.\n\n-  ``SmartCacheTable`` has been moved to `msiemens/tinydb-smartcache`_.\n-  Serialization has been moved to `msiemens/tinydb-serialization`_.\n-  Empty storages are now expected to return ``None`` instead of raising\n   ``ValueError`` (see `Issue #67 <https://github.com/msiemens/tinydb/issues/67>`_).\n\n.. _msiemens/tinydb-smartcache: https://github.com/msiemens/tinydb-smartcache\n.. _msiemens/tinydb-serialization: https://github.com/msiemens/tinydb-serialization\n\n.. _upgrade_v2_0:\n\nVersion 2.0\n-----------\n\nBreaking API Changes\n^^^^^^^^^^^^^^^^^^^^\n\n- The syntax ``query in db`` is not supported any more. Use ``db.contains(...)``\n  instead.\n- The ``ConcurrencyMiddleware`` has been removed due to a insecure implementation\n  (see `Issue #18 <https://github.com/msiemens/tinydb/issues/18>`_).  Consider\n  :ref:`tinyrecord` instead.\n\nApart from that the API remains compatible to v1.4 and prior.\n\nFor migration from v1 to v2, check out the `v2.0 documentation <http://tinydb.readthedocs.io/en/v2.0/upgrade.html#upgrade-v2-0>`_\n"
  },
  {
    "path": "docs/usage.rst",
    "content": ":tocdepth: 3\n\n.. toctree::\n   :maxdepth: 2\n\nAdvanced Usage\n==============\n\nRemarks on Storage\n------------------\n\nBefore we dive deeper into the usage of TinyDB, we should stop for a moment\nand discuss how TinyDB stores data.\n\nTo convert your data to a format that is writable to disk TinyDB uses the\n`Python JSON <http://docs.python.org/2/library/json.html>`_ module by default.\nIt's great when only simple data types are involved but it cannot handle more\ncomplex data types like custom classes. On Python 2 it also converts strings to\nUnicode strings upon reading\n(described `here <http://stackoverflow.com/q/956867/997063>`_).\n\nIf that causes problems, you can write\n:doc:`your own storage <extend>`, that uses a more powerful (but also slower)\nlibrary like `pickle <http://docs.python.org/library/pickle.html>`_ or\n`PyYAML <http://pyyaml.org/>`_.\n\n.. hint:: Opening multiple TinyDB instances on the same data (e.g. with the\n   ``JSONStorage``) may result in unexpected behavior due to query caching.\n   See query_caching_ on how to disable the query cache.\n\nQueries\n-------\n\nWith that out of the way, let's start with TinyDB's rich set of queries.\nThere are two main ways to construct queries. The first one resembles the\nsyntax of popular ORM tools:\n\n>>> from tinydb import Query\n>>> User = Query()\n>>> db.search(User.name == 'John')\n\nAs you can see, we first create a new Query object and then use it to specify\nwhich fields to check. Searching for nested fields is just as easy:\n\n>>> db.search(User.birthday.year == 1990)\n\nNot all fields can be accessed this way if the field name is not a valid Python\nidentifier. In this case, you can switch to dict access notation:\n\n>>> # This would be invalid Python syntax:\n>>> db.search(User.country-code == 'foo')\n>>> # Use this instead:\n>>> db.search(User['country-code'] == 'foo')\n\nIn addition, you can use arbitrary transform function where a field would be,\nfor example:\n\n>>> from unidecode import unidecode\n>>> db.search(User.name.map(unidecode) == 'Jose')\n>>> # will match 'José' etc.\n\nThe second, traditional way of constructing queries is as follows:\n\n>>> from tinydb import where\n>>> db.search(where('field') == 'value')\n\nUsing ``where('field')`` is a shorthand for the following code:\n\n>>> db.search(Query()['field'] == 'value')\n\nAccessing nested fields with this syntax can be achieved like this:\n\n>>> db.search(where('birthday').year == 1900)\n>>> db.search(where('birthday')['year'] == 1900)\n\nAdvanced queries\n................\n\nIn the :doc:`getting-started` you've learned about the basic comparisons\n(``==``, ``<``, ``>``, ...). In addition to these TinyDB supports the following\nqueries:\n\n>>> # Existence of a field:\n>>> db.search(User.name.exists())\n\n>>> # Regex:\n>>> # Full item has to match the regex:\n>>> db.search(User.name.matches('[aZ]*'))\n>>> # Case insensitive search for 'John':\n>>> import re\n>>> db.search(User.name.matches('John', flags=re.IGNORECASE))\n>>> # Any part of the item has to match the regex:\n>>> db.search(User.name.search('b+'))\n\n>>> # Custom test:\n>>> test_func = lambda s: s == 'John'\n>>> db.search(User.name.test(test_func))\n\n>>> # Custom test with parameters:\n>>> def test_func(val, m, n):\n>>>     return m <= val <= n\n>>> db.search(User.age.test(test_func, 0, 21))\n>>> db.search(User.age.test(test_func, 21, 99))\n\nAnother case is if you have a ``dict`` where you want to find all documents\nthat match this ``dict``. We call this searching for a fragment:\n\n>>> db.search(Query().fragment({'foo': True, 'bar': False}))\n[{'foo': True, 'bar': False, 'foobar: 'yes!'}]\n\nYou also can search for documents where a specific field matches the fragment:\n\n>>> db.search(Query().field.fragment({'foo': True, 'bar': False}))\n[{'field': {'foo': True, 'bar': False, 'foobar: 'yes!'}]\n\nWhen a field contains a list, you also can use the ``any`` and ``all`` methods.\nThere are two ways to use them: with lists of values and with nested queries.\nLet's start with the first one. Assuming we have a user object with a groups list\nlike this:\n\n>>> db.insert({'name': 'user1', 'groups': ['user']})\n>>> db.insert({'name': 'user2', 'groups': ['admin', 'user']})\n>>> db.insert({'name': 'user3', 'groups': ['sudo', 'user']})\n\nNow we can use the following queries:\n\n>>> # User's groups include at least one value from ['admin', 'sudo']\n>>> db.search(User.groups.any(['admin', 'sudo']))\n[{'name': 'user2', 'groups': ['admin', 'user']},\n {'name': 'user3', 'groups': ['sudo', 'user']}]\n>>>\n>>> # User's groups include all values from ['admin', 'user']\n>>> db.search(User.groups.all(['admin', 'user']))\n[{'name': 'user2', 'groups': ['admin', 'user']}]\n\nIn some cases you may want to have more complex ``any``/``all`` queries.\nThis is where nested queries come in as helpful. Let's set up a table like this:\n\n>>> Group = Query()\n>>> Permission = Query()\n>>> groups = db.table('groups')\n>>> groups.insert({\n        'name': 'user',\n        'permissions': [{'type': 'read'}]})\n>>> groups.insert({\n        'name': 'sudo',\n        'permissions': [{'type': 'read'}, {'type': 'sudo'}]})\n>>> groups.insert({\n        'name': 'admin',\n        'permissions': [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]})\n\nNow let's search this table using nested ``any``/``all`` queries:\n\n>>> # Group has a permission with type 'read'\n>>> groups.search(Group.permissions.any(Permission.type == 'read'))\n[{'name': 'user', 'permissions': [{'type': 'read'}]},\n {'name': 'sudo', 'permissions': [{'type': 'read'}, {'type': 'sudo'}]},\n {'name': 'admin', 'permissions':\n        [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]}]\n>>> # Group has ONLY permission 'read'\n>>> groups.search(Group.permissions.all(Permission.type == 'read'))\n[{'name': 'user', 'permissions': [{'type': 'read'}]}]\n\n\nAs you can see, ``any`` tests if there is *at least one* document matching\nthe query while ``all`` ensures *all* documents match the query.\n\nThe opposite operation, checking if a single item is contained in a list,\nis also possible using ``one_of``:\n\n>>> db.search(User.name.one_of(['jane', 'john']))\n\nQuery modifiers\n...............\n\nTinyDB also allows you to use logical operations to modify and combine\nqueries:\n\n>>> # Negate a query:\n>>> db.search(~ (User.name == 'John'))\n\n>>> # Logical AND:\n>>> db.search((User.name == 'John') & (User.age <= 30))\n\n>>> # Logical OR:\n>>> db.search((User.name == 'John') | (User.name == 'Bob'))\n\n.. note::\n\n    When using ``&`` or ``|``, make sure you wrap the conditions on both sides\n    with parentheses or Python will mess up the comparison.\n\n    Also, when using negation (``~``) you'll have to wrap the query you want\n    to negate in parentheses.\n\n    The reason for these requirements is that Python's binary operators that are\n    used for query modifiers have a higher operator precedence than comparison\n    operators. Simply put, ``~ User.name == 'John'`` is parsed by Python as\n    ``(~User.name) == 'John'`` instead of ``~(User.name == 'John')``. See also the\n    Python `docs on operator precedence\n    <https://docs.python.org/3/reference/expressions.html#operator-precedence>`_\n    for details.\n\n    You can compose queries dynamically by using the no-op query ``Query().noop()``.\n\n    Comparisons only support literal values on the right-hand side. Field-to-field\n    comparisons like ``Query().a == Query().b`` are not supported. Use a callable\n    predicate like ``db.search(lambda doc: doc.get('a') == doc.get('b'))`` for\n    custom logic.\n\n.. note::\n    Callables passed to query APIs (e.g. predicates, ``Query().map``,\n    ``Query().test``) execute in-process and must **never** be derived from\n    untrusted or user-controlled input.\n\nRecap\n.....\n\nLet's review the query operations we've learned:\n\n+-------------------------------------+---------------------------------------------------------------------+\n| **Queries**                                                                                               |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.exists()``          | Match any document where a field called ``field`` exists            |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.matches(regex)``    | Match any document with the whole field matching the                |\n|                                     | regular expression                                                  |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.search(regex)``     | Match any document with a substring of the field matching           |\n|                                     | the regular expression                                              |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.test(func, *args)`` | Matches any document for which the function returns                 |\n|                                     | ``True``                                                            |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.all(query | list)`` | If given a query, matches all documents where all documents         |\n|                                     | in the list ``field`` match the query.                              |\n|                                     | If given a list, matches all documents where all documents          |\n|                                     | in the list ``field`` are a member of the given list                |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.any(query | list)`` | If given a query, matches all documents where at least one          |\n|                                     | document in the list ``field`` match the query.                     |\n|                                     | If given a list, matches all documents where at least one           |\n|                                     | documents in the list ``field`` are a member of the given           |\n|                                     | list                                                                |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``Query().field.one_of(list)``      | Match if the field is contained in the list                         |\n+-------------------------------------+---------------------------------------------------------------------+\n| **Logical operations on queries**                                                                         |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``~ (query)``                       | Match documents that don't match the query (logical NOT)            |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``(query1) & (query2)``             | Match documents that match both queries (logical AND)               |\n+-------------------------------------+---------------------------------------------------------------------+\n| ``(query1) | (query2)``             | Match documents that match at least one of the queries (logical OR) |\n+-------------------------------------+---------------------------------------------------------------------+\n\nHandling Data\n-------------\n\nNext, let's look at some more ways to insert, update and retrieve data from\nyour database.\n\nInserting data\n..............\n\nAs already described you can insert a document using ``db.insert(...)``.\nIn case you want to insert multiple documents, you can use ``db.insert_multiple(...)``:\n\n>>> db.insert_multiple([\n        {'name': 'John', 'age': 22},\n        {'name': 'John', 'age': 37}])\n>>> db.insert_multiple({'int': 1, 'value': i} for i in range(2))\n\nAlso in some cases it may be useful to specify the document ID yourself when\ninserting data. You can do that by using the :class:`~tinydb.table.Document`\nclass:\n\n>>> db.insert(Document({'name': 'John', 'age': 22}, doc_id=12))\n12\n\nThe same is possible when using ``db.insert_multiple(...)``:\n\n>>> db.insert_multiple([\n    Document({'name': 'John', 'age': 22}, doc_id=12),\n    Document({'name': 'Jane', 'age': 24}, doc_id=14),\n])\n[12, 14]\n\n.. note::\n    Inserting a ``Document`` with an ID that already exists will result\n    in a ``ValueError`` being raised.\n\nUpdating data\n.............\n\nSometimes you want to update all documents in your database. In this case, you\ncan leave out the ``query`` argument:\n\n>>> db.update({'foo': 'bar'})\n\nWhen passing a dict to ``db.update(fields, query)``, it only allows you to\nupdate a document by adding or overwriting its values. But sometimes you may\nneed to e.g. remove one field or increment its value. In that case you can\npass a function instead of ``fields``:\n\n>>> from tinydb.operations import delete\n>>> db.update(delete('key1'), User.name == 'John')\n\nThis will remove the key ``key1`` from all matching documents. TinyDB comes\nwith these operations:\n\n- ``delete(key)``: delete a key from the document\n- ``increment(key)``: increment the value of a key\n- ``decrement(key)``: decrement the value of a key\n- ``add(key, value)``: add ``value`` to the value of a key (also works for strings)\n- ``subtract(key, value)``: subtract ``value`` from the value of a key\n- ``set(key, value)``: set ``key`` to ``value``\n\nOf course you also can write your own operations:\n\n>>> def your_operation(your_arguments):\n...     def transform(doc):\n...         # do something with the document\n...         # ...\n...     return transform\n...\n>>> db.update(your_operation(arguments), query)\n\nIn order to perform multiple update operations at once, you can use the\n``update_multiple`` method like this:\n\n>>> db.update_multiple([\n...     ({'int': 2}, where('char') == 'a'),\n...     ({'int': 4}, where('char') == 'b'),\n... ])\n\nYou also can mix normal updates with update operations:\n\n>>> db.update_multiple([\n...     ({'int': 2}, where('char') == 'a'),\n...     ({delete('int'), where('char') == 'b'),\n... ])\n\nData access and modification\n----------------------------\n\nUpserting data\n..............\n\nIn some cases you'll need a mix of both ``update`` and ``insert``: ``upsert``.\nThis operation is provided a document and a query. If it finds any documents\nmatching the query, they will be updated with the data from the provided document.\nOn the other hand, if no matching document is found, it inserts the provided\ndocument into the table:\n\n>>> db.upsert({'name': 'John', 'logged-in': True}, User.name == 'John')\n\nThis will update all users with the name John to have ``logged-in`` set to ``True``.\nIf no matching user is found, a new document is inserted with both the name set\nand the ``logged-in`` flag.\n\nTo use the ID of the document as matching criterion a :class:`~tinydb.table.Document`\nwith ``doc_id`` is passed instead of a query:\n\n>>> db.upsert(Document({'name': 'John', 'logged-in': True}, doc_id=12))\n\nRetrieving data\n...............\n\nThere are several ways to retrieve data from your database. For instance you\ncan get the number of stored documents:\n\n>>> len(db)\n3\n\n.. hint::\n    This will return the number of documents in the default table\n    (see the notes on the :ref:`default table <default_table>`).\n\nThen of course you can use ``db.search(...)`` as described in the :doc:`getting-started`\nsection. But sometimes you want to get only one matching document. Instead of using\n\n>>> try:\n...     result = db.search(User.name == 'John')[0]\n... except IndexError:\n...     pass\n\n\nyou can use ``db.get(...)``:\n\n>>> db.get(User.name == 'John')\n{'name': 'John', 'age': 22}\n>>> db.get(User.name == 'Bobby')\nNone\n\n.. caution::\n\n    If multiple documents match the query, probably a random one of them will\n    be returned!\n\nOften you don't want to search for documents but only know whether they are\nstored in the database. In this case ``db.contains(...)`` is your friend:\n\n>>> db.contains(User.name == 'John')\n\nIn a similar manner you can look up the number of documents matching a query:\n\n>>> db.count(User.name == 'John')\n2\n\nRecap\n^^^^^\n\nLet's summarize the ways to handle data:\n\n+-------------------------------+---------------------------------------------------------------+\n| **Inserting data**                                                                            |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.insert_multiple(...)``   | Insert multiple documents                                     |\n+-------------------------------+---------------------------------------------------------------+\n| **Updating data**                                                                             |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.update(operation, ...)`` | Update all matching documents with a special operation        |\n+-------------------------------+---------------------------------------------------------------+\n| **Retrieving data**                                                                           |\n+-------------------------------+---------------------------------------------------------------+\n| ``len(db)``                   | Get the number of documents in the database                   |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.get(query)``             | Get one document matching the query                           |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.contains(query)``        | Check if the database contains a matching document            |\n+-------------------------------+---------------------------------------------------------------+\n| ``db.count(query)``           | Get the number of matching documents                          |\n+-------------------------------+---------------------------------------------------------------+\n\n\n.. note::\n\n    This was a new feature in v3.6.0\n\n.. _document_ids:\n\nUsing Document IDs\n------------------\n\nInternally TinyDB associates an ID with every document you insert. It's returned\nafter inserting a document:\n\n>>> db.insert({'name': 'John', 'age': 22})\n3\n>>> db.insert_multiple([{...}, {...}, {...}])\n[4, 5, 6]\n\nIn addition you can get the ID of already inserted documents using\n``document.doc_id``. This works both with ``get`` and ``all``:\n\n>>> el = db.get(User.name == 'John')\n>>> el.doc_id\n3\n>>> el = db.all()[0]\n>>> el.doc_id\n1\n>>> el = db.all()[-1]\n>>> el.doc_id\n12\n\nDifferent TinyDB methods also work with IDs, namely: ``update``, ``remove``,\n``contains`` and ``get``. The first two also return a list of affected IDs.\n\n>>> db.update({'value': 2}, doc_ids=[1, 2])\n>>> db.contains(doc_id=1)\nTrue\n>>> db.remove(doc_ids=[1, 2])\n>>> db.get(doc_id=3)\n{...}\n>>> db.get(doc_ids=[1, 2])\n[{...}, {...}]\n\nUsing ``doc_id``/``doc_ids`` instead of ``Query()`` again is slightly faster\nin operation.\n\nRecap\n.....\n\nLet's sum up the way TinyDB supports working with IDs:\n\n+-------------------------------------+------------------------------------------------------------+\n| **Getting a document's ID**                                                                      |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.insert(...)``                  | Returns the inserted document's ID                         |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.insert_multiple(...)``         | Returns the inserted documents' ID                         |\n+-------------------------------------+------------------------------------------------------------+\n| ``document.doc_id``                 | Get the ID of a document fetched from the db               |\n+-------------------------------------+------------------------------------------------------------+\n| **Working with IDs**                                                                             |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.get(doc_id=...)``              | Get the document with the given ID                         |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.contains(doc_id=...)``         | Check if the db contains a document with the given         |\n|                                     | IDs                                                        |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.update({...}, doc_ids=[...])`` | Update all documents with the given IDs                    |\n+-------------------------------------+------------------------------------------------------------+\n| ``db.remove(doc_ids=[...])``        | Remove all documents with the given IDs                    |\n+-------------------------------------+------------------------------------------------------------+\n\n\nTables\n------\n\nTinyDB supports working with multiple tables. They behave just the same as\nthe ``TinyDB`` class. To create and use a table, use ``db.table(name)``.\n\n>>> table = db.table('table_name')\n>>> table.insert({'value': True})\n>>> table.all()\n[{'value': True}]\n>>> for row in table:\n>>>     print(row)\n{'value': True}\n\nTo remove a table from a database, use:\n\n>>> db.drop_table('table_name')\n\nIf on the other hand you want to remove all tables, use the counterpart:\n\n>>> db.drop_tables()\n\nFinally, you can get a list with the names of all tables in your database:\n\n>>> db.tables()\n{'_default', 'table_name'}\n\n.. _default_table:\n\nDefault Table\n.............\n\nTinyDB uses a table named ``_default`` as the default table. All operations\non the database object (like ``db.insert(...)``) operate on this table.\nThe name of this table can be modified by setting the ``default_table_name``\nclass variable to modify the default table name for all instances:\n\n>>> #1: for a single instance only\n>>> db = TinyDB(storage=SomeStorage)\n>>> db.default_table_name = 'my-default'\n>>> #2: for all instances\n>>> TinyDB.default_table_name = 'my-default'\n\n.. _query_caching:\n\nQuery Caching\n.............\n\nTinyDB caches query result for performance. That way re-running a query won't\nhave to read the data from the storage as long as the database hasn't been\nmodified. You can optimize the query cache size by passing the ``cache_size``\nto the ``table(...)`` function:\n\n>>> table = db.table('table_name', cache_size=30)\n\n.. hint:: You can set ``cache_size`` to ``None`` to make the cache unlimited in\n   size. Also, you can set ``cache_size`` to 0 to disable it.\n\n.. hint:: It's not possible to open the same table multiple times with different\n   settings. After the first invocation, all the subsequent calls will return\n   the same table with the same settings as the first one.\n\n.. hint:: The TinyDB query cache doesn't check if the underlying storage\n   that the database uses has been modified by an external process. In this\n   case the query cache may return outdated results. To clear the cache and\n   read data from the storage again you can use ``db.clear_cache()``.\n\n.. hint:: When using an unlimited cache size and ``test()`` queries, TinyDB\n   will store a reference to the test function. As a result of that behavior\n   long-running applications that use ``lambda`` functions as a test function\n   may experience memory leaks.\n\nStorage & Middleware\n--------------------\n\nStorage Types\n.............\n\nTinyDB comes with two storage types: JSON and in-memory. By\ndefault TinyDB stores its data in JSON files so you have to specify the path\nwhere to store it:\n\n>>> from tinydb import TinyDB, where\n>>> db = TinyDB('path/to/db.json')\n\nTo use the in-memory storage, use:\n\n>>> from tinydb.storages import MemoryStorage\n>>> db = TinyDB(storage=MemoryStorage)\n\n.. hint::\n    All arguments except for the ``storage`` argument are forwarded to the\n    underlying storage. For the JSON storage you can use this to pass\n    additional keyword arguments to Python's\n    `json.dumps(...) <https://docs.python.org/3/library/json.html#json.dumps>`_\n    method. For example, you can set it to create prettified JSON files like\n    this:\n\n    >>> db = TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': '))\n\n.. note::\n    ``JSONStorage`` forwards ``**kwargs`` to ``json.dumps``. **Never** pass\n    user-controlled values for callable arguments (e.g. ``default`` or\n    ``cls``) as they are executed in-process on every write operation.\n\nTo modify the default storage for all ``TinyDB`` instances, set the\n``default_storage_class`` class variable:\n\n>>> TinyDB.default_storage_class = MemoryStorage\n\nIn case you need to access the storage instance directly, you can use the\n``storage`` property of your TinyDB instance. This may be useful to call\nmethod directly on the storage or middleware:\n\n>>> db = TinyDB(storage=CachingMiddleware(MemoryStorage))\n<tinydb.middlewares.CachingMiddleware at 0x10991def0>\n>>> db.storage.flush()\n\nMiddleware\n..........\n\nMiddleware wraps around existing storage allowing you to customize their\nbehaviour.\n\n>>> from tinydb.storages import JSONStorage\n>>> from tinydb.middlewares import CachingMiddleware\n>>> db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))\n\n.. hint::\n    You can nest middleware:\n\n    >>> db = TinyDB('/path/to/db.json',\n                    storage=FirstMiddleware(SecondMiddleware(JSONStorage)))\n\nCachingMiddleware\n^^^^^^^^^^^^^^^^^\n\nThe ``CachingMiddleware`` improves speed by reducing disk I/O. It caches all\nread operations and writes data to disk after a configured number of\nwrite operations.\n\nTo make sure that all data is safely written when closing the table, use one\nof these ways:\n\n.. code-block:: python\n\n    # Using a context manager:\n    with database as db:\n        # Your operations\n\n.. code-block:: python\n\n    # Using the close function\n    db.close()\n\n.. _mypy_type_checking:\n\nMyPy Type Checking\n------------------\n\nTinyDB comes with type annotations that MyPy can use to make sure you're using\nthe API correctly. Unfortunately, MyPy doesn't understand all code patterns\nthat TinyDB uses. For that reason TinyDB ships a MyPy plugin that helps\ncorrectly type checking code that uses TinyDB. To use it, add it to the\nplugins list in the `MyPy configuration file\n<https://mypy.readthedocs.io/en/latest/config_file.html>`_\n(typically located in ``setup.cfg`` or ``mypy.ini``):\n\n.. code-block:: ini\n\n    [mypy]\n    plugins = tinydb.mypy_plugin\n\nWhat's next\n-----------\n\nCongratulations, you've made through the user guide! Now go and build something\nawesome or dive deeper into TinyDB with these resources:\n\n- Want to learn how to customize TinyDB (storages, middlewares) and what\n  extensions exist? Check out :doc:`extend` and :doc:`extensions`.\n- Want to study the API in detail? Read :doc:`api`.\n- Interested in contributing to the TinyDB development guide? Go on to the\n  :doc:`contribute`.\n"
  },
  {
    "path": "mypy.ini",
    "content": "[mypy]\nplugins = tinydb/mypy_plugin.py\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"tinydb\"\nversion = \"4.8.2\"\ndescription = \"TinyDB is a tiny, document oriented database optimized for your happiness :)\"\nauthors = [{ name = \"Markus Siemens\", email = \"markus@m-siemens.de\" }]\nrequires-python = \">=3.10,<4\"\nreadme = \"README.rst\"\nlicense = \"MIT\"\nkeywords = [\n    \"database\",\n    \"nosql\",\n]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: System Administrators\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Topic :: Database\",\n    \"Topic :: Database :: Database Engines/Servers\",\n    \"Topic :: Utilities\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n    \"Operating System :: OS Independent\",\n    \"Typing :: Typed\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/msiemens/tinydb\"\nDocumentation = \"https://tinydb.readthedocs.org/\"\nChangelog = \"https://tinydb.readthedocs.io/en/latest/changelog.html\"\nIssues = \"https://github.com/msiemens/tinydb/issues\"\n\n[dependency-groups]\ndev = [\n    \"pytest~=9.0\",\n    \"pytest-pycodestyle~=2.3\",\n    \"pytest-cov~=7.0\",\n    \"pycodestyle~=2.10\",\n    \"sphinx~=8.1\",\n    \"coveralls~=4.0\",\n    \"pyyaml~=6.0\",\n    \"pytest-mypy~=1.0 ; platform_python_implementation != 'PyPy'\",\n    \"types-PyYAML~=6.0\",\n]\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"tinydb\",\n    \"tests\",\n]\n\n[tool.hatch.build.targets.wheel]\ninclude = [\"tinydb\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\naddopts=--verbose --cov-append --cov-report term --cov tinydb"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "import os.path\nimport tempfile\nfrom pathlib import Path\n\nimport pytest  # type: ignore\n\nfrom tinydb.middlewares import CachingMiddleware\nfrom tinydb.storages import MemoryStorage\nfrom tinydb import TinyDB, JSONStorage\n\n\n@pytest.fixture(params=['memory', 'json'])\ndef db(request, tmp_path: Path):\n    if request.param == 'json':\n        db_ = TinyDB(tmp_path / 'test.db', storage=JSONStorage)\n    else:\n        db_ = TinyDB(storage=MemoryStorage)\n\n    db_.drop_tables()\n    db_.insert_multiple({'int': 1, 'char': c} for c in 'abc')\n\n    yield db_\n\n\n@pytest.fixture\ndef storage():\n    return CachingMiddleware(MemoryStorage)()\n"
  },
  {
    "path": "tests/test_middlewares.py",
    "content": "import os\n\nfrom tinydb import TinyDB\nfrom tinydb.middlewares import CachingMiddleware\nfrom tinydb.storages import MemoryStorage, JSONStorage\n\ndoc = {'none': [None, None], 'int': 42, 'float': 3.1415899999999999,\n       'list': ['LITE', 'RES_ACID', 'SUS_DEXT'],\n       'dict': {'hp': 13, 'sp': 5},\n       'bool': [True, False, True, False]}\n\n\ndef test_caching(storage):\n    # Write contents\n    storage.write(doc)\n\n    # Verify contents\n    assert doc == storage.read()\n\n\ndef test_caching_read():\n    db = TinyDB(storage=CachingMiddleware(MemoryStorage))\n    assert db.all() == []\n\n\ndef test_caching_write_many(storage):\n    storage.WRITE_CACHE_SIZE = 3\n\n    # Storage should be still empty\n    assert storage.memory is None\n\n    # Write contents\n    for x in range(2):\n        storage.write(doc)\n        assert storage.memory is None  # Still cached\n\n    storage.write(doc)\n\n    # Verify contents: Cache should be emptied and written to storage\n    assert storage.memory\n\n\ndef test_caching_flush(storage):\n    # Write contents\n    for _ in range(CachingMiddleware.WRITE_CACHE_SIZE - 1):\n        storage.write(doc)\n\n    # Not yet flushed...\n    assert storage.memory is None\n\n    storage.write(doc)\n\n    # Verify contents: Cache should be emptied and written to storage\n    assert storage.memory\n\n\ndef test_caching_flush_manually(storage):\n    # Write contents\n    storage.write(doc)\n\n    storage.flush()\n\n    # Verify contents: Cache should be emptied and written to storage\n    assert storage.memory\n\n\ndef test_caching_write(storage):\n    # Write contents\n    storage.write(doc)\n\n    storage.close()\n\n    # Verify contents: Cache should be emptied and written to storage\n    assert storage.storage.memory\n\n\ndef test_nested():\n    storage = CachingMiddleware(MemoryStorage)\n    storage()  # Initialization\n\n    # Write contents\n    storage.write(doc)\n\n    # Verify contents\n    assert doc == storage.read()\n\n\ndef test_caching_json_write(tmpdir):\n    path = str(tmpdir.join('test.db'))\n\n    with TinyDB(path, storage=CachingMiddleware(JSONStorage)) as db:\n        db.insert({'key': 'value'})\n\n    # Verify database filesize\n    statinfo = os.stat(path)\n    assert statinfo.st_size != 0\n\n    # Assert JSON file has been closed\n    assert db._storage._handle.closed\n\n    del db\n\n    # Reopen database\n    with TinyDB(path, storage=CachingMiddleware(JSONStorage)) as db:\n        assert db.all() == [{'key': 'value'}]\n"
  },
  {
    "path": "tests/test_operations.py",
    "content": "from tinydb import where\nfrom tinydb.operations import delete, increment, decrement, add, subtract, set\n\n\ndef test_delete(db):\n    db.update(delete('int'), where('char') == 'a')\n    assert 'int' not in db.get(where('char') == 'a')\n\n\ndef test_add_int(db):\n    db.update(add('int', 5), where('char') == 'a')\n    assert db.get(where('char') == 'a')['int'] == 6\n\n\ndef test_add_str(db):\n    db.update(add('char', 'xyz'), where('char') == 'a')\n    assert db.get(where('char') == 'axyz')['int'] == 1\n\n\ndef test_subtract(db):\n    db.update(subtract('int', 5), where('char') == 'a')\n    assert db.get(where('char') == 'a')['int'] == -4\n\n\ndef test_set(db):\n    db.update(set('char', 'xyz'), where('char') == 'a')\n    assert db.get(where('char') == 'xyz')['int'] == 1\n\n\ndef test_increment(db):\n    db.update(increment('int'), where('char') == 'a')\n    assert db.get(where('char') == 'a')['int'] == 2\n\n\ndef test_decrement(db):\n    db.update(decrement('int'), where('char') == 'a')\n    assert db.get(where('char') == 'a')['int'] == 0\n"
  },
  {
    "path": "tests/test_queries.py",
    "content": "import re\n\nimport pytest\n\nfrom tinydb.queries import Query, where\n\n\ndef test_no_path():\n    with pytest.raises(ValueError):\n        _ = Query() == 2\n\n\ndef test_path_exists():\n    query = Query()['value'].exists()\n    assert query == where('value').exists()\n    assert query({'value': 1})\n    assert not query({'something': 1})\n    assert hash(query)\n    assert hash(query) != hash(where('asd'))\n\n    query = Query()['value']['val'].exists()\n    assert query == where('value')['val'].exists()\n    assert query({'value': {'val': 2}})\n    assert not query({'value': 1})\n    assert not query({'value': {'asd': 1}})\n    assert not query({'something': 1})\n    assert hash(query)\n    assert hash(query) != hash(where('asd'))\n\n\ndef test_path_and():\n    query = Query()['value'].exists() & (Query()['value'] == 5)\n    assert query({'value': 5})\n    assert not query({'value': 10})\n    assert not query({'something': 1})\n    assert hash(query)\n    assert hash(query) != hash(where('value'))\n\n\ndef test_callable_in_path_with_map():\n    double = lambda x: x + x\n    query = Query().value.map(double) == 10\n    assert query({'value': 5})\n    assert not query({'value': 10})\n\n\ndef test_callable_in_path_with_chain():\n    rekey = lambda x: {'y': x['a'], 'z': x['b']}\n    query = Query().map(rekey).z == 10\n    assert query({'a': 5, 'b': 10})\n\n\ndef test_eq():\n    query = Query().value == 1\n    assert query({'value': 1})\n    assert not query({'value': 2})\n    assert hash(query)\n\n    query = Query().value == [0, 1]\n    assert query({'value': [0, 1]})\n    assert not query({'value': [0, 1, 2]})\n    assert hash(query)\n\n\ndef test_ne():\n    query = Query().value != 1\n    assert query({'value': 0})\n    assert query({'value': 2})\n    assert not query({'value': 1})\n    assert hash(query)\n\n    query = Query().value != [0, 1]\n    assert query({'value': [0, 1, 2]})\n    assert not query({'value': [0, 1]})\n    assert hash(query)\n\n\ndef test_lt():\n    query = Query().value < 1\n    assert query({'value': 0})\n    assert not query({'value': 1})\n    assert not query({'value': 2})\n    assert hash(query)\n\n\ndef test_le():\n    query = Query().value <= 1\n    assert query({'value': 0})\n    assert query({'value': 1})\n    assert not query({'value': 2})\n    assert hash(query)\n\n\ndef test_gt():\n    query = Query().value > 1\n    assert query({'value': 2})\n    assert not query({'value': 1})\n    assert hash(query)\n\n\ndef test_ge():\n    query = Query().value >= 1\n    assert query({'value': 2})\n    assert query({'value': 1})\n    assert not query({'value': 0})\n    assert hash(query)\n\n\ndef test_or():\n    query = (\n            (Query().val1 == 1) |\n            (Query().val2 == 2)\n    )\n    assert query({'val1': 1})\n    assert query({'val2': 2})\n    assert query({'val1': 1, 'val2': 2})\n    assert not query({'val1': '', 'val2': ''})\n    assert hash(query)\n\n\ndef test_and():\n    query = (\n            (Query().val1 == 1) &\n            (Query().val2 == 2)\n    )\n    assert query({'val1': 1, 'val2': 2})\n    assert not query({'val1': 1})\n    assert not query({'val2': 2})\n    assert not query({'val1': '', 'val2': ''})\n    assert hash(query)\n\n\ndef test_not():\n    query = ~ (Query().val1 == 1)\n    assert query({'val1': 5, 'val2': 2})\n    assert not query({'val1': 1, 'val2': 2})\n    assert hash(query)\n\n    query = (\n            (~ (Query().val1 == 1)) &\n            (Query().val2 == 2)\n    )\n    assert query({'val1': '', 'val2': 2})\n    assert query({'val2': 2})\n    assert not query({'val1': 1, 'val2': 2})\n    assert not query({'val1': 1})\n    assert not query({'val1': '', 'val2': ''})\n    assert hash(query)\n\n\ndef test_has_key():\n    query = Query().val3.exists()\n\n    assert query({'val3': 1})\n    assert not query({'val1': 1, 'val2': 2})\n    assert hash(query)\n\n\ndef test_regex():\n    query = Query().val.matches(r'\\d{2}\\.')\n\n    assert query({'val': '42.'})\n    assert not query({'val': '44'})\n    assert not query({'val': 'ab.'})\n    assert not query({'val': 155})\n    assert not query({'val': False})\n    assert not query({'': None})\n    assert hash(query)\n\n    query = Query().val.search(r'\\d+')\n\n    assert query({'val': 'ab3'})\n    assert not query({'val': 'abc'})\n    assert not query({'val': ''})\n    assert not query({'val': True})\n    assert not query({'': None})\n    assert hash(query)\n\n    query = Query().val.search(r'JOHN', flags=re.IGNORECASE)\n    assert query({'val': 'john'})\n    assert query({'val': 'xJohNx'})\n    assert not query({'val': 'JOH'})\n    assert not query({'val': 12})\n    assert not query({'': None})\n    assert hash(query)\n\n\ndef test_custom():\n    def test(value):\n        return value == 42\n\n    query = Query().val.test(test)\n\n    assert query({'val': 42})\n    assert not query({'val': 40})\n    assert not query({'val': '44'})\n    assert not query({'': None})\n    assert hash(query)\n\n    def in_list(value, l):\n        return value in l\n\n    query = Query().val.test(in_list, tuple([25, 35]))\n    assert not query({'val': 20})\n    assert query({'val': 25})\n    assert not query({'val': 30})\n    assert query({'val': 35})\n    assert not query({'val': 36})\n    assert hash(query)\n\n\ndef test_custom_with_params():\n    def test(value, minimum, maximum):\n        return minimum <= value <= maximum\n\n    query = Query().val.test(test, 1, 10)\n\n    assert query({'val': 5})\n    assert not query({'val': 0})\n    assert not query({'val': 11})\n    assert not query({'': None})\n    assert hash(query)\n\n\ndef test_any():\n    query = Query().followers.any(Query().name == 'don')\n\n    assert query({'followers': [{'name': 'don'}, {'name': 'john'}]})\n    assert not query({'followers': 1})\n    assert not query({})\n    assert hash(query)\n\n    query = Query().followers.any(Query().num.matches('\\\\d+'))\n    assert query({'followers': [{'num': '12'}, {'num': 'abc'}]})\n    assert not query({'followers': [{'num': 'abc'}]})\n    assert hash(query)\n\n    query = Query().followers.any(['don', 'jon'])\n    assert query({'followers': ['don', 'greg', 'bill']})\n    assert not query({'followers': ['greg', 'bill']})\n    assert not query({})\n    assert hash(query)\n\n    query = Query().followers.any([{'name': 'don'}, {'name': 'john'}])\n    assert query({'followers': [{'name': 'don'}, {'name': 'greg'}]})\n    assert not query({'followers': [{'name': 'greg'}]})\n    assert hash(query)\n\n\ndef test_all():\n    query = Query().followers.all(Query().name == 'don')\n    assert query({'followers': [{'name': 'don'}]})\n    assert not query({'followers': [{'name': 'don'}, {'name': 'john'}]})\n    assert hash(query)\n\n    query = Query().followers.all(Query().num.matches('\\\\d+'))\n    assert query({'followers': [{'num': '123'}, {'num': '456'}]})\n    assert not query({'followers': [{'num': '123'}, {'num': 'abc'}]})\n    assert hash(query)\n\n    query = Query().followers.all(['don', 'john'])\n    assert query({'followers': ['don', 'john', 'greg']})\n    assert not query({'followers': ['don', 'greg']})\n    assert not query({})\n    assert hash(query)\n\n    query = Query().followers.all([{'name': 'jane'}, {'name': 'john'}])\n    assert query({'followers': [{'name': 'john'}, {'name': 'jane'}]})\n    assert query({'followers': [{'name': 'john'},\n                                {'name': 'jane'},\n                                {'name': 'bob'}]})\n    assert not query({'followers': [{'name': 'john'}, {'name': 'bob'}]})\n    assert hash(query)\n\n\ndef test_has():\n    query = Query().key1.key2.exists()\n    str(query)  # This used to cause a bug...\n\n    assert query({'key1': {'key2': {'key3': 1}}})\n    assert query({'key1': {'key2': 1}})\n    assert not query({'key1': 3})\n    assert not query({'key1': {'key1': 1}})\n    assert not query({'key2': {'key1': 1}})\n    assert hash(query)\n\n    query = Query().key1.key2 == 1\n\n    assert query({'key1': {'key2': 1}})\n    assert not query({'key1': {'key2': 2}})\n    assert hash(query)\n\n    # Nested has: key exists\n    query = Query().key1.key2.key3.exists()\n    assert query({'key1': {'key2': {'key3': 1}}})\n    # Not a dict\n    assert not query({'key1': 1})\n    assert not query({'key1': {'key2': 1}})\n    # Wrong key\n    assert not query({'key1': {'key2': {'key0': 1}}})\n    assert not query({'key1': {'key0': {'key3': 1}}})\n    assert not query({'key0': {'key2': {'key3': 1}}})\n\n    assert hash(query)\n\n    # Nested has: check for value\n    query = Query().key1.key2.key3 == 1\n    assert query({'key1': {'key2': {'key3': 1}}})\n    assert not query({'key1': {'key2': {'key3': 0}}})\n    assert hash(query)\n\n    # Test special methods: regex matches\n    query = Query().key1.value.matches(r'\\d+')\n    assert query({'key1': {'value': '123'}})\n    assert not query({'key2': {'value': '123'}})\n    assert not query({'key2': {'value': 'abc'}})\n    assert hash(query)\n\n    # Test special methods: regex contains\n    query = Query().key1.value.search(r'\\d+')\n    assert query({'key1': {'value': 'a2c'}})\n    assert not query({'key2': {'value': 'a2c'}})\n    assert not query({'key2': {'value': 'abc'}})\n    assert hash(query)\n\n    # Test special methods: nested has and regex matches\n    query = Query().key1.x.y.matches(r'\\d+')\n    assert query({'key1': {'x': {'y': '123'}}})\n    assert not query({'key1': {'x': {'y': 'abc'}}})\n    assert hash(query)\n\n    # Test special method: nested has and regex contains\n    query = Query().key1.x.y.search(r'\\d+')\n    assert query({'key1': {'x': {'y': 'a2c'}}})\n    assert not query({'key1': {'x': {'y': 'abc'}}})\n    assert hash(query)\n\n    # Test special methods: custom test\n    query = Query().key1.int.test(lambda x: x == 3)\n    assert query({'key1': {'int': 3}})\n    assert hash(query)\n\n\ndef test_one_of():\n    query = Query().key1.one_of(['value 1', 'value 2'])\n    assert query({'key1': 'value 1'})\n    assert query({'key1': 'value 2'})\n    assert not query({'key1': 'value 3'})\n\n\ndef test_hash():\n    d = {\n        Query().key1 == 2: True,\n        Query().key1.key2.key3.exists(): True,\n        Query().key1.exists() & Query().key2.exists(): True,\n        Query().key1.exists() | Query().key2.exists(): True,\n    }\n\n    assert (Query().key1 == 2) in d\n    assert (Query().key1.key2.key3.exists()) in d\n    assert (Query()['key1.key2'].key3.exists()) not in d\n\n    # Commutative property of & and |\n    assert (Query().key1.exists() & Query().key2.exists()) in d\n    assert (Query().key2.exists() & Query().key1.exists()) in d\n    assert (Query().key1.exists() | Query().key2.exists()) in d\n    assert (Query().key2.exists() | Query().key1.exists()) in d\n\n\ndef test_orm_usage():\n    data = {'name': 'John', 'age': {'year': 2000}}\n\n    User = Query()\n    query1 = User.name == 'John'\n    query2 = User.age.year == 2000\n    assert query1(data)\n    assert query2(data)\n\n\ndef test_repr():\n    Fruit = Query()\n\n    assert repr(Fruit) == \"Query()\"\n    assert repr(Fruit.type == 'peach') == \"QueryImpl('==', ('type',), 'peach')\"\n\n\ndef test_subclass():\n    # Test that a new query test method in a custom subclass is properly usable\n    class MyQueryClass(Query):\n        def equal_double(self, rhs):\n            return self._generate_test(\n                lambda value: value == rhs * 2,\n                ('equal_double', self._path, rhs)\n            )\n\n    query = MyQueryClass().val.equal_double('42')\n\n    assert query({'val': '4242'})\n    assert not query({'val': '42'})\n    assert not query({'': None})\n    assert hash(query)\n\n\ndef test_noop():\n    query = Query().noop()\n\n    assert query({'foo': True})\n    assert query({'foo': None})\n    assert query({})\n\n\ndef test_equality():\n    q = Query()\n    assert (q.foo == 2) != 0\n    assert (q.foo == 'yes') != ''\n\n\ndef test_empty_query_error():\n    with pytest.raises(RuntimeError, match='Empty query was evaluated'):\n        Query()({})\n\n\ndef test_fragment():\n    query = Query().fragment({'a': 4, 'b': True})\n\n    assert query({'a': 4, 'b': True, 'c': 'yes'})\n    assert not query({'a': 4, 'c': 'yes'})\n    assert not query({'b': True, 'c': 'yes'})\n    assert not query({'a': 5, 'b': True, 'c': 'yes'})\n    assert not query({'a': 4, 'b': 'no', 'c': 'yes'})\n\n\ndef test_fragment_with_path():\n    query = Query().doc.fragment({'a': 4, 'b': True})\n\n    assert query({'doc': {'a': 4, 'b': True, 'c': 'yes'}})\n    assert not query({'a': 4, 'b': True, 'c': 'yes'})\n    assert not query({'doc': {'a': 4, 'c': 'yes'}})\n\n\ndef test_get_item():\n    query = Query()['test'] == 1\n\n    assert query({'test': 1})\n    assert not query({'test': 0})\n"
  },
  {
    "path": "tests/test_storages.py",
    "content": "import json\nimport os\nimport random\nimport tempfile\n\nimport pytest\n\nfrom tinydb import TinyDB, where\nfrom tinydb.storages import JSONStorage, MemoryStorage, Storage, touch\nfrom tinydb.table import Document\n\nrandom.seed()\n\ndoc = {'none': [None, None], 'int': 42, 'float': 3.1415899999999999,\n       'list': ['LITE', 'RES_ACID', 'SUS_DEXT'],\n       'dict': {'hp': 13, 'sp': 5},\n       'bool': [True, False, True, False]}\n\n\ndef test_json(tmpdir):\n    # Write contents\n    path = str(tmpdir.join('test.db'))\n    storage = JSONStorage(path)\n    storage.write(doc)\n\n    # Verify contents\n    assert doc == storage.read()\n    storage.close()\n\n\ndef test_json_kwargs(tmpdir):\n    db_file = tmpdir.join('test.db')\n    db = TinyDB(str(db_file), sort_keys=True, indent=4, separators=(',', ': '))\n\n    # Write contents\n    db.insert({'b': 1})\n    db.insert({'a': 1})\n\n    assert db_file.read() == '''{\n    \"_default\": {\n        \"1\": {\n            \"b\": 1\n        },\n        \"2\": {\n            \"a\": 1\n        }\n    }\n}'''\n    db.close()\n\n\ndef test_json_readwrite(tmpdir):\n    \"\"\"\n    Regression test for issue #1\n    \"\"\"\n    path = str(tmpdir.join('test.db'))\n\n    # Create TinyDB instance\n    db = TinyDB(path, storage=JSONStorage)\n\n    item = {'name': 'A very long entry'}\n    item2 = {'name': 'A short one'}\n\n    def get(s):\n        return db.get(where('name') == s)\n\n    db.insert(item)\n    assert get('A very long entry') == item\n\n    db.remove(where('name') == 'A very long entry')\n    assert get('A very long entry') is None\n\n    db.insert(item2)\n    assert get('A short one') == item2\n\n    db.remove(where('name') == 'A short one')\n    assert get('A short one') is None\n\n    db.close()\n\n\ndef test_json_read(tmpdir):\n    r\"\"\"Open a database only for reading\"\"\"\n    path = str(tmpdir.join('test.db'))\n    with pytest.raises(FileNotFoundError):\n        db = TinyDB(path, storage=JSONStorage, access_mode='r')\n    # Create small database\n    db = TinyDB(path, storage=JSONStorage)\n    db.insert({'b': 1})\n    db.insert({'a': 1})\n    db.close()\n    # Access in read mode\n    db = TinyDB(path, storage=JSONStorage, access_mode='r')\n    assert db.get(where('a') == 1) == {'a': 1}  # reading is fine\n    with pytest.raises(IOError):\n        db.insert({'c': 1})  # writing is not\n    db.close()\n\n\ndef test_create_dirs():\n    temp_dir = tempfile.gettempdir()\n\n    while True:\n        dname = os.path.join(temp_dir, str(random.getrandbits(20)))\n        if not os.path.exists(dname):\n            db_dir = dname\n            db_file = os.path.join(db_dir, 'db.json')\n            break\n\n    with pytest.raises(IOError):\n        JSONStorage(db_file)\n\n    JSONStorage(db_file, create_dirs=True).close()\n    assert os.path.exists(db_file)\n\n    # Use create_dirs with already existing directory\n    JSONStorage(db_file, create_dirs=True).close()\n    assert os.path.exists(db_file)\n\n    os.remove(db_file)\n    os.rmdir(db_dir)\n\n\ndef test_json_invalid_directory():\n    with pytest.raises(IOError):\n        with TinyDB('/this/is/an/invalid/path/db.json', storage=JSONStorage):\n            pass\n\n\ndef test_in_memory():\n    # Write contents\n    storage = MemoryStorage()\n    storage.write(doc)\n\n    # Verify contents\n    assert doc == storage.read()\n\n    # Test case for #21\n    other = MemoryStorage()\n    other.write({})\n    assert other.read() != storage.read()\n\n\ndef test_in_memory_close():\n    with TinyDB(storage=MemoryStorage) as db:\n        db.insert({})\n\n\ndef test_custom():\n    # noinspection PyAbstractClass\n    class MyStorage(Storage):\n        pass\n\n    with pytest.raises(TypeError):\n        MyStorage()\n\n\ndef test_read_once():\n    count = 0\n\n    # noinspection PyAbstractClass\n    class MyStorage(Storage):\n        def __init__(self):\n            self.memory = None\n\n        def read(self):\n            nonlocal count\n            count += 1\n\n            return self.memory\n\n        def write(self, data):\n            self.memory = data\n\n    with TinyDB(storage=MyStorage) as db:\n        assert count == 0\n\n        db.table(db.default_table_name)\n\n        assert count == 0\n\n        db.all()\n\n        assert count == 1\n\n        db.insert({'foo': 'bar'})\n\n        assert count == 3  # One for getting the next ID, one for the insert\n\n        db.all()\n\n        assert count == 4\n\n\ndef test_custom_with_exception():\n    class MyStorage(Storage):\n        def read(self):\n            pass\n\n        def write(self, data):\n            pass\n\n        def __init__(self):\n            raise ValueError()\n\n        def close(self):\n            raise RuntimeError()\n\n    with pytest.raises(ValueError):\n        with TinyDB(storage=MyStorage) as db:\n            pass\n\n\ndef test_yaml(tmpdir):\n    \"\"\"\n    :type tmpdir: py._path.local.LocalPath\n    \"\"\"\n\n    try:\n        import yaml\n    except ImportError:\n        return pytest.skip('PyYAML not installed')\n\n    def represent_doc(dumper, data):\n        # Represent `Document` objects as their dict's string representation\n        # which PyYAML understands\n        return dumper.represent_data(dict(data))\n\n    yaml.add_representer(Document, represent_doc)\n\n    class YAMLStorage(Storage):\n        def __init__(self, filename):\n            self.filename = filename\n            touch(filename, False)\n\n        def read(self):\n            with open(self.filename) as handle:\n                data = yaml.safe_load(handle.read())\n                return data\n\n        def write(self, data):\n            with open(self.filename, 'w') as handle:\n                yaml.dump(data, handle)\n\n        def close(self):\n            pass\n\n    # Write contents\n    path = str(tmpdir.join('test.db'))\n    db = TinyDB(path, storage=YAMLStorage)\n    db.insert(doc)\n    assert db.all() == [doc]\n\n    db.update({'name': 'foo'})\n\n    assert '!' not in tmpdir.join('test.db').read()\n\n    assert db.contains(where('name') == 'foo')\n    assert len(db) == 1\n\n\ndef test_encoding(tmpdir):\n    japanese_doc = {\"Test\": u\"こんにちは世界\"}\n\n    path = str(tmpdir.join('test.db'))\n    # cp936 is used for japanese encodings\n    jap_storage = JSONStorage(path, encoding=\"cp936\")\n    jap_storage.write(japanese_doc)\n\n    try:\n        exception = json.decoder.JSONDecodeError\n    except AttributeError:\n        exception = ValueError\n\n    with pytest.raises(exception):\n        # cp037 is used for english encodings\n        eng_storage = JSONStorage(path, encoding=\"cp037\")\n        eng_storage.read()\n\n    jap_storage = JSONStorage(path, encoding=\"cp936\")\n    assert japanese_doc == jap_storage.read()\n"
  },
  {
    "path": "tests/test_tables.py",
    "content": "import re\n\nimport pytest\n\nfrom tinydb import where\n\n\ndef test_next_id(db):\n    db.truncate()\n\n    assert db._get_next_id() == 1\n    assert db._get_next_id() == 2\n    assert db._get_next_id() == 3\n\n\ndef test_tables_list(db):\n    db.table('table1').insert({'a': 1})\n    db.table('table2').insert({'a': 1})\n\n    assert db.tables() == {'_default', 'table1', 'table2'}\n\n\ndef test_one_table(db):\n    table1 = db.table('table1')\n\n    table1.insert_multiple({'int': 1, 'char': c} for c in 'abc')\n\n    assert table1.get(where('int') == 1)['char'] == 'a'\n    assert table1.get(where('char') == 'b')['char'] == 'b'\n\n\ndef test_multiple_tables(db):\n    table1 = db.table('table1')\n    table2 = db.table('table2')\n    table3 = db.table('table3')\n\n    table1.insert({'int': 1, 'char': 'a'})\n    table2.insert({'int': 1, 'char': 'b'})\n    table3.insert({'int': 1, 'char': 'c'})\n\n    assert table1.count(where('char') == 'a') == 1\n    assert table2.count(where('char') == 'b') == 1\n    assert table3.count(where('char') == 'c') == 1\n\n    db.drop_tables()\n\n    assert len(table1) == 0\n    assert len(table2) == 0\n    assert len(table3) == 0\n\n\ndef test_caching(db):\n    table1 = db.table('table1')\n    table2 = db.table('table1')\n\n    assert table1 is table2\n\n\ndef test_query_cache(db):\n    query1 = where('int') == 1\n\n    assert db.count(query1) == 3\n    assert query1 in db._query_cache\n\n    assert db.count(query1) == 3\n    assert query1 in db._query_cache\n\n    query2 = where('int') == 0\n\n    assert db.count(query2) == 0\n    assert query2 in db._query_cache\n\n    assert db.count(query2) == 0\n    assert query2 in db._query_cache\n\n\ndef test_query_cache_with_mutable_callable(db):\n    table = db.table('table')\n    table.insert({'val': 5})\n\n    mutable = 5\n    increase = lambda x: x + mutable\n\n    assert where('val').is_cacheable()\n    assert not where('val').map(increase).is_cacheable()\n    assert not (where('val').map(increase) == 10).is_cacheable()\n\n    search = where('val').map(increase) == 10\n    assert table.count(search) == 1\n\n    # now `increase` would yield 15, not 10\n    mutable = 10\n\n    assert table.count(search) == 0\n    assert len(table._query_cache) == 0\n\n\ndef test_zero_cache_size(db):\n    table = db.table('table3', cache_size=0)\n    query = where('int') == 1\n\n    table.insert({'int': 1})\n    table.insert({'int': 1})\n\n    assert table.count(query) == 2\n    assert table.count(where('int') == 2) == 0\n    assert len(table._query_cache) == 0\n\n\ndef test_query_cache_size(db):\n    table = db.table('table3', cache_size=1)\n    query = where('int') == 1\n\n    table.insert({'int': 1})\n    table.insert({'int': 1})\n\n    assert table.count(query) == 2\n    assert table.count(where('int') == 2) == 0\n    assert len(table._query_cache) == 1\n\n\ndef test_lru_cache(db):\n    # Test integration into TinyDB\n    table = db.table('table3', cache_size=2)\n    query = where('int') == 1\n\n    table.search(query)\n    table.search(where('int') == 2)\n    table.search(where('int') == 3)\n    assert query not in table._query_cache\n\n    table.remove(where('int') == 1)\n    assert not table._query_cache.lru\n\n    table.search(query)\n\n    assert len(table._query_cache) == 1\n    table.clear_cache()\n    assert len(table._query_cache) == 0\n\n\ndef test_table_is_iterable(db):\n    table = db.table('table1')\n\n    table.insert_multiple({'int': i} for i in range(3))\n\n    assert [r for r in table] == table.all()\n\n\ndef test_table_name(db):\n    name = 'table3'\n    table = db.table(name)\n    assert name == table.name\n\n    with pytest.raises(AttributeError):\n        table.name = 'foo'\n\n\ndef test_table_repr(db):\n    name = 'table4'\n    table = db.table(name)\n\n    assert re.match(\n        r\"<Table name=\\'table4\\', total=0, \"\n        r\"storage=<tinydb\\.storages\\.(MemoryStorage|JSONStorage) object at [a-zA-Z0-9]+>>\",\n        repr(table))\n\n\ndef test_truncate_table(db):\n    db.truncate()\n    assert db._get_next_id() == 1\n\n\ndef test_persist_table(db):\n    db.table(\"persisted\", persist_empty=True)\n    assert \"persisted\" in db.tables()\n\n    db.table(\"nonpersisted\", persist_empty=False)\n    assert \"nonpersisted\" not in db.tables()\n"
  },
  {
    "path": "tests/test_tinydb.py",
    "content": "import re\nfrom collections.abc import Mapping\n\nimport pytest\n\nfrom tinydb import TinyDB, where, Query\nfrom tinydb.middlewares import Middleware, CachingMiddleware\nfrom tinydb.storages import MemoryStorage, JSONStorage\nfrom tinydb.table import Document\n\n\ndef test_drop_tables(db: TinyDB):\n    db.drop_tables()\n\n    db.insert({})\n    db.drop_tables()\n\n    assert len(db) == 0\n\n\ndef test_all(db: TinyDB):\n    db.drop_tables()\n\n    for i in range(10):\n        db.insert({})\n\n    assert len(db.all()) == 10\n\n\ndef test_insert(db: TinyDB):\n    db.drop_tables()\n    db.insert({'int': 1, 'char': 'a'})\n\n    assert db.count(where('int') == 1) == 1\n\n    db.drop_tables()\n\n    db.insert({'int': 1, 'char': 'a'})\n    db.insert({'int': 1, 'char': 'b'})\n    db.insert({'int': 1, 'char': 'c'})\n\n    assert db.count(where('int') == 1) == 3\n    assert db.count(where('char') == 'a') == 1\n\n\ndef test_insert_ids(db: TinyDB):\n    db.drop_tables()\n    assert db.insert({'int': 1, 'char': 'a'}) == 1\n    assert db.insert({'int': 1, 'char': 'a'}) == 2\n\n\ndef test_insert_with_doc_id(db: TinyDB):\n    db.drop_tables()\n    assert db.insert({'int': 1, 'char': 'a'}) == 1\n    assert db.insert(Document({'int': 1, 'char': 'a'}, 12)) == 12\n    assert db.insert(Document({'int': 1, 'char': 'a'}, 77)) == 77\n    assert db.insert({'int': 1, 'char': 'a'}) == 78\n\n\ndef test_insert_with_duplicate_doc_id(db: TinyDB):\n    db.drop_tables()\n    assert db.insert({'int': 1, 'char': 'a'}) == 1\n\n    with pytest.raises(ValueError):\n        db.insert(Document({'int': 1, 'char': 'a'}, 1))\n\n\ndef test_insert_multiple(db: TinyDB):\n    db.drop_tables()\n    assert not db.contains(where('int') == 1)\n\n    # Insert multiple from list\n    db.insert_multiple([{'int': 1, 'char': 'a'},\n                        {'int': 1, 'char': 'b'},\n                        {'int': 1, 'char': 'c'}])\n\n    assert db.count(where('int') == 1) == 3\n    assert db.count(where('char') == 'a') == 1\n\n    # Insert multiple from generator function\n    def generator():\n        for j in range(10):\n            yield {'int': j}\n\n    db.drop_tables()\n\n    db.insert_multiple(generator())\n\n    for i in range(10):\n        assert db.count(where('int') == i) == 1\n    assert db.count(where('int').exists()) == 10\n\n    # Insert multiple from inline generator\n    db.drop_tables()\n\n    db.insert_multiple({'int': i} for i in range(10))\n\n    for i in range(10):\n        assert db.count(where('int') == i) == 1\n\n\ndef test_insert_multiple_with_ids(db: TinyDB):\n    db.drop_tables()\n\n    # Insert multiple from list\n    assert db.insert_multiple([{'int': 1, 'char': 'a'},\n                               {'int': 1, 'char': 'b'},\n                               {'int': 1, 'char': 'c'}]) == [1, 2, 3]\n\n\ndef test_insert_multiple_with_doc_ids(db: TinyDB):\n    db.drop_tables()\n\n    assert db.insert_multiple([\n        Document({'int': 1, 'char': 'a'}, 12),\n        Document({'int': 1, 'char': 'b'}, 77)\n    ]) == [12, 77]\n    assert db.get(doc_id=12) == {'int': 1, 'char': 'a'}\n    assert db.get(doc_id=77) == {'int': 1, 'char': 'b'}\n\n    with pytest.raises(ValueError):\n        db.insert_multiple([Document({'int': 1, 'char': 'a'}, 12)])\n\n\ndef test_insert_invalid_type_raises_error(db: TinyDB):\n    with pytest.raises(ValueError, match='Document is not a Mapping'):\n        # object() as an example of a non-mapping-type\n        db.insert(object())  # type: ignore\n\n\ndef test_insert_valid_mapping_type(db: TinyDB):\n    class CustomDocument(Mapping):\n        def __init__(self, data):\n            self.data = data\n\n        def __getitem__(self, key):\n            return self.data[key]\n\n        def __iter__(self):\n            return iter(self.data)\n\n        def __len__(self):\n            return len(self.data)\n\n    db.drop_tables()\n    db.insert(CustomDocument({'int': 1, 'char': 'a'}))\n    assert db.count(where('int') == 1) == 1\n\n\ndef test_custom_mapping_type_with_json(tmpdir):\n    class CustomDocument(Mapping):\n        def __init__(self, data):\n            self.data = data\n\n        def __getitem__(self, key):\n            return self.data[key]\n\n        def __iter__(self):\n            return iter(self.data)\n\n        def __len__(self):\n            return len(self.data)\n\n    # Insert\n    db = TinyDB(str(tmpdir.join('test.db')))\n    db.drop_tables()\n    db.insert(CustomDocument({'int': 1, 'char': 'a'}))\n    assert db.count(where('int') == 1) == 1\n\n    # Insert multiple\n    db.insert_multiple([\n        CustomDocument({'int': 2, 'char': 'a'}),\n        CustomDocument({'int': 3, 'char': 'a'})\n    ])\n    assert db.count(where('int') == 1) == 1\n    assert db.count(where('int') == 2) == 1\n    assert db.count(where('int') == 3) == 1\n\n    # Write back\n    doc_id = db.get(where('int') == 3).doc_id\n    db.update(CustomDocument({'int': 4, 'char': 'a'}), doc_ids=[doc_id])\n    assert db.count(where('int') == 3) == 0\n    assert db.count(where('int') == 4) == 1\n\n\ndef test_remove(db: TinyDB):\n    db.remove(where('char') == 'b')\n\n    assert len(db) == 2\n    assert db.count(where('int') == 1) == 2\n\n\ndef test_remove_all_fails(db: TinyDB):\n    with pytest.raises(RuntimeError):\n        db.remove()\n\n\ndef test_remove_multiple(db: TinyDB):\n    db.remove(where('int') == 1)\n\n    assert len(db) == 0\n\n\ndef test_remove_ids(db: TinyDB):\n    db.remove(doc_ids=[1, 2])\n\n    assert len(db) == 1\n\n\ndef test_remove_returns_ids(db: TinyDB):\n    assert db.remove(where('char') == 'b') == [2]\n\n\ndef test_update(db: TinyDB):\n    assert len(db) == 3\n\n    db.update({'int': 2}, where('char') == 'a')\n\n    assert db.count(where('int') == 2) == 1\n    assert db.count(where('int') == 1) == 2\n\n\ndef test_update_all(db: TinyDB):\n    assert db.count(where('int') == 1) == 3\n\n    db.update({'newField': True})\n\n    assert db.count(where('newField') == True) == 3  # noqa\n\n\ndef test_update_returns_ids(db: TinyDB):\n    db.drop_tables()\n    assert db.insert({'int': 1, 'char': 'a'}) == 1\n    assert db.insert({'int': 1, 'char': 'a'}) == 2\n\n    assert db.update({'char': 'b'}, where('int') == 1) == [1, 2]\n\n\ndef test_update_transform(db: TinyDB):\n    def increment(field):\n        def transform(el):\n            el[field] += 1\n\n        return transform\n\n    def delete(field):\n        def transform(el):\n            del el[field]\n\n        return transform\n\n    assert db.count(where('int') == 1) == 3\n\n    db.update(increment('int'), where('char') == 'a')\n    db.update(delete('char'), where('char') == 'a')\n\n    assert db.count(where('int') == 2) == 1\n    assert db.count(where('char') == 'a') == 0\n    assert db.count(where('int') == 1) == 2\n\n\ndef test_update_ids(db: TinyDB):\n    db.update({'int': 2}, doc_ids=[1, 2])\n\n    assert db.count(where('int') == 2) == 2\n\n\ndef test_update_multiple(db: TinyDB):\n    assert len(db) == 3\n\n    db.update_multiple([\n        ({'int': 2}, where('char') == 'a'),\n        ({'int': 4}, where('char') == 'b'),\n    ])\n\n    assert db.count(where('int') == 1) == 1\n    assert db.count(where('int') == 2) == 1\n    assert db.count(where('int') == 4) == 1\n\n\ndef test_update_multiple_operation(db: TinyDB):\n    def increment(field):\n        def transform(el):\n            el[field] += 1\n\n        return transform\n\n    assert db.count(where('int') == 1) == 3\n\n    db.update_multiple([\n        (increment('int'), where('char') == 'a'),\n        (increment('int'), where('char') == 'b')\n    ])\n\n    assert db.count(where('int') == 2) == 2\n\n\ndef test_upsert(db: TinyDB):\n    assert len(db) == 3\n\n    # Document existing\n    db.upsert({'int': 5}, where('char') == 'a')\n    assert db.count(where('int') == 5) == 1\n\n    # Document missing\n    assert db.upsert({'int': 9, 'char': 'x'}, where('char') == 'x') == [4]\n    assert db.count(where('int') == 9) == 1\n\n\ndef test_upsert_by_id(db: TinyDB):\n    assert len(db) == 3\n\n    # Single document existing\n    extant_doc = Document({'char': 'v'}, doc_id=1)\n    assert db.upsert(extant_doc) == [1]\n    doc = db.get(where('char') == 'v')\n    assert isinstance(doc, Document)\n    assert doc is not None\n    assert doc.doc_id == 1\n    assert len(db) == 3\n\n    # Single document missing\n    missing_doc = Document({'int': 5, 'char': 'w'}, doc_id=5)\n    assert db.upsert(missing_doc) == [5]\n    doc = db.get(where('char') == 'w')\n    assert isinstance(doc, Document)\n    assert doc is not None\n    assert doc.doc_id == 5\n    assert len(db) == 4\n\n    # Missing doc_id and condition\n    with pytest.raises(ValueError, match=r\"(?=.*\\bdoc_id\\b)(?=.*\\bquery\\b)\"):\n        db.upsert({'no_Document': 'no_query'})\n\n    # Make sure we didn't break anything\n    assert db.insert({'check': '_next_id'}) == 6\n\n\ndef test_search(db: TinyDB):\n    assert not db._query_cache\n    assert len(db.search(where('int') == 1)) == 3\n\n    assert len(db._query_cache) == 1\n    assert len(db.search(where('int') == 1)) == 3  # Query result from cache\n\n\ndef test_search_path(db: TinyDB):\n    assert not db._query_cache\n    assert len(db.search(where('int').exists())) == 3\n    assert len(db._query_cache) == 1\n\n    assert len(db.search(where('asd').exists())) == 0\n    assert len(db.search(where('int').exists())) == 3  # Query result from cache\n\n\ndef test_search_no_results_cache(db: TinyDB):\n    assert len(db.search(where('missing').exists())) == 0\n    assert len(db.search(where('missing').exists())) == 0\n\n\ndef test_get(db: TinyDB):\n    item = db.get(where('char') == 'b')\n    assert isinstance(item, Document)\n    assert item is not None\n    assert item['char'] == 'b'\n\n\ndef test_get_ids(db: TinyDB):\n    el = db.all()[0]\n    assert db.get(doc_id=el.doc_id) == el\n    assert db.get(doc_id=float('NaN')) is None  # type: ignore\n\n\ndef test_get_multiple_ids(db: TinyDB):\n    el = db.all()\n    assert db.get(doc_ids=[x.doc_id for x in el]) == el\n\n\ndef test_get_invalid(db: TinyDB):\n    with pytest.raises(RuntimeError):\n        db.get()\n\n\ndef test_count(db: TinyDB):\n    assert db.count(where('int') == 1) == 3\n    assert db.count(where('char') == 'd') == 0\n\n\ndef test_contains(db: TinyDB):\n    assert db.contains(where('int') == 1)\n    assert not db.contains(where('int') == 0)\n\n\ndef test_contains_ids(db: TinyDB):\n    assert db.contains(doc_id=1)\n    assert db.contains(doc_id=2)\n    assert not db.contains(doc_id=88)\n\n\ndef test_contains_invalid(db: TinyDB):\n    with pytest.raises(RuntimeError):\n        db.contains()\n\n\ndef test_get_idempotent(db: TinyDB):\n    u = db.get(where('int') == 1)\n    z = db.get(where('int') == 1)\n    assert u == z\n\n\ndef test_multiple_dbs():\n    \"\"\"\n    Regression test for issue #3\n    \"\"\"\n    db1 = TinyDB(storage=MemoryStorage)\n    db2 = TinyDB(storage=MemoryStorage)\n\n    db1.insert({'int': 1, 'char': 'a'})\n    db1.insert({'int': 1, 'char': 'b'})\n    db1.insert({'int': 1, 'value': 5.0})\n\n    db2.insert({'color': 'blue', 'animal': 'turtle'})\n\n    assert len(db1) == 3\n    assert len(db2) == 1\n\n\ndef test_storage_closed_once():\n    class Storage:\n        def __init__(self):\n            self.closed = False\n\n        def read(self):\n            return {}\n\n        def write(self, data):\n            pass\n\n        def close(self):\n            assert not self.closed\n            self.closed = True\n\n    with TinyDB(storage=Storage) as db:\n        db.close()\n\n    del db\n    # If db.close() is called during cleanup, the assertion will fail and throw\n    # and exception\n\n\ndef test_unique_ids(tmpdir):\n    \"\"\"\n    :type tmpdir: py._path.local.LocalPath\n    \"\"\"\n    path = str(tmpdir.join('db.json'))\n\n    # Verify ids are unique when reopening the DB and inserting\n    with TinyDB(path) as _db:\n        _db.insert({'x': 1})\n\n    with TinyDB(path) as _db:\n        _db.insert({'x': 1})\n\n    with TinyDB(path) as _db:\n        data = _db.all()\n\n        assert data[0].doc_id != data[1].doc_id\n\n    # Verify ids stay unique when inserting/removing\n    with TinyDB(path) as _db:\n        _db.drop_tables()\n        _db.insert_multiple({'x': i} for i in range(5))\n        _db.remove(where('x') == 2)\n\n        assert len(_db) == 4\n\n        ids = [e.doc_id for e in _db.all()]\n        assert len(ids) == len(set(ids))\n\n\ndef test_lastid_after_open(tmpdir):\n    \"\"\"\n    Regression test for issue #34\n\n    :type tmpdir: py._path.local.LocalPath\n    \"\"\"\n\n    NUM = 100\n    path = str(tmpdir.join('db.json'))\n\n    with TinyDB(path) as _db:\n        _db.insert_multiple({'i': i} for i in range(NUM))\n\n    with TinyDB(path) as _db:\n        assert _db._get_next_id() - 1 == NUM\n\n\ndef test_doc_ids_json(tmpdir):\n    \"\"\"\n    Regression test for issue #45\n    \"\"\"\n\n    path = str(tmpdir.join('db.json'))\n\n    with TinyDB(path) as _db:\n        _db.drop_tables()\n        assert _db.insert({'int': 1, 'char': 'a'}) == 1\n        assert _db.insert({'int': 1, 'char': 'a'}) == 2\n\n        _db.drop_tables()\n        assert _db.insert_multiple([{'int': 1, 'char': 'a'},\n                                    {'int': 1, 'char': 'b'},\n                                    {'int': 1, 'char': 'c'}]) == [1, 2, 3]\n\n        assert _db.contains(doc_id=1)\n        assert _db.contains(doc_id=2)\n        assert not _db.contains(doc_id=88)\n\n        _db.update({'int': 2}, doc_ids=[1, 2])\n        assert _db.count(where('int') == 2) == 2\n\n        el = _db.all()[0]\n        assert _db.get(doc_id=el.doc_id) == el\n        assert _db.get(doc_id=float('NaN')) is None\n\n        _db.remove(doc_ids=[1, 2])\n        assert len(_db) == 1\n\n\ndef test_insert_string(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    with TinyDB(path) as _db:\n        data = [{'int': 1}, {'int': 2}]\n        _db.insert_multiple(data)\n\n        with pytest.raises(ValueError):\n            _db.insert([1, 2, 3])  # Fails\n\n        with pytest.raises(ValueError):\n            _db.insert({'bark'})  # Fails\n\n        assert data == _db.all()\n\n        _db.insert({'int': 3})  # Does not fail\n\n\ndef test_insert_invalid_dict(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    with TinyDB(path) as _db:\n        data = [{'int': 1}, {'int': 2}]\n        _db.insert_multiple(data)\n\n        with pytest.raises(TypeError):\n            _db.insert({'int': _db})  # Fails\n\n        assert data == _db.all()\n\n        _db.insert({'int': 3})  # Does not fail\n\n\ndef test_gc(tmpdir):\n    # See https://github.com/msiemens/tinydb/issues/92\n    path = str(tmpdir.join('db.json'))\n    db = TinyDB(path)\n    table = db.table('foo')\n    table.insert({'something': 'else'})\n    table.insert({'int': 13})\n    assert len(table.search(where('int') == 13)) == 1\n    assert table.all() == [{'something': 'else'}, {'int': 13}]\n    db.close()\n\n\ndef test_drop_table():\n    db = TinyDB(storage=MemoryStorage)\n    default_table_name = db.table(db.default_table_name).name\n\n    assert [] == list(db.tables())\n    db.drop_table(default_table_name)\n\n    db.insert({'a': 1})\n    assert [default_table_name] == list(db.tables())\n\n    db.drop_table(default_table_name)\n    assert [] == list(db.tables())\n\n    table_name = 'some-other-table'\n    db = TinyDB(storage=MemoryStorage)\n    db.table(table_name).insert({'a': 1})\n    assert {table_name} == db.tables()\n\n    db.drop_table(table_name)\n    assert set() == db.tables()\n    assert table_name not in db._tables\n\n    db.drop_table('non-existent-table-name')\n    assert set() == db.tables()\n\n\ndef test_empty_write(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    class ReadOnlyMiddleware(Middleware):\n        def write(self, data):\n            raise AssertionError('No write for unchanged db')\n\n    TinyDB(path).close()\n    TinyDB(path, storage=ReadOnlyMiddleware(JSONStorage)).close()\n\n\ndef test_query_cache():\n    db = TinyDB(storage=MemoryStorage)\n    db.insert_multiple([\n        {'name': 'foo', 'value': 42},\n        {'name': 'bar', 'value': -1337}\n    ])\n\n    query = where('value') > 0\n\n    results = db.search(query)\n    assert len(results) == 1\n\n    # Modify the db instance to not return any results when\n    # bypassing the query cache\n    db._tables[db.table(db.default_table_name).name]._read_table = lambda: {}\n\n    # Make sure we got an independent copy of the result list\n    results.extend([1])\n    assert db.search(query) == [{'name': 'foo', 'value': 42}]\n\n\ndef test_tinydb_is_iterable(db: TinyDB):\n    assert [r for r in db] == db.all()\n\n\ndef test_repr(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    db = TinyDB(path)\n    db.insert({'a': 1})\n\n    assert re.match(\n        r\"<TinyDB \"\n        r\"tables=\\[u?\\'_default\\'\\], \"\n        r\"tables_count=1, \"\n        r\"default_table_documents_count=1, \"\n        r\"all_tables_documents_count=\\[\\'_default=1\\'\\]>\",\n        repr(db))\n\n\ndef test_delete(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    db = TinyDB(path, ensure_ascii=False)\n    q = Query()\n    db.insert({'network': {'id': '114', 'name': 'ok', 'rpc': 'dac',\n                           'ticker': 'mkay'}})\n    assert db.search(q.network.id == '114') == [\n        {'network': {'id': '114', 'name': 'ok', 'rpc': 'dac',\n                     'ticker': 'mkay'}}\n    ]\n    db.remove(q.network.id == '114')\n    assert db.search(q.network.id == '114') == []\n\n\ndef test_insert_multiple_with_single_dict(db: TinyDB):\n    with pytest.raises(ValueError):\n        d = {'first': 'John', 'last': 'smith'}\n        db.insert_multiple(d)  # type: ignore\n        db.close()\n\n\ndef test_access_storage():\n    assert isinstance(TinyDB(storage=MemoryStorage).storage,\n                      MemoryStorage)\n    assert isinstance(TinyDB(storage=CachingMiddleware(MemoryStorage)).storage,\n                      CachingMiddleware)\n\n\ndef test_empty_db_len():\n    db = TinyDB(storage=MemoryStorage)\n    assert len(db) == 0\n\n\ndef test_insert_on_existing_db(tmpdir):\n    path = str(tmpdir.join('db.json'))\n\n    db = TinyDB(path, ensure_ascii=False)\n    db.insert({'foo': 'bar'})\n\n    assert len(db) == 1\n\n    db.close()\n\n    db = TinyDB(path, ensure_ascii=False)\n    db.insert({'foo': 'bar'})\n    db.insert({'foo': 'bar'})\n\n    assert len(db) == 3\n\n\ndef test_storage_access():\n    db = TinyDB(storage=MemoryStorage)\n\n    assert isinstance(db.storage, MemoryStorage)\n\n\ndef test_lambda_query():\n    db = TinyDB(storage=MemoryStorage)\n    db.insert({'foo': 'bar'})\n\n    query = lambda doc: doc.get('foo') == 'bar'\n    query.is_cacheable = lambda: False\n    assert db.search(query) == [{'foo': 'bar'}]\n    assert not db._query_cache\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import pytest\n\nfrom tinydb.utils import LRUCache, freeze, FrozenDict\n\n\ndef test_lru_cache():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"b\"] = 2\n    cache[\"c\"] = 3\n    _ = cache[\"a\"]  # move to front in lru queue\n    cache[\"d\"] = 4  # move oldest item out of lru queue\n\n    try:\n        _ = cache['f']\n    except KeyError:\n        pass\n\n    assert cache.lru == [\"c\", \"a\", \"d\"]\n\n\ndef test_lru_cache_set_multiple():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"a\"] = 2\n    cache[\"a\"] = 3\n    cache[\"a\"] = 4\n\n    assert cache.lru == [\"a\"]\n\n\ndef test_lru_cache_set_update():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"a\"] = 2\n\n    assert cache[\"a\"] == 2\n\n\ndef test_lru_cache_get():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"b\"] = 1\n    cache[\"c\"] = 1\n    cache.get(\"a\")\n    cache[\"d\"] = 4\n\n    assert cache.lru == [\"c\", \"a\", \"d\"]\n\n\ndef test_lru_cache_delete():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"b\"] = 2\n    del cache[\"a\"]\n\n    try:\n        del cache['f']\n    except KeyError:\n        pass\n\n    assert cache.lru == [\"b\"]\n\n\ndef test_lru_cache_clear():\n    cache = LRUCache(capacity=3)\n    cache[\"a\"] = 1\n    cache[\"b\"] = 2\n    cache.clear()\n\n    assert cache.lru == []\n\n\ndef test_lru_cache_unlimited():\n    cache = LRUCache()\n    for i in range(100):\n        cache[i] = i\n\n    assert len(cache.lru) == 100\n\n\ndef test_lru_cache_unlimited_explicit():\n    cache = LRUCache(capacity=None)\n    for i in range(100):\n        cache[i] = i\n\n    assert len(cache.lru) == 100\n\n\ndef test_lru_cache_iteration_works():\n    cache = LRUCache()\n    count = 0\n    for _ in cache:\n        assert False, 'there should be no elements in the cache'\n\n    assert count == 0\n\n\ndef test_lru_cache_falsy_values_bug():\n    \"\"\"\n    Test for GitHub issue #596: LRU cache should handle falsy values correctly.\n    \n    Bug: `if self.cache.get(key):` treated falsy values as non-existent keys,\n    breaking LRU ordering when updating existing keys with falsy values.\n    \"\"\"\n    cache = LRUCache(capacity=3)\n    \n    # Set up cache with falsy value\n    cache[\"a\"] = 0      # Falsy value\n    cache[\"b\"] = 1\n    cache[\"c\"] = 2\n\n    assert cache.lru == [\"a\", \"b\", \"c\"]\n    \n    # Update existing key with falsy value - should move to end\n    cache.set(\"a\", 3)\n    assert cache.lru == [\"b\", \"c\", \"a\"]\n    \n    # Add new item - should evict oldest (\"b\"), not \"a\"\n    cache.set(\"d\", 4)\n    assert cache.lru == [\"c\", \"a\", \"d\"]\n    assert \"b\" not in cache\n    assert cache[\"a\"] == 3\n\ndef test_freeze():\n    frozen = freeze([0, 1, 2, {'a': [1, 2, 3]}, {1, 2}])\n    assert isinstance(frozen, tuple)\n    assert isinstance(frozen[3], FrozenDict)\n    assert isinstance(frozen[3]['a'], tuple)\n    assert isinstance(frozen[4], frozenset)\n\n    with pytest.raises(TypeError):\n        frozen[0] = 10\n\n    with pytest.raises(TypeError):\n        frozen[3]['a'] = 10\n\n    with pytest.raises(TypeError):\n        frozen[3].pop('a')\n\n    with pytest.raises(TypeError):\n        frozen[3].update({'a': 9})\n"
  },
  {
    "path": "tinydb/__init__.py",
    "content": "\"\"\"\nTinyDB is a tiny, document oriented database optimized for your happiness :)\n\nTinyDB stores different types of Python data types using a configurable\nstorage mechanism. It comes with a syntax for querying data and storing\ndata in multiple tables.\n\n.. codeauthor:: Markus Siemens <markus@m-siemens.de>\n\nUsage example:\n\n>>> from tinydb import TinyDB, where\n>>> from tinydb.storages import MemoryStorage\n>>> db = TinyDB(storage=MemoryStorage)\n>>> db.insert({'data': 5})  # Insert into '_default' table\n>>> db.search(where('data') == 5)\n[{'data': 5, '_id': 1}]\n>>> # Now let's create a new table\n>>> tbl = db.table('our_table')\n>>> for i in range(10):\n...     tbl.insert({'data': i})\n...\n>>> len(tbl.search(where('data') < 5))\n5\n\"\"\"\n\nfrom .queries import Query, where\nfrom .storages import Storage, JSONStorage\nfrom .database import TinyDB\nfrom .version import __version__\n\n__all__ = ('TinyDB', 'Storage', 'JSONStorage', 'Query', 'where')\n"
  },
  {
    "path": "tinydb/database.py",
    "content": "\"\"\"\nThis module contains the main component of TinyDB: the database.\n\"\"\"\n\nfrom typing import Dict, Iterator, Set, Type\n\nfrom . import JSONStorage\nfrom .storages import Storage\nfrom .table import Table, Document\nfrom .utils import with_typehint\n\n# The table's base class. This is used to add type hinting from the Table\n# class to TinyDB. Currently, this supports PyCharm, Pyright/VS Code and MyPy.\nTableBase: Type[Table] = with_typehint(Table)\n\n\nclass TinyDB(TableBase):\n    \"\"\"\n    The main class of TinyDB.\n\n    The ``TinyDB`` class is responsible for creating the storage class instance\n    that will store this database's documents, managing the database\n    tables as well as providing access to the default table.\n\n    For table management, a simple ``dict`` is used that stores the table class\n    instances accessible using their table name.\n\n    Default table access is provided by forwarding all unknown method calls\n    and property access operations to the default table by implementing\n    ``__getattr__``.\n\n    When creating a new instance, all arguments and keyword arguments (except\n    for ``storage``) will be passed to the storage class that is provided. If\n    no storage class is specified, :class:`~tinydb.storages.JSONStorage` will be\n    used.\n\n    .. admonition:: Customization\n\n        For customization, the following class variables can be set:\n\n        - ``table_class`` defines the class that is used to create tables,\n        - ``default_table_name`` defines the name of the default table, and\n        - ``default_storage_class`` will define the class that will be used to\n          create storage instances if no other storage is passed.\n\n        .. versionadded:: 4.0\n\n    .. admonition:: Data Storage Model\n\n        Data is stored using a storage class that provides persistence for a\n        ``dict`` instance. This ``dict`` contains all tables and their data.\n        The data is modelled like this::\n\n            {\n                'table1': {\n                    0: {document...},\n                    1: {document...},\n                },\n                'table2': {\n                    ...\n                }\n            }\n\n        Each entry in this ``dict`` uses the table name as its key and a\n        ``dict`` of documents as its value. The document ``dict`` contains\n        document IDs as keys and the documents themselves as values.\n\n    :param storage: The class of the storage to use. Will be initialized\n                    with ``args`` and ``kwargs``.\n    \"\"\"\n\n    #: The class that will be used to create table instances\n    #:\n    #: .. versionadded:: 4.0\n    table_class = Table\n\n    #: The name of the default table\n    #:\n    #: .. versionadded:: 4.0\n    default_table_name = '_default'\n\n    #: The class that will be used by default to create storage instances\n    #:\n    #: .. versionadded:: 4.0\n    default_storage_class = JSONStorage\n\n    def __init__(self, *args, **kwargs) -> None:\n        \"\"\"\n        Create a new instance of TinyDB.\n        \"\"\"\n\n        storage = kwargs.pop('storage', self.default_storage_class)\n\n        # Prepare the storage\n        self._storage: Storage = storage(*args, **kwargs)\n\n        self._opened = True\n        self._tables: Dict[str, Table] = {}\n\n    def __repr__(self):\n\n        args = [\n            f'tables={list(self.tables())}',\n            f'tables_count={len(self.tables())}',\n            f'default_table_documents_count={self.__len__()}',\n            f'all_tables_documents_count={[f\"{table}={len(self.table(table))}\" for table in self.tables()]}',\n        ]\n\n        return '<{} {}>'.format(type(self).__name__, ', '.join(args))\n\n    def table(self, name: str, **kwargs) -> Table:\n        \"\"\"\n        Get access to a specific table.\n\n        If the table hasn't been accessed yet, a new table instance will be\n        created using the :attr:`~tinydb.database.TinyDB.table_class` class.\n        Otherwise, the previously created table instance will be returned.\n\n        All further options besides the name are passed to the table class which\n        by default is :class:`~tinydb.table.Table`. Check its documentation\n        for further parameters you can pass.\n\n        :param name: The name of the table.\n        :param kwargs: Keyword arguments to pass to the table class constructor\n        \"\"\"\n\n        if name in self._tables:\n            return self._tables[name]\n\n        table = self.table_class(self.storage, name, **kwargs)\n        self._tables[name] = table\n\n        return table\n\n    def tables(self) -> Set[str]:\n        \"\"\"\n        Get the names of all tables in the database.\n\n        :returns: a set of table names\n        \"\"\"\n\n        # TinyDB stores data as a dict of tables like this:\n        #\n        #   {\n        #       '_default': {\n        #           0: {document...},\n        #           1: {document...},\n        #       },\n        #       'table1': {\n        #           ...\n        #       }\n        #   }\n        #\n        # To get a set of table names, we thus construct a set of this main\n        # dict which returns a set of the dict keys which are the table names.\n        #\n        # Storage.read() may return ``None`` if the database file is empty,\n        # so we need to consider this case to and return an empty set in this\n        # case.\n\n        return set(self.storage.read() or {})\n\n    def drop_tables(self) -> None:\n        \"\"\"\n        Drop all tables from the database. **CANNOT BE REVERSED!**\n        \"\"\"\n\n        # We drop all tables from this database by writing an empty dict\n        # to the storage thereby returning to the initial state with no tables.\n        self.storage.write({})\n\n        # After that we need to remember to empty the ``_tables`` dict, so we'll\n        # create new table instances when a table is accessed again.\n        self._tables.clear()\n\n    def drop_table(self, name: str) -> None:\n        \"\"\"\n        Drop a specific table from the database. **CANNOT BE REVERSED!**\n\n        :param name: The name of the table to drop.\n        \"\"\"\n\n        # If the table is currently opened, we need to forget the table class\n        # instance\n        if name in self._tables:\n            del self._tables[name]\n\n        data = self.storage.read()\n\n        # The database is uninitialized, there's nothing to do\n        if data is None:\n            return\n\n        # The table does not exist, there's nothing to do\n        if name not in data:\n            return\n\n        # Remove the table from the data dict\n        del data[name]\n\n        # Store the updated data back to the storage\n        self.storage.write(data)\n\n    @property\n    def storage(self) -> Storage:\n        \"\"\"\n        Get the storage instance used for this TinyDB instance.\n\n        :return: This instance's storage\n        :rtype: Storage\n        \"\"\"\n        return self._storage\n\n    def close(self) -> None:\n        \"\"\"\n        Close the database.\n\n        This may be needed if the storage instance used for this database\n        needs to perform cleanup operations like closing file handles.\n\n        To ensure this method is called, the TinyDB instance can be used as a\n        context manager::\n\n            with TinyDB('data.json') as db:\n                db.insert({'foo': 'bar'})\n\n        Upon leaving this context, the ``close`` method will be called.\n        \"\"\"\n        self._opened = False\n        self.storage.close()\n\n    def __enter__(self):\n        \"\"\"\n        Use the database as a context manager.\n\n        Using the database as a context manager ensures that the\n        :meth:`~tinydb.database.TinyDB.close` method is called upon leaving\n        the context.\n\n        :return: The current instance\n        \"\"\"\n        return self\n\n    def __exit__(self, *args):\n        \"\"\"\n        Close the storage instance when leaving a context.\n        \"\"\"\n        if self._opened:\n            self.close()\n\n    def __getattr__(self, name):\n        \"\"\"\n        Forward all unknown attribute calls to the default table instance.\n        \"\"\"\n        return getattr(self.table(self.default_table_name), name)\n\n    # Here we forward magic methods to the default table instance. These are\n    # not handled by __getattr__ so we need to forward them manually here\n\n    def __len__(self):\n        \"\"\"\n        Get the total number of documents in the default table.\n\n        >>> db = TinyDB('db.json')\n        >>> len(db)\n        0\n        \"\"\"\n        return len(self.table(self.default_table_name))\n\n    def __iter__(self) -> Iterator[Document]:\n        \"\"\"\n        Return an iterator for the default table's documents.\n        \"\"\"\n        return iter(self.table(self.default_table_name))\n"
  },
  {
    "path": "tinydb/middlewares.py",
    "content": "\"\"\"\nContains the :class:`base class <tinydb.middlewares.Middleware>` for\nmiddlewares and implementations.\n\"\"\"\nfrom typing import Optional\n\nfrom tinydb import Storage\n\n\nclass Middleware:\n    \"\"\"\n    The base class for all Middlewares.\n\n    Middlewares hook into the read/write process of TinyDB allowing you to\n    extend the behaviour by adding caching, logging, ...\n\n    Your middleware's ``__init__`` method has to call the parent class\n    constructor so the middleware chain can be configured properly.\n    \"\"\"\n\n    def __init__(self, storage_cls) -> None:\n        self._storage_cls = storage_cls\n        self.storage: Storage = None  # type: ignore\n\n    def __call__(self, *args, **kwargs):\n        \"\"\"\n        Create the storage instance and store it as self.storage.\n\n        Usually a user creates a new TinyDB instance like this::\n\n            TinyDB(storage=StorageClass)\n\n        The storage keyword argument is used by TinyDB this way::\n\n            self.storage = storage(*args, **kwargs)\n\n        As we can see, ``storage(...)`` runs the constructor and returns the\n        new storage instance.\n\n\n        Using Middlewares, the user will call::\n\n                                       The 'real' storage class\n                                       v\n            TinyDB(storage=Middleware(StorageClass))\n                       ^\n                       Already an instance!\n\n        So, when running ``self.storage = storage(*args, **kwargs)`` Python\n        now will call ``__call__`` and TinyDB will expect the return value to\n        be the storage (or Middleware) instance. Returning the instance is\n        simple, but we also got the underlying (*real*) StorageClass as an\n        __init__ argument that still is not an instance.\n        So, we initialize it in __call__ forwarding any arguments we receive\n        from TinyDB (``TinyDB(arg1, kwarg1=value, storage=...)``).\n\n        In case of nested Middlewares, calling the instance as if it was a\n        class results in calling ``__call__`` what initializes the next\n        nested Middleware that itself will initialize the next Middleware and\n        so on.\n        \"\"\"\n\n        self.storage = self._storage_cls(*args, **kwargs)\n\n        return self\n\n    def __getattr__(self, name):\n        \"\"\"\n        Forward all unknown attribute calls to the underlying storage, so we\n        remain as transparent as possible.\n        \"\"\"\n\n        return getattr(self.__dict__['storage'], name)\n\n\nclass CachingMiddleware(Middleware):\n    \"\"\"\n    Add some caching to TinyDB.\n\n    This Middleware aims to improve the performance of TinyDB by writing only\n    the last DB state every :attr:`WRITE_CACHE_SIZE` time and reading always\n    from cache.\n    \"\"\"\n\n    #: The number of write operations to cache before writing to disc\n    WRITE_CACHE_SIZE = 1000\n\n    def __init__(self, storage_cls):\n        # Initialize the parent constructor\n        super().__init__(storage_cls)\n\n        # Prepare the cache\n        self.cache = None\n        self._cache_modified_count = 0\n\n    def read(self):\n        if self.cache is None:\n            # Empty cache: read from the storage\n            self.cache = self.storage.read()\n\n        # Return the cached data\n        return self.cache\n\n    def write(self, data):\n        # Store data in cache\n        self.cache = data\n        self._cache_modified_count += 1\n\n        # Check if we need to flush the cache\n        if self._cache_modified_count >= self.WRITE_CACHE_SIZE:\n            self.flush()\n\n    def flush(self):\n        \"\"\"\n        Flush all unwritten data to disk.\n        \"\"\"\n        if self._cache_modified_count > 0:\n            # Force-flush the cache by writing the data to the storage\n            self.storage.write(self.cache)\n            self._cache_modified_count = 0\n\n    def close(self):\n        # Flush potentially unwritten data\n        self.flush()\n\n        # Let the storage clean up too\n        self.storage.close()\n"
  },
  {
    "path": "tinydb/mypy_plugin.py",
    "content": "from typing import TypeVar, Optional, Callable, Dict\n\nfrom mypy.nodes import NameExpr\nfrom mypy.options import Options\nfrom mypy.plugin import Plugin, DynamicClassDefContext\n\nT = TypeVar('T')\nCB = Optional[Callable[[T], None]]\nDynamicClassDef = DynamicClassDefContext\n\n\nclass TinyDBPlugin(Plugin):\n    def __init__(self, options: Options):\n        super().__init__(options)\n\n        self.named_placeholders: Dict[str, str] = {}\n\n    def get_dynamic_class_hook(self, fullname: str) -> CB[DynamicClassDef]:\n        if fullname == 'tinydb.utils.with_typehint':\n            def hook(ctx: DynamicClassDefContext):\n                klass = ctx.call.args[0]\n                assert isinstance(klass, NameExpr)\n\n                type_name = klass.fullname\n                assert type_name is not None\n\n                qualified = self.lookup_fully_qualified(type_name)\n                assert qualified is not None\n\n                ctx.api.add_symbol_table_node(ctx.name, qualified)\n\n            return hook\n\n        return None\n\n\ndef plugin(_version: str):\n    return TinyDBPlugin\n"
  },
  {
    "path": "tinydb/operations.py",
    "content": "\"\"\"\nA collection of update operations for TinyDB.\n\nThey are used for updates like this:\n\n>>> db.update(delete('foo'), where('foo') == 2)\n\nThis would delete the ``foo`` field from all documents where ``foo`` equals 2.\n\"\"\"\n\n\ndef delete(field):\n    \"\"\"\n    Delete a given field from the document.\n    \"\"\"\n    def transform(doc):\n        del doc[field]\n\n    return transform\n\n\ndef add(field, n):\n    \"\"\"\n    Add ``n`` to a given field in the document.\n    \"\"\"\n    def transform(doc):\n        doc[field] += n\n\n    return transform\n\n\ndef subtract(field, n):\n    \"\"\"\n    Subtract ``n`` to a given field in the document.\n    \"\"\"\n    def transform(doc):\n        doc[field] -= n\n\n    return transform\n\n\ndef set(field, val):\n    \"\"\"\n    Set a given field to ``val``.\n    \"\"\"\n    def transform(doc):\n        doc[field] = val\n\n    return transform\n\n\ndef increment(field):\n    \"\"\"\n    Increment a given field in the document by 1.\n    \"\"\"\n    def transform(doc):\n        doc[field] += 1\n\n    return transform\n\n\ndef decrement(field):\n    \"\"\"\n    Decrement a given field in the document by 1.\n    \"\"\"\n    def transform(doc):\n        doc[field] -= 1\n\n    return transform\n"
  },
  {
    "path": "tinydb/py.typed",
    "content": ""
  },
  {
    "path": "tinydb/queries.py",
    "content": "\"\"\"\nContains the querying interface.\n\nStarting with :class:`~tinydb.queries.Query` you can construct complex\nqueries:\n\n>>> ((where('f1') == 5) & (where('f2') != 2)) | where('s').matches(r'^\\\\w+$')\n(('f1' == 5) and ('f2' != 2)) or ('s' ~= ^\\\\w+$ )\n\nQueries are executed by using the ``__call__``:\n\n>>> q = where('val') == 5\n>>> q({'val': 5})\nTrue\n>>> q({'val': 1})\nFalse\n\"\"\"\n\nimport re\nfrom typing import Mapping, Tuple, Callable, Any, Union, List, Optional, Protocol\n\nfrom .utils import freeze\n\n__all__ = ('Query', 'QueryLike', 'where')\n\n\ndef is_sequence(obj):\n    return hasattr(obj, '__iter__')\n\n\nclass QueryLike(Protocol):\n    \"\"\"\n    A typing protocol that acts like a query.\n\n    Something that we use as a query must have two properties:\n\n    1. It must be callable, accepting a `Mapping` object and returning a\n       boolean that indicates whether the value matches the query, and\n    2. it must have a stable hash that will be used for query caching.\n\n    In addition, to mark a query as non-cacheable (e.g. if it involves\n    some remote lookup) it needs to have a method called ``is_cacheable``\n    that returns ``False``.\n\n    This query protocol is used to make MyPy correctly support the query\n    pattern that TinyDB uses.\n\n    See also https://mypy.readthedocs.io/en/stable/protocols.html#simple-user-defined-protocols\n    \"\"\"\n    def __call__(self, value: Mapping) -> bool: ...\n\n    def __hash__(self) -> int: ...\n\n\nclass QueryInstance:\n    \"\"\"\n    A query instance.\n\n    This is the object on which the actual query operations are performed. The\n    :class:`~tinydb.queries.Query` class acts like a query builder and\n    generates :class:`~tinydb.queries.QueryInstance` objects which will\n    evaluate their query against a given document when called.\n\n    Query instances can be combined using logical OR and AND and inverted using\n    logical NOT.\n\n    In order to be usable in a query cache, a query needs to have a stable hash\n    value with the same query always returning the same hash. That way a query\n    instance can be used as a key in a dictionary.\n    \"\"\"\n\n    def __init__(self, test: Callable[[Mapping], bool], hashval: Optional[Tuple]):\n        self._test = test\n        self._hash = hashval\n\n    def is_cacheable(self) -> bool:\n        return self._hash is not None\n\n    def __call__(self, value: Mapping) -> bool:\n        \"\"\"\n        Evaluate the query to check if it matches a specified value.\n\n        :param value: The value to check.\n        :return: Whether the value matches this query.\n        \"\"\"\n        return self._test(value)\n\n    def __hash__(self) -> int:\n        # We calculate the query hash by using the ``hashval`` object which\n        # describes this query uniquely, so we can calculate a stable hash\n        # value by simply hashing it\n        return hash(self._hash)\n\n    def __repr__(self):\n        return 'QueryImpl{}'.format(self._hash)\n\n    def __eq__(self, other: object):\n        if isinstance(other, QueryInstance):\n            return self._hash == other._hash\n\n        return False\n\n    # --- Query modifiers -----------------------------------------------------\n\n    def __and__(self, other: 'QueryInstance') -> 'QueryInstance':\n        # We use a frozenset for the hash as the AND operation is commutative\n        # (a & b == b & a) and the frozenset does not consider the order of\n        # elements\n        if self.is_cacheable() and other.is_cacheable():\n            hashval = ('and', frozenset([self._hash, other._hash]))\n        else:\n            hashval = None\n        return QueryInstance(lambda value: self(value) and other(value), hashval)\n\n    def __or__(self, other: 'QueryInstance') -> 'QueryInstance':\n        # We use a frozenset for the hash as the OR operation is commutative\n        # (a | b == b | a) and the frozenset does not consider the order of\n        # elements\n        if self.is_cacheable() and other.is_cacheable():\n            hashval = ('or', frozenset([self._hash, other._hash]))\n        else:\n            hashval = None\n        return QueryInstance(lambda value: self(value) or other(value), hashval)\n\n    def __invert__(self) -> 'QueryInstance':\n        hashval = ('not', self._hash) if self.is_cacheable() else None\n        return QueryInstance(lambda value: not self(value), hashval)\n\n\nclass Query(QueryInstance):\n    \"\"\"\n    TinyDB Queries.\n\n    Allows building queries for TinyDB databases. There are two main ways of\n    using queries:\n\n    1) ORM-like usage:\n\n    >>> User = Query()\n    >>> db.search(User.name == 'John Doe')\n    >>> db.search(User['logged-in'] == True)\n\n    2) Classical usage:\n\n    >>> db.search(where('value') == True)\n\n    Note that ``where(...)`` is a shorthand for ``Query(...)`` allowing for\n    a more fluent syntax.\n\n    Besides the methods documented here you can combine queries using the\n    binary AND and OR operators:\n\n    >>> # Binary AND:\n    >>> db.search((where('field1').exists()) & (where('field2') == 5))\n    >>> # Binary OR:\n    >>> db.search((where('field1').exists()) | (where('field2') == 5))\n\n    Queries are executed by calling the resulting object. They expect to get\n    the document to test as the first argument and return ``True`` or\n    ``False`` depending on whether the documents match the query or not.\n    \"\"\"\n\n    def __init__(self) -> None:\n        # The current path of fields to access when evaluating the object\n        self._path: Tuple[Union[str, Callable], ...] = ()\n\n        # Prevent empty queries to be evaluated\n        def notest(_):\n            raise RuntimeError('Empty query was evaluated')\n\n        super().__init__(\n            test=notest,\n            hashval=(None,)\n        )\n\n    def __repr__(self):\n        return '{}()'.format(type(self).__name__)\n\n    def __hash__(self):\n        return super().__hash__()\n\n    def __getattr__(self, item: str):\n        # Generate a new query object with the new query path\n        # We use type(self) to get the class of the current query in case\n        # someone uses a subclass of ``Query``\n        query = type(self)()\n\n        # Now we add the accessed item to the query path ...\n        query._path = self._path + (item,)\n\n        # ... and update the query hash\n        query._hash = ('path', query._path) if self.is_cacheable() else None\n\n        return query\n\n    def __getitem__(self, item: str):\n        # A different syntax for ``__getattr__``\n\n        # We cannot call ``getattr(item)`` here as it would try to resolve\n        # the name as a method name first, only then call our ``__getattr__``\n        # method. By calling ``__getattr__`` directly, we make sure that\n        # calling e.g. ``Query()['test']`` will always generate a query for a\n        # document's ``test`` field instead of returning a reference to the\n        # ``Query.test`` method\n        return self.__getattr__(item)\n\n    def _generate_test(\n            self,\n            test: Callable[[Any], bool],\n            hashval: Tuple,\n            allow_empty_path: bool = False\n    ) -> QueryInstance:\n        \"\"\"\n        Generate a query based on a test function that first resolves the query\n        path.\n\n        :param test: The test the query executes.\n        :param hashval: The hash of the query.\n        :return: A :class:`~tinydb.queries.QueryInstance` object\n        \"\"\"\n        if not self._path and not allow_empty_path:\n            raise ValueError('Query has no path')\n\n        def runner(value):\n            try:\n                # Resolve the path\n                for part in self._path:\n                    if isinstance(part, str):\n                        value = value[part]\n                    else:\n                        value = part(value)\n            except (KeyError, TypeError):\n                return False\n            else:\n                # Perform the specified test\n                return test(value)\n\n        return QueryInstance(\n            lambda value: runner(value),\n            (hashval if self.is_cacheable() else None)\n        )\n\n    def __eq__(self, rhs: Any):\n        \"\"\"\n        Test a dict value for equality.\n\n        >>> Query().f1 == 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value == rhs,\n            ('==', self._path, freeze(rhs))\n        )\n\n    def __ne__(self, rhs: Any):\n        \"\"\"\n        Test a dict value for inequality.\n\n        >>> Query().f1 != 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value != rhs,\n            ('!=', self._path, freeze(rhs))\n        )\n\n    def __lt__(self, rhs: Any) -> QueryInstance:\n        \"\"\"\n        Test a dict value for being lower than another value.\n\n        >>> Query().f1 < 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value < rhs,\n            ('<', self._path, rhs)\n        )\n\n    def __le__(self, rhs: Any) -> QueryInstance:\n        \"\"\"\n        Test a dict value for being lower than or equal to another value.\n\n        >>> where('f1') <= 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value <= rhs,\n            ('<=', self._path, rhs)\n        )\n\n    def __gt__(self, rhs: Any) -> QueryInstance:\n        \"\"\"\n        Test a dict value for being greater than another value.\n\n        >>> Query().f1 > 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value > rhs,\n            ('>', self._path, rhs)\n        )\n\n    def __ge__(self, rhs: Any) -> QueryInstance:\n        \"\"\"\n        Test a dict value for being greater than or equal to another value.\n\n        >>> Query().f1 >= 42\n\n        :param rhs: The value to compare against\n        \"\"\"\n        return self._generate_test(\n            lambda value: value >= rhs,\n            ('>=', self._path, rhs)\n        )\n\n    def exists(self) -> QueryInstance:\n        \"\"\"\n        Test for a dict where a provided key exists.\n\n        >>> Query().f1.exists()\n        \"\"\"\n        return self._generate_test(\n            lambda _: True,\n            ('exists', self._path)\n        )\n\n    def matches(self, regex: str, flags: int = 0) -> QueryInstance:\n        \"\"\"\n        Run a regex test against a dict value (whole string has to match).\n\n        >>> Query().f1.matches(r'^\\\\w+$')\n\n        :param regex: The regular expression to use for matching\n        :param flags: regex flags to pass to ``re.match``\n        \"\"\"\n        def test(value):\n            if not isinstance(value, str):\n                return False\n\n            return re.match(regex, value, flags) is not None\n\n        return self._generate_test(test, ('matches', self._path, regex))\n\n    def search(self, regex: str, flags: int = 0) -> QueryInstance:\n        \"\"\"\n        Run a regex test against a dict value (only substring string has to\n        match).\n\n        >>> Query().f1.search(r'^\\\\w+$')\n\n        :param regex: The regular expression to use for matching\n        :param flags: regex flags to pass to ``re.match``\n        \"\"\"\n\n        def test(value):\n            if not isinstance(value, str):\n                return False\n\n            return re.search(regex, value, flags) is not None\n\n        return self._generate_test(test, ('search', self._path, regex))\n\n    def test(self, func: Callable[[Mapping], bool], *args) -> QueryInstance:\n        \"\"\"\n        Run a user-defined test function against a dict value.\n\n        >>> def test_func(val):\n        ...     return val == 42\n        ...\n        >>> Query().f1.test(test_func)\n\n        .. warning::\n\n            The test function provided needs to be deterministic (returning the\n            same value when provided with the same arguments), otherwise this\n            may mess up the query cache that :class:`~tinydb.table.Table`\n            implements.\n\n        :param func: The function to call, passing the dict as the first\n                     argument\n        :param args: Additional arguments to pass to the test function\n        \"\"\"\n        return self._generate_test(\n            lambda value: func(value, *args),\n            ('test', self._path, func, args)\n        )\n\n    def any(self, cond: Union[QueryInstance, List[Any]]) -> QueryInstance:\n        \"\"\"\n        Check if a condition is met by any document in a list,\n        where a condition can also be a sequence (e.g. list).\n\n        >>> Query().f1.any(Query().f2 == 1)\n\n        Matches::\n\n            {'f1': [{'f2': 1}, {'f2': 0}]}\n\n        >>> Query().f1.any([1, 2, 3])\n\n        Matches::\n\n            {'f1': [1, 2]}\n            {'f1': [3, 4, 5]}\n\n        :param cond: Either a query that at least one document has to match or\n                     a list of which at least one document has to be contained\n                     in the tested document.\n        \"\"\"\n        if callable(cond):\n            def test(value):\n                return is_sequence(value) and any(cond(e) for e in value)\n\n        else:\n            def test(value):\n                return is_sequence(value) and any(e in cond for e in value)\n\n        return self._generate_test(\n            lambda value: test(value),\n            ('any', self._path, freeze(cond))\n        )\n\n    def all(self, cond: Union['QueryInstance', List[Any]]) -> QueryInstance:\n        \"\"\"\n        Check if a condition is met by all documents in a list,\n        where a condition can also be a sequence (e.g. list).\n\n        >>> Query().f1.all(Query().f2 == 1)\n\n        Matches::\n\n            {'f1': [{'f2': 1}, {'f2': 1}]}\n\n        >>> Query().f1.all([1, 2, 3])\n\n        Matches::\n\n            {'f1': [1, 2, 3, 4, 5]}\n\n        :param cond: Either a query that all documents have to match or a list\n                     which has to be contained in the tested document.\n        \"\"\"\n        if callable(cond):\n            def test(value):\n                return is_sequence(value) and all(cond(e) for e in value)\n\n        else:\n            def test(value):\n                return is_sequence(value) and all(e in value for e in cond)\n\n        return self._generate_test(\n            lambda value: test(value),\n            ('all', self._path, freeze(cond))\n        )\n\n    def one_of(self, items: List[Any]) -> QueryInstance:\n        \"\"\"\n        Check if the value is contained in a list or generator.\n\n        >>> Query().f1.one_of(['value 1', 'value 2'])\n\n        :param items: The list of items to check with\n        \"\"\"\n        return self._generate_test(\n            lambda value: value in items,\n            ('one_of', self._path, freeze(items))\n        )\n\n    def fragment(self, document: Mapping) -> QueryInstance:\n        def test(value):\n            for key in document:\n                if key not in value or value[key] != document[key]:\n                    return False\n\n            return True\n\n        return self._generate_test(\n            lambda value: test(value),\n            ('fragment', freeze(document)),\n            allow_empty_path=True\n        )\n\n    def noop(self) -> QueryInstance:\n        \"\"\"\n        Always evaluate to ``True``.\n\n        Useful for having a base value when composing queries dynamically.\n        \"\"\"\n\n        return QueryInstance(\n            lambda value: True,\n            ()\n        )\n\n    def map(self, fn: Callable[[Any], Any]) -> 'Query':\n        \"\"\"\n        Add a function to the query path. Similar to __getattr__ but for\n        arbitrary functions.\n        \"\"\"\n        query = type(self)()\n\n        # Now we add the callable to the query path ...\n        query._path = self._path + (fn,)\n\n        # ... and kill the hash - callable objects can be mutable, so it's\n        # harmful to cache their results.\n        query._hash = None\n\n        return query\n\ndef where(key: str) -> Query:\n    \"\"\"\n    A shorthand for ``Query()[key]``\n    \"\"\"\n    return Query()[key]\n"
  },
  {
    "path": "tinydb/storages.py",
    "content": "\"\"\"\nContains the :class:`base class <tinydb.storages.Storage>` for storages and\nimplementations.\n\"\"\"\n\nimport io\nimport json\nimport os\nimport warnings\nfrom abc import ABC, abstractmethod\nfrom typing import Dict, Any, Optional\n\n__all__ = ('Storage', 'JSONStorage', 'MemoryStorage')\n\n\ndef touch(path: str, create_dirs: bool):\n    \"\"\"\n    Create a file if it doesn't exist yet.\n\n    :param path: The file to create.\n    :param create_dirs: Whether to create all missing parent directories.\n    \"\"\"\n    if create_dirs:\n        base_dir = os.path.dirname(path)\n\n        # Check if we need to create missing parent directories\n        if not os.path.exists(base_dir):\n            os.makedirs(base_dir)\n\n    # Create the file by opening it in 'a' mode which creates the file if it\n    # does not exist yet but does not modify its contents\n    with open(path, 'a'):\n        pass\n\n\nclass Storage(ABC):\n    \"\"\"\n    The abstract base class for all Storages.\n\n    A Storage (de)serializes the current state of the database and stores it in\n    some place (memory, file on disk, ...).\n    \"\"\"\n\n    # Using ABCMeta as metaclass allows instantiating only storages that have\n    # implemented read and write\n\n    @abstractmethod\n    def read(self) -> Optional[Dict[str, Dict[str, Any]]]:\n        \"\"\"\n        Read the current state.\n\n        Any kind of deserialization should go here.\n\n        Return ``None`` here to indicate that the storage is empty.\n        \"\"\"\n\n        raise NotImplementedError('To be overridden!')\n\n    @abstractmethod\n    def write(self, data: Dict[str, Dict[str, Any]]) -> None:\n        \"\"\"\n        Write the current state of the database to the storage.\n\n        Any kind of serialization should go here.\n\n        :param data: The current state of the database.\n        \"\"\"\n\n        raise NotImplementedError('To be overridden!')\n\n    def close(self) -> None:\n        \"\"\"\n        Optional: Close open file handles, etc.\n        \"\"\"\n\n        pass\n\n\nclass JSONStorage(Storage):\n    \"\"\"\n    Store the data in a JSON file.\n    \"\"\"\n\n    def __init__(self, path: str, create_dirs=False, encoding=None, access_mode='r+', **kwargs):\n        \"\"\"\n        Create a new instance.\n\n        Also creates the storage file, if it doesn't exist and the access mode\n        is appropriate for writing.\n\n        **Note:** Using an access mode other than `r` or `r+` will probably\n        lead to data loss or data corruption!\n\n        **Note:** **Never** pass untrusted or user-controlled code as ``kwargs``\n        members like ``cls`` or ``default`` will be called on every write\n        operation.\n\n        :param path: Where to store the JSON data.\n        :param access_mode: mode in which the file is opened (r, r+)\n        :type access_mode: str\n        \"\"\"\n\n        super().__init__()\n\n        self._mode = access_mode\n        self.kwargs = kwargs\n\n        if access_mode not in ('r', 'rb', 'r+', 'rb+'):\n            warnings.warn(\n                'Using an `access_mode` other than \\'r\\', \\'rb\\', \\'r+\\' '\n                'or \\'rb+\\' can cause data loss or corruption'\n            )\n\n        # Create the file if it doesn't exist and creating is allowed by the\n        # access mode\n        if any([character in self._mode for character in ('+', 'w', 'a')]):  # any of the writing modes\n            touch(path, create_dirs=create_dirs)\n\n        # Open the file for reading/writing\n        self._handle = open(path, mode=self._mode, encoding=encoding)\n\n    def close(self) -> None:\n        self._handle.close()\n\n    def read(self) -> Optional[Dict[str, Dict[str, Any]]]:\n        # Get the file size by moving the cursor to the file end and reading\n        # its location\n        self._handle.seek(0, os.SEEK_END)\n        size = self._handle.tell()\n\n        if not size:\n            # File is empty, so we return ``None`` so TinyDB can properly\n            # initialize the database\n            return None\n        else:\n            # Return the cursor to the beginning of the file\n            self._handle.seek(0)\n\n            # Load the JSON contents of the file\n            return json.load(self._handle)\n\n    def write(self, data: Dict[str, Dict[str, Any]]):\n        # Move the cursor to the beginning of the file just in case\n        self._handle.seek(0)\n\n        # Serialize the database state using the user-provided arguments\n        serialized = json.dumps(data, **self.kwargs)\n\n        # Write the serialized data to the file\n        try:\n            self._handle.write(serialized)\n        except io.UnsupportedOperation:\n            raise IOError('Cannot write to the database. Access mode is \"{0}\"'.format(self._mode))\n\n        # Ensure the file has been written\n        self._handle.flush()\n        os.fsync(self._handle.fileno())\n\n        # Remove data that is behind the new cursor in case the file has\n        # gotten shorter\n        self._handle.truncate()\n\n\nclass MemoryStorage(Storage):\n    \"\"\"\n    Store the data as JSON in memory.\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"\n        Create a new instance.\n        \"\"\"\n\n        super().__init__()\n        self.memory = None\n\n    def read(self) -> Optional[Dict[str, Dict[str, Any]]]:\n        return self.memory\n\n    def write(self, data: Dict[str, Dict[str, Any]]):\n        self.memory = data\n"
  },
  {
    "path": "tinydb/table.py",
    "content": "\"\"\"\nThis module implements tables, the central place for accessing and manipulating\ndata in TinyDB.\n\"\"\"\n\nfrom typing import (\n    Callable,\n    Dict,\n    Iterable,\n    Iterator,\n    List,\n    Mapping,\n    NoReturn,\n    Optional,\n    Union,\n    cast,\n    Tuple,\n    overload\n)\n\nfrom .queries import QueryLike\nfrom .storages import Storage\nfrom .utils import LRUCache\n\n__all__ = ('Document', 'Table')\n\n\nclass Document(dict):\n    \"\"\"\n    A document stored in the database.\n\n    This class provides a way to access both a document's content and\n    its ID using ``doc.doc_id``.\n    \"\"\"\n\n    def __init__(self, value: Mapping, doc_id: int):\n        super().__init__(value)\n        self.doc_id = doc_id\n\n\nclass Table:\n    \"\"\"\n    Represents a single TinyDB table.\n\n    It provides methods for accessing and manipulating documents.\n\n    .. admonition:: Query Cache\n\n        As an optimization, a query cache is implemented using a\n        :class:`~tinydb.utils.LRUCache`. This class mimics the interface of\n        a normal ``dict``, but starts to remove the least-recently used entries\n        once a threshold is reached.\n\n        The query cache is updated on every search operation. When writing\n        data, the whole cache is discarded as the query results may have\n        changed.\n\n    .. admonition:: Customization\n\n        For customization, the following class variables can be set:\n\n        - ``document_class`` defines the class that is used to represent\n          documents,\n        - ``document_id_class`` defines the class that is used to represent\n          document IDs,\n        - ``query_cache_class`` defines the class that is used for the query\n          cache\n        - ``default_query_cache_capacity`` defines the default capacity of\n          the query cache\n\n        .. versionadded:: 4.0\n\n\n    :param storage: The storage instance to use for this table\n    :param name: The table name\n    :param cache_size: Maximum capacity of query cache\n    :param persist_empty: Store new table even with no operations on it\n    \"\"\"\n\n    #: The class used to represent documents\n    #:\n    #: .. versionadded:: 4.0\n    document_class = Document\n\n    #: The class used to represent a document ID\n    #:\n    #: .. versionadded:: 4.0\n    document_id_class = int\n\n    #: The class used for caching query results\n    #:\n    #: .. versionadded:: 4.0\n    query_cache_class = LRUCache\n\n    #: The default capacity of the query cache\n    #:\n    #: .. versionadded:: 4.0\n    default_query_cache_capacity = 10\n\n    def __init__(\n        self,\n        storage: Storage,\n        name: str,\n        cache_size: int = default_query_cache_capacity,\n        persist_empty: bool = False\n    ):\n        \"\"\"\n        Create a table instance.\n        \"\"\"\n\n        self._storage = storage\n        self._name = name\n        self._query_cache: LRUCache[QueryLike, List[Document]] \\\n            = self.query_cache_class(capacity=cache_size)\n\n        self._next_id = None\n        if persist_empty:\n            self._update_table(lambda table: table.clear())\n\n    def __repr__(self):\n        args = [\n            'name={!r}'.format(self.name),\n            'total={}'.format(len(self)),\n            'storage={}'.format(self._storage),\n        ]\n\n        return '<{} {}>'.format(type(self).__name__, ', '.join(args))\n\n    @property\n    def name(self) -> str:\n        \"\"\"\n        Get the table name.\n        \"\"\"\n        return self._name\n\n    @property\n    def storage(self) -> Storage:\n        \"\"\"\n        Get the table storage instance.\n        \"\"\"\n        return self._storage\n\n    def insert(self, document: Mapping) -> int:\n        \"\"\"\n        Insert a new document into the table.\n\n        :param document: the document to insert\n        :returns: the inserted document's ID\n        \"\"\"\n\n        # Make sure the document implements the ``Mapping`` interface\n        if not isinstance(document, Mapping):\n            raise ValueError('Document is not a Mapping')\n\n        # First, we get the document ID for the new document\n        if isinstance(document, self.document_class):\n            # For a `Document` object we use the specified ID\n            doc_id = document.doc_id\n\n            # We also reset the stored next ID so the next insert won't\n            # re-use document IDs by accident when storing an old value\n            self._next_id = None\n        else:\n            # In all other cases we use the next free ID\n            doc_id = self._get_next_id()\n\n        # Now, we update the table and add the document\n        def updater(table: dict):\n            if doc_id in table:\n                raise ValueError(f'Document with ID {str(doc_id)} '\n                                 f'already exists')\n\n            # By calling ``dict(document)`` we convert the data we got to a\n            # ``dict`` instance even if it was a different class that\n            # implemented the ``Mapping`` interface\n            table[doc_id] = dict(document)\n\n        # See below for details on ``Table._update``\n        self._update_table(updater)\n\n        return doc_id\n\n    def insert_multiple(self, documents: Iterable[Mapping]) -> List[int]:\n        \"\"\"\n        Insert multiple documents into the table.\n\n        :param documents: an Iterable of documents to insert\n        :returns: a list containing the inserted documents' IDs\n        \"\"\"\n        doc_ids = []\n\n        def updater(table: dict):\n            for document in documents:\n\n                # Make sure the document implements the ``Mapping`` interface\n                if not isinstance(document, Mapping):\n                    raise ValueError('Document is not a Mapping')\n\n                if isinstance(document, self.document_class):\n                    # Check if document does not override an existing document\n                    if document.doc_id in table:\n                        raise ValueError(\n                            f'Document with ID {str(document.doc_id)} '\n                            f'already exists'\n                        )\n\n                    # Store the doc_id, so we can return all document IDs\n                    # later. Then save the document with its doc_id and\n                    # skip the rest of the current loop\n                    doc_id = document.doc_id\n                    doc_ids.append(doc_id)\n                    table[doc_id] = dict(document)\n                    continue\n\n                # Generate new document ID for this document\n                # Store the doc_id, so we can return all document IDs\n                # later, then save the document with the new doc_id\n                doc_id = self._get_next_id()\n                doc_ids.append(doc_id)\n                table[doc_id] = dict(document)\n\n        # See below for details on ``Table._update``\n        self._update_table(updater)\n\n        return doc_ids\n\n    def all(self) -> List[Document]:\n        \"\"\"\n        Get all documents stored in the table.\n\n        :returns: a list with all documents.\n        \"\"\"\n\n        # iter(self) (implemented in Table.__iter__ provides an iterator\n        # that returns all documents in this table. We use it to get a list\n        # of all documents by using the ``list`` constructor to perform the\n        # conversion.\n\n        return list(iter(self))\n\n    def search(self, cond: QueryLike) -> List[Document]:\n        \"\"\"\n        Search for all documents matching a 'where' cond.\n\n        :param cond: the condition to check against\n        :returns: list of matching documents\n        \"\"\"\n\n        # First, we check the query cache to see if it has results for this\n        # query\n        cached_results = self._query_cache.get(cond)\n        if cached_results is not None:\n            return cached_results[:]\n\n        # Perform the search by applying the query to all documents.\n        # Then, only if the document matches the query, convert it\n        # to the document class and document ID class.\n        docs = [\n            self.document_class(doc, self.document_id_class(doc_id))\n            for doc_id, doc in self._read_table().items()\n            if cond(doc)\n        ]\n\n        # Only cache cacheable queries.\n        #\n        # This weird `getattr` dance is needed to make MyPy happy as\n        # it doesn't know that a query might have a `is_cacheable` method\n        # that is not declared in the `QueryLike` protocol due to it being\n        # optional.\n        # See: https://github.com/python/mypy/issues/1424\n        #\n        # Note also that by default we expect custom query objects to be\n        # cacheable (which means they need to have a stable hash value).\n        # This is to keep consistency with TinyDB's behavior before\n        # `is_cacheable` was introduced which assumed that all queries\n        # are cacheable.\n        is_cacheable: Callable[[], bool] = getattr(cond, 'is_cacheable',\n                                                   lambda: True)\n        if is_cacheable():\n            # Update the query cache\n            self._query_cache[cond] = docs[:]\n\n        return docs\n\n    @overload\n    def get(self) -> NoReturn: ...\n\n    @overload\n    def get(\n        self, cond: QueryLike, doc_id: None = ..., doc_ids: None = ...\n    ) -> Optional[Document]: ...\n\n    @overload\n    def get(\n        self, *, cond: QueryLike, doc_id: None = ..., doc_ids: None = ...\n    ) -> Optional[Document]: ...\n\n    @overload\n    def get(\n        self, cond: Optional[QueryLike], doc_id: int, doc_ids: Optional[List] = ...\n    ) -> Optional[Document]: ...\n\n    @overload\n    def get(\n        self, *, cond: Optional[QueryLike] = ..., doc_id: int, doc_ids: Optional[List] = ...,\n    ) -> Optional[Document]: ...\n\n    @overload\n    def get(\n        self, cond: Optional[QueryLike], doc_id: None, doc_ids: List\n    ) -> List[Document]: ...\n\n    @overload\n    def get(\n        self, cond: Optional[QueryLike], *, doc_id: None = ..., doc_ids: List\n    ) -> List[Document]: ...\n\n    @overload\n    def get(\n        self, *, cond: Optional[QueryLike] = ..., doc_id: None = ..., doc_ids: List\n    ) -> List[Document]: ...\n\n    def get(\n        self,\n        cond: Optional[QueryLike] = None,\n        doc_id: Optional[int] = None,\n        doc_ids: Optional[List] = None\n    ):\n        \"\"\"\n        Get exactly one document specified by a query or a document ID.\n        However, if multiple document IDs are given then returns all\n        documents in a list.\n        \n        Returns ``None`` if the document doesn't exist.\n\n        :param cond: the condition to check against\n        :param doc_id: the document's ID\n        :param doc_ids: the document's IDs(multiple)\n\n        :returns: the document(s) or ``None``\n        \"\"\"\n        table = self._read_table()\n\n        if doc_id is not None:\n            # Retrieve a document specified by its ID\n            raw_doc = table.get(str(doc_id), None)\n\n            if raw_doc is None:\n                return None\n\n            # Convert the raw data to the document class\n            return self.document_class(raw_doc, doc_id)\n\n        elif doc_ids is not None:\n            # Filter the table by extracting out all those documents which\n            # have doc id specified in the doc_id list.\n\n            # Since document IDs will be unique, we make it a set to ensure\n            # constant time lookup\n            doc_ids_set = set(str(doc_id) for doc_id in doc_ids)\n\n            # Now return the filtered documents in form of list\n            return [\n                self.document_class(doc, self.document_id_class(doc_id))\n                for doc_id, doc in table.items()\n                if doc_id in doc_ids_set\n            ]\n\n        elif cond is not None:\n            # Find a document specified by a query\n            # The trailing underscore in doc_id_ is needed so MyPy\n            # doesn't think that `doc_id_` (which is a string) needs\n            # to have the same type as `doc_id` which is this function's\n            # parameter and is an optional `int`.\n            for doc_id_, doc in self._read_table().items():\n                if cond(doc):\n                    return self.document_class(\n                        doc,\n                        self.document_id_class(doc_id_)\n                    )\n\n            return None\n\n        raise RuntimeError('You have to pass either cond or doc_id or doc_ids')\n\n    def contains(\n        self,\n        cond: Optional[QueryLike] = None,\n        doc_id: Optional[int] = None\n    ) -> bool:\n        \"\"\"\n        Check whether the database contains a document matching a query or\n        an ID.\n\n        If ``doc_id`` is set, it checks if the db contains the specified ID.\n\n        :param cond: the condition use\n        :param doc_id: the document ID to look for\n        \"\"\"\n        if doc_id is not None:\n            # Documents specified by ID\n            return self.get(doc_id=doc_id) is not None\n\n        elif cond is not None:\n            # Document specified by condition\n            return self.get(cond) is not None\n\n        raise RuntimeError('You have to pass either cond or doc_id')\n\n    def update(\n        self,\n        fields: Union[Mapping, Callable[[Mapping], None]],\n        cond: Optional[QueryLike] = None,\n        doc_ids: Optional[Iterable[int]] = None,\n    ) -> List[int]:\n        \"\"\"\n        Update all matching documents to have a given set of fields.\n\n        :param fields: the fields that the matching documents will have\n                       or a method that will update the documents\n        :param cond: which documents to update\n        :param doc_ids: a list of document IDs\n        :returns: a list containing the updated document's ID\n        \"\"\"\n\n        # Define the function that will perform the update\n        if callable(fields):\n            def perform_update(table, doc_id):\n                # Update documents by calling the update function provided by\n                # the user\n                fields(table[doc_id])\n        else:\n            def perform_update(table, doc_id):\n                # Update documents by setting all fields from the provided data\n                table[doc_id].update(fields)\n\n        if doc_ids is not None:\n            # Perform the update operation for documents specified by a list\n            # of document IDs\n\n            updated_ids = list(doc_ids)\n\n            def updater(table: dict):\n                # Call the processing callback with all document IDs\n                for doc_id in updated_ids:\n                    perform_update(table, doc_id)\n\n            # Perform the update operation (see _update_table for details)\n            self._update_table(updater)\n\n            return updated_ids\n\n        elif cond is not None:\n            # Perform the update operation for documents specified by a query\n\n            # Collect affected doc_ids\n            updated_ids = []\n\n            def updater(table: dict):\n                _cond = cast(QueryLike, cond)\n\n                # We need to convert the keys iterator to a list because\n                # we may remove entries from the ``table`` dict during\n                # iteration and doing this without the list conversion would\n                # result in an exception (RuntimeError: dictionary changed size\n                # during iteration)\n                for doc_id in list(table.keys()):\n                    # Pass through all documents to find documents matching the\n                    # query. Call the processing callback with the document ID\n                    if _cond(table[doc_id]):\n                        # Add ID to list of updated documents\n                        updated_ids.append(doc_id)\n\n                        # Perform the update (see above)\n                        perform_update(table, doc_id)\n\n            # Perform the update operation (see _update_table for details)\n            self._update_table(updater)\n\n            return updated_ids\n\n        else:\n            # Update all documents unconditionally\n\n            updated_ids = []\n\n            def updater(table: dict):\n                # Process all documents\n                for doc_id in list(table.keys()):\n                    # Add ID to list of updated documents\n                    updated_ids.append(doc_id)\n\n                    # Perform the update (see above)\n                    perform_update(table, doc_id)\n\n            # Perform the update operation (see _update_table for details)\n            self._update_table(updater)\n\n            return updated_ids\n\n    def update_multiple(\n        self,\n        updates: Iterable[\n            Tuple[Union[Mapping, Callable[[Mapping], None]], QueryLike]\n        ],\n    ) -> List[int]:\n        \"\"\"\n        Update all matching documents to have a given set of fields.\n\n        :returns: a list containing the updated document's ID\n        \"\"\"\n\n        # Define the function that will perform the update\n        def perform_update(fields, table, doc_id):\n            if callable(fields):\n                # Update documents by calling the update function provided\n                # by the user\n                fields(table[doc_id])\n            else:\n                # Update documents by setting all fields from the provided\n                # data\n                table[doc_id].update(fields)\n\n        # Perform the update operation for documents specified by a query\n\n        # Collect affected doc_ids\n        updated_ids = []\n\n        def updater(table: dict):\n            # We need to convert the keys iterator to a list because\n            # we may remove entries from the ``table`` dict during\n            # iteration and doing this without the list conversion would\n            # result in an exception (RuntimeError: dictionary changed size\n            # during iteration)\n            for doc_id in list(table.keys()):\n                for fields, cond in updates:\n                    _cond = cast(QueryLike, cond)\n\n                    # Pass through all documents to find documents matching the\n                    # query. Call the processing callback with the document ID\n                    if _cond(table[doc_id]):\n                        # Add ID to list of updated documents\n                        updated_ids.append(doc_id)\n\n                        # Perform the update (see above)\n                        perform_update(fields, table, doc_id)\n\n        # Perform the update operation (see _update_table for details)\n        self._update_table(updater)\n\n        return updated_ids\n\n    def upsert(self, document: Mapping, cond: Optional[QueryLike] = None) -> List[int]:\n        \"\"\"\n        Update documents, if they exist, insert them otherwise.\n\n        Note: This will update *all* documents matching the query. Document\n        argument can be a tinydb.table.Document object if you want to specify a\n        doc_id.\n\n        :param document: the document to insert or the fields to update\n        :param cond: which document to look for, optional if you've passed a\n        Document with a doc_id\n        :returns: a list containing the updated documents' IDs\n        \"\"\"\n\n        # Extract doc_id\n        if isinstance(document, self.document_class) and hasattr(document, 'doc_id'):\n            doc_ids: Optional[List[int]] = [document.doc_id]\n        else:\n            doc_ids = None\n\n        # Make sure we can actually find a matching document\n        if doc_ids is None and cond is None:\n            raise ValueError(\"If you don't specify a search query, you must \"\n                             \"specify a doc_id. Hint: use a table.Document \"\n                             \"object.\")\n\n        # Perform the update operation\n        try:\n            updated_docs: Optional[List[int]] = self.update(document, cond, doc_ids)\n        except KeyError:\n            # This happens when a doc_id is specified, but it's missing\n            updated_docs = None\n\n        # If documents have been updated: return their IDs\n        if updated_docs:\n            return updated_docs\n\n        # There are no documents that match the specified query -> insert the\n        # data as a new document\n        return [self.insert(document)]\n\n    def remove(\n        self,\n        cond: Optional[QueryLike] = None,\n        doc_ids: Optional[Iterable[int]] = None,\n    ) -> List[int]:\n        \"\"\"\n        Remove all matching documents.\n\n        :param cond: the condition to check against\n        :param doc_ids: a list of document IDs\n        :returns: a list containing the removed documents' ID\n        \"\"\"\n        if doc_ids is not None:\n            # This function returns the list of IDs for the documents that have\n            # been removed. When removing documents identified by a set of\n            # document IDs, it's this list of document IDs we need to return\n            # later.\n            # We convert the document ID iterator into a list, so we can both\n            # use the document IDs to remove the specified documents and\n            # to return the list of affected document IDs\n            removed_ids = list(doc_ids)\n\n            def updater(table: dict):\n                for doc_id in removed_ids:\n                    table.pop(doc_id)\n\n            # Perform the remove operation\n            self._update_table(updater)\n\n            return removed_ids\n\n        if cond is not None:\n            removed_ids = []\n\n            # This updater function will be called with the table data\n            # as its first argument. See ``Table._update`` for details on this\n            # operation\n            def updater(table: dict):\n                # We need to convince MyPy (the static type checker) that\n                # the ``cond is not None`` invariant still holds true when\n                # the updater function is called\n                _cond = cast(QueryLike, cond)\n\n                # We need to convert the keys iterator to a list because\n                # we may remove entries from the ``table`` dict during\n                # iteration and doing this without the list conversion would\n                # result in an exception (RuntimeError: dictionary changed size\n                # during iteration)\n                for doc_id in list(table.keys()):\n                    if _cond(table[doc_id]):\n                        # Add document ID to list of removed document IDs\n                        removed_ids.append(doc_id)\n\n                        # Remove document from the table\n                        table.pop(doc_id)\n\n            # Perform the remove operation\n            self._update_table(updater)\n\n            return removed_ids\n\n        raise RuntimeError('Use truncate() to remove all documents')\n\n    def truncate(self) -> None:\n        \"\"\"\n        Truncate the table by removing all documents.\n        \"\"\"\n\n        # Update the table by resetting all data\n        self._update_table(lambda table: table.clear())\n\n        # Reset document ID counter\n        self._next_id = None\n\n    def count(self, cond: QueryLike) -> int:\n        \"\"\"\n        Count the documents matching a query.\n\n        :param cond: the condition use\n        \"\"\"\n\n        return len(self.search(cond))\n\n    def clear_cache(self) -> None:\n        \"\"\"\n        Clear the query cache.\n        \"\"\"\n\n        self._query_cache.clear()\n\n    def __len__(self):\n        \"\"\"\n        Count the total number of documents in this table.\n        \"\"\"\n\n        return len(self._read_table())\n\n    def __iter__(self) -> Iterator[Document]:\n        \"\"\"\n        Iterate over all documents stored in the table.\n\n        :returns: an iterator over all documents.\n        \"\"\"\n\n        # Iterate all documents and their IDs\n        for doc_id, doc in self._read_table().items():\n            # Convert documents to the document class\n            yield self.document_class(doc, self.document_id_class(doc_id))\n\n    def _get_next_id(self):\n        \"\"\"\n        Return the ID for a newly inserted document.\n        \"\"\"\n\n        # If we already know the next ID\n        if self._next_id is not None:\n            next_id = self._next_id\n            self._next_id = next_id + 1\n\n            return next_id\n\n        # Determine the next document ID by finding out the max ID value\n        # of the current table documents\n\n        # Read the table documents\n        table = self._read_table()\n\n        # If the table is empty, set the initial ID\n        if not table:\n            next_id = 1\n            self._next_id = next_id + 1\n\n            return next_id\n\n        # Determine the next ID based on the maximum ID that's currently in use\n        max_id = max(self.document_id_class(i) for i in table.keys())\n        next_id = max_id + 1\n\n        # The next ID we will return AFTER this call needs to be larger than\n        # the current next ID we calculated\n        self._next_id = next_id + 1\n\n        return next_id\n\n    def _read_table(self) -> Dict[str, Mapping]:\n        \"\"\"\n        Read the table data from the underlying storage.\n\n        Documents and doc_ids are NOT yet transformed, as\n        we may not want to convert *all* documents when returning\n        only one document for example.\n        \"\"\"\n\n        # Retrieve the tables from the storage\n        tables = self._storage.read()\n\n        if tables is None:\n            # The database is empty\n            return {}\n\n        # Retrieve the current table's data\n        try:\n            table = tables[self.name]\n        except KeyError:\n            # The table does not exist yet, so it is empty\n            return {}\n\n        return table\n\n    def _update_table(self, updater: Callable[[Dict[int, Mapping]], None]):\n        \"\"\"\n        Perform a table update operation.\n\n        The storage interface used by TinyDB only allows to read/write the\n        complete database data, but not modifying only portions of it. Thus,\n        to only update portions of the table data, we first perform a read\n        operation, perform the update on the table data and then write\n        the updated data back to the storage.\n\n        As a further optimization, we don't convert the documents into the\n        document class, as the table data will *not* be returned to the user.\n        \"\"\"\n\n        tables = self._storage.read()\n\n        if tables is None:\n            # The database is empty\n            tables = {}\n\n        try:\n            raw_table = tables[self.name]\n        except KeyError:\n            # The table does not exist yet, so it is empty\n            raw_table = {}\n\n        # Convert the document IDs to the document ID class.\n        # This is required as the rest of TinyDB expects the document IDs\n        # to be an instance of ``self.document_id_class`` but the storage\n        # might convert dict keys to strings.\n        table = {\n            self.document_id_class(doc_id): doc\n            for doc_id, doc in raw_table.items()\n        }\n\n        # Perform the table update operation\n        updater(table)\n\n        # Convert the document IDs back to strings.\n        # This is required as some storages (most notably the JSON file format)\n        # don't support IDs other than strings.\n        tables[self.name] = {\n            str(doc_id): doc\n            for doc_id, doc in table.items()\n        }\n\n        # Write the newly updated data back to the storage\n        self._storage.write(tables)\n\n        # Clear the query cache, as the table contents have changed\n        self.clear_cache()\n"
  },
  {
    "path": "tinydb/utils.py",
    "content": "\"\"\"\nUtility functions.\n\"\"\"\n\nfrom collections import OrderedDict, abc\nfrom typing import List, Iterator, TypeVar, Generic, Union, Optional, Type, \\\n    TYPE_CHECKING\n\nK = TypeVar('K')\nV = TypeVar('V')\nD = TypeVar('D')\nT = TypeVar('T')\n\n__all__ = ('LRUCache', 'freeze', 'with_typehint')\n\n\ndef with_typehint(baseclass: Type[T]):\n    \"\"\"\n    Add type hints from a specified class to a base class:\n\n    >>> class Foo(with_typehint(Bar)):\n    ...     pass\n\n    This would add type hints from class ``Bar`` to class ``Foo``.\n\n    Note that while PyCharm and Pyright (for VS Code) understand this pattern,\n    MyPy does not. For that reason TinyDB has a MyPy plugin in\n    ``mypy_plugin.py`` that adds support for this pattern.\n    \"\"\"\n    if TYPE_CHECKING:\n        # In the case of type checking: pretend that the target class inherits\n        # from the specified base class\n        return baseclass\n\n    # Otherwise: just inherit from `object` like a regular Python class\n    return object\n\n\nclass LRUCache(abc.MutableMapping, Generic[K, V]):\n    \"\"\"\n    A least-recently used (LRU) cache with a fixed cache size.\n\n    This class acts as a dictionary but has a limited size. If the number of\n    entries in the cache exceeds the cache size, the least-recently accessed\n    entry will be discarded.\n\n    This is implemented using an ``OrderedDict``. On every access the accessed\n    entry is moved to the front by re-inserting it into the ``OrderedDict``.\n    When adding an entry and the cache size is exceeded, the last entry will\n    be discarded.\n    \"\"\"\n\n    def __init__(self, capacity=None) -> None:\n        self.capacity = capacity\n        self.cache: OrderedDict[K, V] = OrderedDict()\n\n    @property\n    def lru(self) -> List[K]:\n        return list(self.cache.keys())\n\n    @property\n    def length(self) -> int:\n        return len(self.cache)\n\n    def clear(self) -> None:\n        self.cache.clear()\n\n    def __len__(self) -> int:\n        return self.length\n\n    def __contains__(self, key: object) -> bool:\n        return key in self.cache\n\n    def __setitem__(self, key: K, value: V) -> None:\n        self.set(key, value)\n\n    def __delitem__(self, key: K) -> None:\n        del self.cache[key]\n\n    def __getitem__(self, key) -> V:\n        value = self.get(key)\n        if value is None:\n            raise KeyError(key)\n\n        return value\n\n    def __iter__(self) -> Iterator[K]:\n        return iter(self.cache)\n\n    def get(self, key: K, default: Optional[D] = None) -> Optional[Union[V, D]]:\n        value = self.cache.get(key)\n\n        if value is not None:\n            self.cache.move_to_end(key, last=True)\n\n            return value\n\n        return default\n\n    def set(self, key: K, value: V):\n        if key in self.cache:\n            self.cache[key] = value\n            self.cache.move_to_end(key, last=True)\n        else:\n            self.cache[key] = value\n\n            # Check, if the cache is full and we have to remove old items\n            # If the queue is of unlimited size, self.capacity is NaN and\n            # x > NaN is always False in Python and the cache won't be cleared.\n            if self.capacity is not None and self.length > self.capacity:\n                self.cache.popitem(last=False)\n\n\nclass FrozenDict(dict):\n    \"\"\"\n    An immutable dictionary.\n\n    This is used to generate stable hashes for queries that contain dicts.\n    Usually, Python dicts are not hashable because they are mutable. This\n    class removes the mutability and implements the ``__hash__`` method.\n    \"\"\"\n\n    def __hash__(self):\n        # Calculate the has by hashing a tuple of all dict items\n        return hash(tuple(sorted(self.items())))\n\n    def _immutable(self, *args, **kws):\n        raise TypeError('object is immutable')\n\n    # Disable write access to the dict\n    __setitem__ = _immutable\n    __delitem__ = _immutable\n    clear = _immutable\n    setdefault = _immutable  # type: ignore\n    popitem = _immutable\n\n    def update(self, e=None, **f):\n        raise TypeError('object is immutable')\n\n    def pop(self, k, d=None):\n        raise TypeError('object is immutable')\n\n\ndef freeze(obj):\n    \"\"\"\n    Freeze an object by making it immutable and thus hashable.\n    \"\"\"\n    if isinstance(obj, dict):\n        # Transform dicts into ``FrozenDict``s\n        return FrozenDict((k, freeze(v)) for k, v in obj.items())\n    elif isinstance(obj, list):\n        # Transform lists into tuples\n        return tuple(freeze(el) for el in obj)\n    elif isinstance(obj, set):\n        # Transform sets into ``frozenset``s\n        return frozenset(obj)\n    else:\n        # Don't handle all other objects\n        return obj\n"
  },
  {
    "path": "tinydb/version.py",
    "content": "__version__ = '4.8.2'\n"
  }
]