[
  {
    "path": ".coveragerc",
    "content": "[run]\nrelative_files = True\n[report]\nomit =\n    */tests/*\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".flake8",
    "content": "[flake8]\nextend-exclude = .venv\nstatistics = True\ncount = True\nshow-source = True\n# The GitHub editor is 127 chars wide\nmax-line-length = 127\n# Ignore valid SQLAlchemy NULL query syntax\nextend-ignore = E711\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"pip\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL Advanced\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n  schedule:\n    - cron: '20 16 * * 3'\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: actions\n          build-mode: none\n        - language: python\n          build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Add any setup steps before running the `github/codeql-action/init` action.\n    # This includes steps like installing compilers or runtimes (`actions/setup-node`\n    # or others). This is typically only required for manual builds.\n    # - name: Setup runtime (example)\n    #   uses: actions/setup-example@v1\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n    # If the analyze step fails for one of the languages you are analyzing with\n    # \"We were unable to automatically build your code\", modify the matrix above\n    # to set the build mode to \"manual\" for that language. Then modify this step\n    # to build your code.\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n    - if: matrix.build-mode == 'manual'\n      shell: bash\n      run: |\n        echo 'If you are using a \"manual\" build mode for one or more of the' \\\n          'languages you are analyzing, replace this with the commands to build' \\\n          'your code, for example:'\n        echo '  make bootstrap'\n        echo '  make release'\n        exit 1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "# This workflow will upload a Python Package to PyPI when a release is created\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries\n\nname: Upload Python Package\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: read\n\njobs:\n  release-build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v5\n        with:\n          python-version: \"3.x\"\n\n      - name: Build release distributions\n        run: |\n          # NOTE: put your own distribution build steps here.\n          python -m pip install build\n          python -m build\n\n      - name: Upload distributions\n        uses: actions/upload-artifact@v4\n        with:\n          name: release-dists\n          path: dist/\n\n  pypi-publish:\n    runs-on: ubuntu-latest\n    needs:\n      - release-build\n    permissions:\n      # IMPORTANT: this permission is mandatory for trusted publishing\n      id-token: write\n\n    # Dedicated environments with protections for publishing are strongly recommended.\n    # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules\n    environment:\n      name: pypi\n      # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:\n      # url: https://pypi.org/p/YOURPROJECT\n      #\n      # ALTERNATIVE: if your GitHub Release name is the PyPI project version string\n      # ALTERNATIVE: exactly, uncomment the following line instead:\n      url: https://pypi.org/project/sqlalchemy_mptt/${{ github.event.release.name }}\n\n    steps:\n      - name: Retrieve release distributions\n        uses: actions/download-artifact@v4\n        with:\n          name: release-dists\n          path: dist/\n\n      - name: Publish release distributions to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          packages-dir: dist/\n"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: Check code and run tests\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\npermissions:\n  contents: read\n\njobs:\n  generate-jobs:\n    name: Generate build matrix for tests\n    runs-on: ubuntu-latest\n    outputs:\n      session: ${{ steps.set-matrix.outputs.session }}\n    steps:\n    - uses: actions/checkout@v4\n    - uses: astral-sh/setup-uv@v6\n    - name: Generate build matrix\n      id: set-matrix\n      shell: bash\n      run: echo session=$(uv run noxfile.py --json -l | jq -c '[.[].session]') | tee --append $GITHUB_OUTPUT\n  checks:\n    name: Run ${{ matrix.session }}\n    needs: [generate-jobs]\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        session: ${{ fromJson(needs.generate-jobs.outputs.session) }}\n    steps:\n    - uses: actions/checkout@v4\n    - uses: astral-sh/setup-uv@v6\n    - name: Run nox\n      run: >\n        uv run noxfile.py -s \"${{ matrix.session }}\"\n        -- --pyargs sqlalchemy_mptt --cov-report xml\n    - name: Upload coverage data\n      if: ${{ startsWith(matrix.session, 'test(') }}\n      uses: coverallsapp/github-action@v2\n      with:\n        flag-name: run-${{ join(matrix.*, '-') }}\n        parallel: true\n        debug: true\n\n  coveralls_finish:\n    name: Finalize coverage\n    needs: checks\n    runs-on: ubuntu-latest\n    steps:\n    - uses: coverallsapp/github-action@v2\n      with:\n        parallel-finished: true\n"
  },
  {
    "path": ".gitignore",
    "content": "uv.lock\n.eggs\n.env\n*~\n*.swo\n*.swp\n.settings\n.project\n.pydevproject\nsqlalchemy_mptt/.coverage\nsqlalchemy_mptt/TODO\n*.pyc\nsqlalchemy_mptt/cover\ncover\n.coverage\nbuild\ndist\n*.egg-info\nexample\nTODO\nTODO.txt\nnohup.out\n.cache\n.tox\n__pycache__\n_build\n.ropeproject\n_themes\n.aider.*\n.vscode/\ncoverage.xml\n.hypothesis/\n.nox\n.pytest_cache\n.venv\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v5.0.0\n    hooks:\n    -   id: trailing-whitespace\n    -   id: end-of-file-fixer\n        exclude: '^.*\\.svg$'\n    -   id: check-yaml\n    -   id: check-toml\n    -   id: check-added-large-files\n-   repo: https://github.com/pycqa/flake8\n    rev: '7.3.0'\n    hooks:\n    -   id: flake8\n-   repo: https://github.com/pappasam/toml-sort\n    rev: 'v0.24.2'\n    hooks:\n    -   id: toml-sort\n-   repo: https://github.com/pycqa/isort\n    rev: '7.0.0'\n    hooks:\n    -   id: isort\n        name: isort (python)\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file for Sphinx projects\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n    # You can also specify other tool versions:\n    # nodejs: \"20\"\n    # rust: \"1.70\"\n    # golang: \"1.20\"\n\n# Build documentation in the \"docs/\" directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n  # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs\n  # builder: \"dirhtml\"\n  # Fail on all warnings to avoid broken references\n  # fail_on_warning: true\n\n# Optionally build your docs in additional formats such as PDF and ePub\n# formats:\n#   - pdf\n#   - epub\n\n# Optional but recommended, declare the Python requirements required\n# to build your documentation\n# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\n#python:\n#  install:\n#    - requirements: requirements.txt\npython:\n  install:\n    - method: pip\n      path: .\n"
  },
  {
    "path": ".rstcheck.cfg",
    "content": "[rstcheck]\nignore_directives=automodule,autofunction,autoclass,code-block\nignore_roles=mod,py:mod\n"
  },
  {
    "path": "CHANGES.rst",
    "content": "Versions releases 0.2.x & above\n###############################\n\n0.6.0 (2025-11-29)\n==================\n\nsee issues #109, #111, #112 & #113\n\n- Add support for SQLAlchemy 2.0.\n- Add official support for Python 3.12, 3.13 and 3.14.\n- Remove examples of defunct features from the documentation.\n\n0.5.0 (2025-11-18)\n==================\n\nsee issues #104 & #110\n\n- Add support for SQLAlchemy 1.4.\n- Drop official support for PyPy.\n- Simplify memory management by using ``weakref.WeakSet`` instead of rolling our own\n  weak reference set.\n- Unify ``after_flush_postexec`` execution path for CPython & PyPy.\n- Simplify ``get_siblings``.\n- Run doctest on all code snippets in the documentation.\n- Fix some of the incorrect documentation snippets.\n\n0.4.0 (2025-05-30)\n==================\n\nsee issue #97\n\n- Support for Python 3.10 and 3.11\n- Dropped support for Python 3.7\n\n0.3.0 (2025-05-10)\n==================\n\nsee issues #63, #87, #89 & #90\n\n- Support for joined-table inheritance\n- Restrict to Python & PyPy 3.7 - 3.9\n- Restrict to SQLA 1.0 - 1.3\n- Fixes race condition with garbage collection on PyPy versions\n\n0.2.5 (2019-07-23)\n==================\n\nsee issue #64\n\n- Added similar `django_mptt` methods `get_siblings` and `get_children`\n\n0.2.4 (2018-12-14)\n==================\n\nsee PR #61\n\n- Allow to specify ordering of path_to_root\n\n0.2.3 (2018-06-03)\n==================\n\nsee issue #57\n\n- Fix rebuild tree\n- Added support node's identifier start from 0\n\n0.2.2 (2017-10-05)\n==================\n\nsee issue #56\n\n- Added custom default root level. Support Django style level=0\n\n0.2.1 (2016-01-23)\n==================\n\nsee PR #51\n\n- fix of index columns names\n\n0.2.0 (2015-11-13)\n==================\n\nsee PR #50\n\n- Changed ``parent_id`` to dynamically match the type of the primary_key\n- exposed drilldown_tree's logic and path_to_root's logic as both instance and\n  class level method\n"
  },
  {
    "path": "CHANGES_OLD.rst",
    "content": "Versions releases 0.1.x\n#######################\n\n0.1.9 (2015-09-24)\n==================\n\n- add option ``remove`` to ``sqlalchemy.events.TreesManager.register_mapper``\n\n0.1.8 (2015-09-14)\n==================\n\n- add method ``_base_query_obj``\n\n0.1.7 (2015-08-18)\n==================\n\n- add method ``path_to_root`` (see #46)\n- add data integrity tests\n\n0.1.6 (2015-07-03)\n==================\n\n- fix bug with ``get_tree`` when no rows in database.\n\n0.1.5 (2015-06-25)\n==================\n\n- Add drilldown_tree method (see #41)\n- Add custom ``query`` atribute to ``get_tree`` method\n\n0.1.4 (2015-06-19)\n==================\n\n- delete method ``get_pk_with_class_name``\n\nBug Fixes\n---------\n\n- fix ``_get_tree_table`` function for inheritance models\n\n0.1.3 (2015-06-17)\n==================\n\n- Add test for swap trees\n- rename ``get_pk`` method to ``get_pk_name``\n- rename ``get_db_pk`` method to ``get_pk_column``\n- rename ``get_class_pk`` method to ``get_pk_with_class_name``\n\nBug Fixes\n---------\n\n- Fix order of elements in tree\n\n0.1.2 (2015-04-22)\n==================\n\nBug Fixes\n---------\n\n- Fix MANIFEST.in file\n\nDeprecation\n-----------\n\n- Delete ``BaseNestedSets.register_tree`` method\n- Delete ``BaseNestedSets.get_tree_recursively`` method\n\n0.1.1 (2015-04-21)\n==================\n\nFeatures\n--------\n\n- Add test for rst docs and migrate on new itcase_sphinx_theme (#40)\n\nBug Fixes\n---------\n\n- Remove recursion from BaseNestedSets.get_tree method (#39)\n\n0.1.0 (2014-11-18)\n==================\n\nBug Fixes\n---------\n\n- Fix concurrency issue with multiple session (#36)\n- Flushing the session now expire the instance and it's children (#33)\n\n0.0.9 (2014-10-09)\n==================\n\n- Add MANIFEST.in\n- New docs\n- fixes in setup.py\n\n0.0.8 (2014-08-15)\n==================\n\n- Add CONTRIBUTORS.txt\n\nFeatures\n--------\n\n- Automatically register tree classes enhancement (#28)\n- Added support polymorphic tree models (#24)\n\nBug Fixes\n---------\n\n- Fix expire left/right attributes of parent somewhen after the `before_insert` event (#30)\n- Fix tree_id is incorrectly set to an existing tree if no parent is set (#23)\n- Fix package is not installable if sqlalchemy is not (yet) installed (#22)\n\n0.0.7 (2014-08-04)\n==================\n\n- Add LICENSE.txt\n\nBug Fixes\n---------\n\n- fix get_db_pk function\n\n\n0.0.6 (2014-07-31)\n==================\n\nBug Fixes\n---------\n\n-  Allow the primary key to not be named \"id\" #20. See https://github.com/uralbash/sqlalchemy_mptt/issues/20\n"
  },
  {
    "path": "CONTRIBUTORS.txt",
    "content": "Contributors\n------------\n\n- Dmitry Svintsov (uralbash), 2014/04/16\n- Jonathan Stoppani, 2014/08/11\n- Fayaz Yusuf Khan, 2014/10/10\n- Greg Klupar, 2015/11/13\n- Jiri Kuncar, 2016/01/20\n- Sylvain Thénault (sthenault), 2018/06/03\n- Musharraf (mush42), 2019/02/12\n- Tim Gates (timgates42), 2020/03/27\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 uralbash\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include requirements.txt\ninclude requirements-test.txt\ninclude README.rst CHANGES.rst\n"
  },
  {
    "path": "README.rst",
    "content": "|PyPI Version| |PyPI Downloads| |PyPI Python Versions|\n|Build Status| |Coverage Status|\n\nLibrary for implementing Modified Preorder Tree Traversal with your\nSQLAlchemy Models and working with trees of Model instances, like\ndjango-mptt. Docs http://sqlalchemy-mptt.readthedocs.io/\n\n.. image:: https://cdn.rawgit.com/uralbash/sqlalchemy_mptt/master/docs/img/2_sqlalchemy_mptt_traversal.svg\n   :alt: Nested sets traversal\n   :width: 800px\n\nThe nested set model is a particular technique for representing nested\nsets (also known as trees or hierarchies) in relational databases.\n\nInstalling\n----------\n\nInstall from github:\n\n.. code-block:: bash\n\n    pip install git+http://github.com/uralbash/sqlalchemy_mptt.git\n\nPyPi:\n\n.. code-block:: bash\n\n    pip install sqlalchemy_mptt\n\nSource:\n\n.. code-block:: bash\n\n    pip install -e .\n\nUsage\n-----\n\nAdd mixin to model\n\n.. code-block:: python\n\n    from sqlalchemy import Column, Integer, Boolean\n    from sqlalchemy.ext.declarative import declarative_base\n\n    from sqlalchemy_mptt.mixins import BaseNestedSets\n\n    Base = declarative_base()\n\n\n    class Tree(Base, BaseNestedSets):\n        __tablename__ = \"tree\"\n\n        id = Column(Integer, primary_key=True)\n        visible = Column(Boolean)\n\n        def __repr__(self):\n            return \"<Node (%s)>\" % self.id\n\nNow you can add, move and delete obj!\n\nInsert node\n-----------\n\n.. code-block:: python\n\n    node = Tree(parent_id=6)\n    session.add(node)\n\n::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level     Insert node with parent_id == 6\n            1                    1(1)24\n                    _______________|_________________\n                   |               |                 |\n            2    2(2)5           6(4)13           14(7)23\n                   |           ____|____          ___|____\n                   |          |         |        |        |\n            3    3(3)4      7(5)8    9(6)12  15(8)18   19(10)22\n                                       |        |         |\n            4                      10(23)11  16(9)17  20(11)21\n\nDelete node\n-----------\n\n.. code:: python\n\n    node = session.query(Tree).filter(Tree.id == 4).one()\n    session.delete(node)\n\n::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level         Delete node == 4\n            1                    1(1)16\n                    _______________|_____\n                   |                     |\n            2    2(2)5                 6(7)15\n                   |                     ^\n            3    3(3)4            7(8)10   11(10)14\n                                    |          |\n            4                     8(9)9    12(11)13\n\nUpdate node\n-----------\n\n.. code:: python\n\n    node = session.query(Tree).filter(Tree.id == 8).one()\n    node.parent_id = 5\n    session.add(node)\n\n::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 8 - > 5\n                1                     1(1)22\n                         _______________|__________________\n                        |               |                  |\n                2     2(2)5           6(4)15            16(7)21\n                        |               ^                  |\n                3     3(3)4      7(5)12   13(6)14      17(10)20\n                                   |                        |\n                4                8(8)11                18(11)19\n                                   |\n                5                9(9)10\n\nMove node (support multitree)\n-----------------------------\n\n.. figure:: https://cdn.rawgit.com/uralbash/sqlalchemy_mptt/master/docs/img/3_sqlalchemy_mptt_multitree.svg\n   :alt: Nested sets multitree\n   :width: 600px\n\n   Nested sets multitree\n\nMove inside\n\n.. code:: python\n\n    node = session.query(Tree).filter(Tree.id == 4).one()\n    node.move_inside(\"15\")\n\n::\n\n                     4 -> 15\n            level           Nested sets tree1\n            1                    1(1)16\n                    _______________|_____________________\n                   |                                     |\n            2    2(2)5                                 6(7)15\n                   |                                     ^\n            3    3(3)4                            7(8)10   11(10)14\n                                                    |          |\n            4                                     8(9)9    12(11)13\n\n            level           Nested sets tree2\n            1                     1(12)28\n                     ________________|_______________________\n                    |                |                       |\n            2    2(13)5            6(15)17                18(18)27\n                   |                 ^                        ^\n            3    3(14)4    7(4)12 13(16)14  15(17)16  19(19)22  23(21)26\n                             ^                            |         |\n            4          8(5)9  10(6)11                 20(20)21  24(22)25\n\nMove after\n\n.. code:: python\n\n    node = session.query(Tree).filter(Tree.id == 8).one()\n    node.move_after(\"5\")\n\n::\n\n           level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 8 after 5\n                1                     1(1)22\n                         _______________|__________________\n                        |               |                  |\n                2     2(2)5           6(4)15            16(7)21\n                        |               ^                  |\n                3     3(3)4    7(5)8  9(8)12  13(6)14   17(10)20\n                                        |                  |\n                4                    10(9)11            18(11)19\n\nMove to top level\n\n.. code:: python\n\n    node = session.query(Tree).filter(Tree.id == 15).one()\n    node.move_after(\"1\")\n\n::\n\n            level           tree_id = 1\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level           tree_id = 2\n            1                     1(15)6\n                                     ^\n            2                 2(16)3   4(17)5\n\n            level           tree_id = 3\n            1                    1(12)16\n                     _______________|\n                    |               |\n            2    2(13)5          6(18)15\n                    |               ^\n            3    3(14)4     7(19)10   11(21)14\n                               |          |\n            4               8(20)9    12(22)13\n\nSupport and Development\n=======================\n\nTo report bugs, use the `issue tracker\n<https://github.com/uralbash/sqlalchemy_mptt/issues>`_.\n\nWe welcome any contribution: suggestions, ideas, commits with new\nfutures, bug fixes, refactoring, docs, tests, translations, etc...\n\nIf you have any questions:\n\n* Use the `Discussion board <https://github.com/uralbash/sqlalchemy_mptt/discussions>`_\n* Contact the maintainer via email: fayaz.yusuf.khan@gmail.com\n* Contact the author via email: sacrud@uralbash.ru or #sacrud IRC channel |IRC Freenode|\n\nRefer the detailed contribution guide in the `docs <https://sqlalchemy-mptt.readthedocs.io/CONTRIBUTING.html>`_\nfor more information on setting up the development environment, running tests, and contributing to the project.\n\nLicense\n=======\n\nThe project is licensed under the MIT license.\n\n.. |PyPI Version| image:: https://img.shields.io/pypi/v/sqlalchemy_mptt\n   :alt: PyPI - Version\n.. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/sqlalchemy_mptt\n   :alt: PyPI - Downloads\n.. |PyPI Python Versions| image:: https://img.shields.io/pypi/pyversions/sqlalchemy_mptt\n   :alt: PyPI - Python Version\n.. |Build Status| image:: https://github.com/uralbash/sqlalchemy_mptt/actions/workflows/run-tests.yml/badge.svg?branch=master\n   :target: https://github.com/uralbash/sqlalchemy_mptt/actions/workflows/run-tests.yml\n.. |Coverage Status| image:: https://coveralls.io/repos/uralbash/sqlalchemy_mptt/badge.png\n   :target: https://coveralls.io/r/uralbash/sqlalchemy_mptt\n.. |IRC Freenode| image:: https://img.shields.io/badge/irc-freenode-blue.svg\n   :target: https://webchat.freenode.net/?channels=sacrud\n"
  },
  {
    "path": "RELEASING.rst",
    "content": "Releasing\n=========\n\n1. Merge all intended and verified pull requests into the ``master`` branch.\n2. Create a local build and test:\n    - Run ``uv run noxfile.py -s build`` to create a source distribution and a wheel.\n    - Install both artifacts in a fresh virtual environment to ensure they work correctly.\n3. Bump the version number in ``setup.py``. (May be included in the pull request.)\n4. Update the changelog in ``CHANGES.rst``.\n5. Add new contributors to the ``CONTRIBUTORS.rst`` file.\n6. Update the release date in ``CHANGES.rst``.\n7. Ensure the latest build passes on GitHub Actions.\n8. Rebuild the documentation and check that it looks correct.\n9. Create a new release on GitHub:\n   - Use the version number as the tag.\n   - Include the changelog in the release notes.\n10. Ensure the release is published.\n11. Test the release by installing it in a fresh virtual environment.\n"
  },
  {
    "path": "docs/CONTRIBUTING.rst",
    "content": "Contribution Guidelines\n=======================\n\nAll types of contributions are welcome: suggestions, ideas, commits\nwith new features, bug fixes, refactoring, docs, tests, translations, etc...\n\nIf you have any questions:\n\n* Use the `Discussion board <https://github.com/uralbash/sqlalchemy_mptt/discussions>`_\n* Contact the maintainer via email: fayaz.yusuf.khan@gmail.com\n* Contact the author via email: sacrud@uralbash.ru or #sacrud IRC channel |IRC Freenode|\n\nDevelopment Setup\n-----------------\n\nTo set up the development environment, you can run:\n\n.. code-block:: bash\n\n    # Install uv\n    $ pip install uv\n    # Run the noxfile.py script\n    $ uv run noxfile.py -s dev\n\nBy default, this will create a virtual environment with Python 3.8 and install all the required dependencies.\nIf you need to setup the development environment with a specific Python version, you can run:\n\n.. code-block:: bash\n\n    $ uv run noxfile.py -s dev -P 3.10\n\nRunning Tests\n-------------\n\nTo run the tests and linters, you can use the following command:\n\n.. code-block:: bash\n\n    $ uv run noxfile.py\n\nFor futher details, refer to the ``noxfile.py`` script.\n\nBuilding Documentation\n----------------------\n\nThe documentation on `ReadtheDocs <https://app.readthedocs.org/projects/sqlalchemy-mptt/>`_ is manually built from the master branch.\nTo build the documentation locally, you can run:\n\n.. code-block:: bash\n\n    $ uv tool install sphinx --with-editable . --with-requirements requirements-doctest.txt\n    $ cd docs\n    $ make html\n\nFor futher details, refer to the ``docs/Makefile``.\n\n.. |IRC Freenode| image:: https://img.shields.io/badge/irc-freenode-blue.svg\n   :target: https://webchat.freenode.net/?channels=sacrud\n"
  },
  {
    "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/sqlalchemy_mptt.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/sqlalchemy_mptt.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/sqlalchemy_mptt\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sqlalchemy_mptt\"\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/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# sqlalchemy_mptt documentation build configuration file, created by\n# sphinx-quickstart on Wed Jun 25 14:00:12 2014.\n#\n# This file is execfile()d with the current directory set to its\n# containing 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.\nfrom datetime import date\nimport os\nimport sys\n\nfrom docutils.parsers.rst import directives\nfrom sphinx.directives.code import CodeBlock\n\ndirectives.register_directive('no-code-block', CodeBlock)\n\nsys.path.insert(0, os.path.abspath(\".\"))\nsys.path.insert(0, os.path.abspath(\"../\"))\n\n# -- General configuration ------------------------------------------------\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\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.viewcode',\n    'sphinx.ext.mathjax',\n    'sphinx.ext.doctest',\n]\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 master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'sqlalchemy_mptt'\ncopyright = '{}, uralbash'.format(date.today().year)\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 name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# -- Options for HTML output ----------------------------------------------\n\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# Output file base name for HTML help builder.\nhtmlhelp_basename = 'sqlalchemy_mpttdoc'\n\nhtml_theme_options = {\n    'github_button': True,\n    'github_user': 'uralbash',\n    'github_repo': 'sqlalchemy_mptt',\n}\n\n# -- Options for doctest extension ------------------------------------------\ndoctest_global_setup = \"\"\"\nfrom sqlalchemy import create_engine, Column, Integer, Boolean\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm import Session\n\nfrom sqlalchemy_mptt import tree_manager\nfrom sqlalchemy_mptt.mixins import BaseNestedSets\n\"\"\"\ndoctest_global_cleanup = \"\"\"\ntry:\n    session.flush()\nexcept NameError:\n    pass\n\"\"\"\n"
  },
  {
    "path": "docs/crud.rst",
    "content": "Usage\n=====\n\nINSERT\n------\n\nInsert node with parent_id==6\n\n.. testsetup::\n\n    Base = declarative_base()\n    engine = create_engine(\"sqlite:///:memory:\")\n    session = Session(engine)\n\n    class Tree(Base, BaseNestedSets):\n        __tablename__ = \"tree\"\n\n        id = Column(Integer, primary_key=True)\n        visible = Column(Boolean)\n\n        def __repr__(self):\n            return \"<Node (%s)>\" % self.id\n\n    Base.metadata.create_all(engine)\n    tree_manager.register_events(remove=True)\n    instances = [\n        Tree(id=1, parent_id=None),\n        Tree(id=2, parent_id=1),\n        Tree(id=3, parent_id=2),\n        Tree(id=4, parent_id=1),\n        Tree(id=5, parent_id=4),\n        Tree(id=6, parent_id=4),\n        Tree(id=7, parent_id=1),\n        Tree(id=8, parent_id=7),\n        Tree(id=9, parent_id=8),\n        Tree(id=10, parent_id=7),\n        Tree(id=11, parent_id=10)\n    ]\n    for instance in instances:\n        instance.left = 0\n        instance.right = 0\n        instance.visible = True\n    session.add_all(instances)\n    session.flush()\n    tree_manager.register_events()\n    Tree.rebuild_tree(session, tree_id=None)\n\n\n.. testcode::\n\n    node = Tree(parent_id=6)\n    session.add(node)\n\nTree state before insert\n\n.. code::\n\n    level           Before INSERT\n    1                    1(1)22\n            _______________|___________________\n           |               |                   |\n    2    2(2)5           6(4)11             12(7)21\n           |               ^                   ^\n    3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                          |          |\n    4                                  14(9)15   18(11)19\n\nAfter insert\n\n.. code::\n\n    level           After INSERT\n    1                    1(1)24\n            _______________|_________________\n           |               |                 |\n    2    2(2)5           6(4)13           14(7)23\n           |           ____|___          ____|____\n           |          |        |        |         |\n    3    3(3)4      7(5)8    9(6)12  15(8)18   19(10)22\n                               |        |         |\n    4                      10(23)11  16(9)17   20(11)21\n\nUPDATE\n------\n\nSet parent_id=5 for node with id==8\n\n.. testcode::\n\n    node = session.query(Tree).filter(Tree.id == 8).one()\n    node.parent_id = 5\n    session.add(node)\n\nTree state before update\n\n.. code::\n\n    level           Before UPDATE\n    1                    1(1)22\n            _______________|___________________\n           |               |                   |\n    2    2(2)5           6(4)11             12(7)21\n           |               ^                   ^\n    3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                          |          |\n    4                                  14(9)15   18(11)19\n\nAfter update\n\n.. code::\n\n    level               Move 8 - > 5\n        1                     1(1)22\n                 _______________|__________________\n                |               |                  |\n        2     2(2)5           6(4)15            16(7)21\n                |               ^                  |\n        3     3(3)4      7(5)12   13(6)14      17(10)20\n                           |                       |\n        4                8(8)11                18(11)19\n                           |\n        5                9(9)10\n\nDELETE\n------\n\nDelete node with id==4\n\n.. testcode::\n\n    node = session.query(Tree).filter(Tree.id == 4).one()\n    session.delete(node)\n\nTree state before delete\n\n.. code::\n\n    level           Before DELETE\n    1                    1(1)22\n            _______________|___________________\n           |               |                   |\n    2    2(2)5           6(4)11             12(7)21\n           |               ^                   ^\n    3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                          |          |\n    4                                  14(9)15   18(11)19\n\nAfter delete\n\n.. code::\n\n    level         Delete node == 4\n    1                    1(1)16\n            _______________|_____\n           |                     |\n    2    2(2)5                 6(7)15\n           |                     ^\n    3    3(3)4            7(8)10   11(10)14\n                            |          |\n    4                     8(9)9    12(11)13\n\nFor more example see :mod:`sqlalchemy_mptt.tests.TestTree`\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. sqlalchemy_mptt documentation master file, created by\n   sphinx-quickstart on Wed Jun 25 14:00:12 2014.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nsqlalchemy_mptt\n===============\n\n.. image:: _static/mptt_insert.jpg\n    :alt: MPTT (nested sets) INSERT\n\nLibrary for implementing Modified Preorder Tree Traversal with your\n`SQLAlchemy` Models and working with trees of Model instances, like\n`django-mptt`.\nThe nested set model is a particular technique for representing nested\nsets (also known as trees or hierarchies) in relational databases.\n\nWhere used\n----------\n\n* ps_tree_\n* pyramid_pages_\n* your project ...\n\nManual\n------\n\n.. toctree::\n\n    initialize.rst\n    crud.rst\n\nAPI:\n----\n\n.. toctree::\n   :maxdepth: 2\n\n   sqlalchemy_mptt\n\nTutorial\n--------\n\n.. toctree::\n\n    tut_flask.rst\n\nA lot of examples and logic in\n:py:mod:`sqlalchemy_mptt.tests.cases`\n\nSupport and Development\n=======================\n\n.. toctree::\n\n    CONTRIBUTING.rst\n\nChangelog\n=========\n\n.. toctree::\n    :maxdepth: 1\n\n    CHANGES.rst\n    CHANGES_OLD.rst\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n.. _ps_tree: https://github.com/sacrud/ps_tree\n.. _pyramid_pages: https://github.com/uralbash/pyramid_pages\n"
  },
  {
    "path": "docs/initialize.rst",
    "content": "Setup\n=====\n\nCreate model with MPTT mixin:\n\n.. testcode::\n\n    from sqlalchemy import Column, Integer, Boolean\n    from sqlalchemy.ext.declarative import declarative_base\n\n    from sqlalchemy_mptt.mixins import BaseNestedSets\n\n    Base = declarative_base()\n\n\n    class Tree(Base, BaseNestedSets):\n        __tablename__ = \"tree\"\n\n        id = Column(Integer, primary_key=True)\n        visible = Column(Boolean)  # you custom field\n\n        def __repr__(self):\n            return \"<Node (%s)>\" % self.id\n\n\n\nSession factory wrapper\n-----------------------\n\nFor the automatic tree maintainance triggered after session flush to work\ncorrectly, wrap the Session factory with :mod:`sqlalchemy_mptt.mptt_sessionmaker`\n\n.. testcode::\n\n    from sqlalchemy import create_engine\n    from sqlalchemy.orm import sessionmaker\n    from sqlalchemy_mptt import mptt_sessionmaker\n\n    engine = create_engine('sqlite:///:memory:')\n    Session = mptt_sessionmaker(sessionmaker(bind=engine))\n\nUsing session factory wrapper with flask_sqlalchemy\n---------------------------------------------------\n\nIf you use Flask and SQLAlchemy, you probably use also flask_sqlalchemy\nextension for integration. In that case the Session creation is not directly\naccessible. The following allows you to use the wrapper:\n\n.. testcode::\n\n    from sqlalchemy_mptt import mptt_sessionmaker\n    from flask_sqlalchemy import SQLAlchemy\n\n    # instead of creating db object directly\n    db = SQLAlchemy()\n\n    # subclass the db manager and insert the wrapper at session creation\n    class CustomSQLAlchemy(SQLAlchemy):\n        \"\"\"A custom SQLAlchemy manager, to have control on session creation\"\"\"\n\n        def create_session(self, options):\n            \"\"\"Override the original session factory creation\"\"\"\n            Session = super().create_session(options)\n            # Use wrapper from sqlalchemy_mptt that manage tree tables\n            return mptt_sessionmaker(Session)\n\n    # then\n    db = CustomSQLAlchemy()\n\n\nEvents\n------\n\nThe tree manager automatically registers events. But you can do it manually:\n\n.. testcode::\n\n   from sqlalchemy_mptt import tree_manager\n\n   tree_manager.register_events()  # register events before_insert,\n                                   # before_update and before_delete\n\nOr disable events if it required:\n\n.. testcode::\n\n   from sqlalchemy_mptt import tree_manager\n\n   tree_manager.register_events(remove=True)  # remove events before_insert,\n                                              # before_update and before_delete\n\nData structure\n--------------\n\nFill table with records, for example, as shown in the picture\n\n.. image:: img/2_sqlalchemy_mptt_traversal.svg\n    :width: 500px\n    :alt: SQLAlchemy MPTT (nested sets)\n\nRepresented data of tree like dict\n\n.. testcode::\n\n    tree = (\n        {'id':  '1',                  'parent_id': None},\n\n        {'id':  '2', 'visible': True, 'parent_id':  '1'},\n        {'id':  '3', 'visible': True, 'parent_id':  '2'},\n\n        {'id':  '4', 'visible': True, 'parent_id':  '1'},\n        {'id':  '5', 'visible': True, 'parent_id':  '4'},\n        {'id':  '6', 'visible': True, 'parent_id':  '4'},\n\n        {'id':  '7', 'visible': True, 'parent_id':  '1'},\n        {'id':  '8', 'visible': True, 'parent_id':  '7'},\n        {'id':  '9',                  'parent_id':  '8'},\n        {'id': '10',                  'parent_id':  '7'},\n        {'id': '11',                  'parent_id': '10'},\n    )\n\nInitializing a tree with data\n-----------------------------\n\nWhen you add nodes to the table, the tree manager subsequently updates the\nlevel, left and right attributes in the reset of the table. This is done very\nquickly if the tree already exists in the database, but for initializing the\ntree, it might become a big overhead. In this case, it is recommended to\ndeactivate automatic tree management, fill in the data, reactivate automatic\ntree management and finally call manually a rebuild of the tree once at the end:\n\n.. testcode::\n    :hide:\n\n    # This is some more setup code.\n    from flask import Flask\n\n    class MyModelTree(db.Model, BaseNestedSets):\n        __tablename__ = \"my_model_tree\"\n\n        id = db.Column(db.Integer, primary_key=True)\n        visible = db.Column(db.Boolean)  # you custom field\n\n        def __repr__(self):\n            return \"<Node (%s)>\" % self.id\n\n    app = Flask('test')\n    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'\n    db.init_app(app)\n    app.app_context().push()\n    db.create_all()\n    items = [MyModelTree(**data) for data in tree]\n\n.. testcode::\n\n    from sqlalchemy_mptt import tree_manager\n\n    ...\n\n    tree_manager.register_events(remove=True) # Disable MPTT events\n\n    # Fill tree\n    for item in items:\n        item.left = 0\n        item.right = 0\n        item.tree_id = 1\n        db.session.add(item)\n    db.session.commit()\n\n    ...\n\n    tree_manager.register_events() # enabled MPTT events back\n    MyModelTree.rebuild_tree(db.session, 1) # rebuild lft, rgt value automatically\n\nAfter an initial table with tree you can use mptt features.\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset BUILDDIR=_build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\r\nset I18NSPHINXOPTS=%SPHINXOPTS% .\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\sqlalchemy_mptt.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\sqlalchemy_mptt.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %BUILDDIR%/..\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "docs/sqlalchemy_mptt.rst",
    "content": ":mod:`sqlalchemy_mptt` package\n==============================\n\nEvents\n------\n\nBase events\n~~~~~~~~~~~\n\n.. automodule:: sqlalchemy_mptt.events\n\n    .. autofunction:: mptt_before_insert\n    .. autofunction:: mptt_before_delete\n    .. autofunction:: mptt_before_update\n    .. autoclass:: TreesManager\n\nHidden method\n~~~~~~~~~~~~~\n\n    .. autofunction:: _insert_subtree\n\nMixins\n------\n\n.. automodule:: sqlalchemy_mptt.mixins\n\n    .. autoclass:: BaseNestedSets\n        :members:\n\n        .. automethod:: tree_id\n        .. attribute:: parent_id\n        .. attribute:: parent\n        .. automethod:: left\n        .. automethod:: right\n        .. automethod:: level\n\nTests\n-----\n\n.. automodule:: sqlalchemy_mptt.tests.test_events\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.test_inheritance\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.test_mixins\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nCases tests\n~~~~~~~~~~~\n\n.. automodule:: sqlalchemy_mptt.tests.cases.edit_node\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.cases.get_node\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.cases.get_tree\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.cases.initialize\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.cases.integrity\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. automodule:: sqlalchemy_mptt.tests.cases.move_node\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/tut_flask.rst",
    "content": "Usage with Flask-SQLAlchemy\n===========================\n\nInitialize Flask app and sqlalchemy\n\n.. testsetup::\n\n    __name__ = \"__main__\"\n\n.. testcode::\n\n    from pprint import pprint\n    from flask import Flask\n    from flask_sqlalchemy import SQLAlchemy\n\n    from sqlalchemy_mptt.mixins import BaseNestedSets\n\n    app = Flask(__name__)\n    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'\n    db = SQLAlchemy(app)\n\nMake models.\n\n.. testcode::\n\n    class Category(db.Model, BaseNestedSets):\n        __tablename__ = 'categories'\n        id = db.Column(db.Integer, primary_key=True)\n        name = db.Column(db.String(400), index=True, unique=True)\n        items = db.relationship(\"Product\", backref='item', lazy='dynamic')\n\n        def __repr__(self):\n            return '<Category {}>'.format(self.name)\n\n\n    class Product(db.Model):\n        __tablename__ = 'products'\n        id = db.Column(db.Integer, primary_key=True)\n        category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))\n        name = db.Column(db.String(475), index=True)\n\nRepresent data of tree in table\n-------------------------------\n\nAdd data to table with tree.\n\n.. testcode::\n    :hide:\n\n    # This is some more setup code.\n    app.app_context().push()\n\n.. testcode::\n\n    db.session.add(Category(name=\"root\"))  # root node\n    db.session.add_all(  # first branch of tree\n        [\n            Category(name=\"foo\", parent_id=1),\n            Category(name=\"bar\", parent_id=2),\n            Category(name=\"baz\", parent_id=3),\n        ]\n    )\n    db.session.add_all(  # second branch of tree\n        [\n            Category(name=\"foo1\", parent_id=1),\n            Category(name=\"bar1\", parent_id=5),\n            Category(name=\"baz1\", parent_id=5),\n        ]\n    )\n\n    db.drop_all()\n    db.create_all()\n    db.session.commit()\n\nThe database entries are added:\n\n.. code-block:: text\n\n    \"id\"  \"name\"  \"lft\"  \"rgt\"  \"level\"  \"parent_id\"  \"tree_id\"\n    1     \"root\"  1      14     1        1\n    2     \"foo\"   2      7      2        1            1\n    3     \"bar\"   3      6      3        2            1\n    4     \"baz\"   4      5      4        3            1\n    5     \"foo1\"  8      13     2        1            1\n    6     \"bar1\"  9      10     3        5            1\n    7     \"baz1\"  11     12     3        5            1\n\n\n``Lft`` of root element every time :math:`1`.\n\n:math:`root_{lft} = 1`\n\n``Rgt`` of root element always equal 2 * quantity of tree nodes.\n\n:math:`root_{rgt} = 2 * | P |`\n\n:math:`root_{rgt} = 2 * 7 = 14`\n\nThe tree that displays the records in the database is represented schematically\nbelow:\n\n.. code-block:: text\n\n   level\n     1                  1(root)14\n                            |\n                   ---------------------\n                   |                   |\n     2          2(foo)7             8(foo1)13\n                   |               /         \\\n     3          3(bar)6        9(bar1)10   11(baz1)12\n                   |\n     4          4(baz)5\n\nDrilldown\n---------\n\nDrilldown tree for a given node.\n\nA drilldown tree consists of a node’s ancestors, itself and its immediate\nchildren. For example, a drilldown tree for a ``foo1`` category might look\nsomething like:\n\n.. code-block:: text\n\n   Drilldown for foo1 node\n\n   level\n     1                  1(root)14\n                            |\n                   ---------------------\n                   |         ----------|---------------\n     2          2(foo)7      |      8(foo1)13         |\n                   |         |     /         \\        |\n     3          3(bar)6      | 9(bar1)10   11(baz1)12 |\n                   |         --------------------------\n     4          4(baz)5\n\n.. testcode::\n\n    categories = Category.query.all()\n\n    for item in categories:\n        print(item)\n        pprint(item.drilldown_tree())\n        print()\n\n.. testoutput::\n   :options: +NORMALIZE_WHITESPACE\n\n    <Category root>\n    [{'children': [{'children': [{'children': [{'node': <Category baz>}],\n                                  'node': <Category bar>}],\n                    'node': <Category foo>},\n                   {'children': [{'node': <Category bar1>},\n                                 {'node': <Category baz1>}],\n                    'node': <Category foo1>}],\n      'node': <Category root>}]\n\n    <Category foo>\n    [{'children': [{'children': [{'node': <Category baz>}],\n                    'node': <Category bar>}],\n      'node': <Category foo>}]\n\n    <Category bar>\n    [{'children': [{'node': <Category baz>}], 'node': <Category bar>}]\n\n    <Category baz>\n    [{'node': <Category baz>}]\n\n    <Category foo1>\n    [{'children': [{'node': <Category bar1>}, {'node': <Category baz1>}],\n      'node': <Category foo1>}]\n\n    <Category bar1>\n    [{'node': <Category bar1>}]\n\n    <Category baz1>\n    [{'node': <Category baz1>}]\n\nRepresent it to JSON format:\n\n.. testcode::\n\n   def cat_to_json(item):\n       return {\n           'id': item.id,\n           'name': item.name\n       }\n\n   for item in categories:\n       pprint(item.drilldown_tree(json=True, json_fields=cat_to_json))\n       print()\n\n.. testoutput::\n    :options: +NORMALIZE_WHITESPACE\n\n    [{'children': [{'children': [{'children': [{'id': 4,\n                                                'label': '<Category baz>',\n                                                'name': 'baz'}],\n                                  'id': 3,\n                                  'label': '<Category bar>',\n                                  'name': 'bar'}],\n                    'id': 2,\n                    'label': '<Category foo>',\n                    'name': 'foo'},\n                   {'children': [{'id': 6,\n                                  'label': '<Category bar1>',\n                                  'name': 'bar1'},\n                                 {'id': 7,\n                                  'label': '<Category baz1>',\n                                  'name': 'baz1'}],\n                    'id': 5,\n                    'label': '<Category foo1>',\n                    'name': 'foo1'}],\n      'id': 1,\n      'label': '<Category root>',\n      'name': 'root'}]\n\n    [{'children': [{'children': [{'id': 4,\n                                  'label': '<Category baz>',\n                                  'name': 'baz'}],\n                    'id': 3,\n                    'label': '<Category bar>',\n                    'name': 'bar'}],\n      'id': 2,\n      'label': '<Category foo>',\n      'name': 'foo'}]\n\n    [{'children': [{'id': 4, 'label': '<Category baz>', 'name': 'baz'}],\n      'id': 3,\n      'label': '<Category bar>',\n      'name': 'bar'}]\n\n    [{'id': 4, 'label': '<Category baz>', 'name': 'baz'}]\n\n    [{'children': [{'id': 6, 'label': '<Category bar1>', 'name': 'bar1'},\n                   {'id': 7, 'label': '<Category baz1>', 'name': 'baz1'}],\n      'id': 5,\n      'label': '<Category foo1>',\n      'name': 'foo1'}]\n\n    [{'id': 6, 'label': '<Category bar1>', 'name': 'bar1'}]\n\n    [{'id': 7, 'label': '<Category baz1>', 'name': 'baz1'}]\n\nPath to root\n------------\n\nReturns a list containing the ancestors and the node itself in tree order.\n\n.. code-block:: text\n\n   Path to root of bar node\n\n   level      ---------------------\n     1        |         1(root)14 |\n              |             |     |\n              |    ---------------|-----\n              |    |    -----------    |\n     2        | 2(foo)7 |           8(foo1)13\n              |    |    |          /         \\\n     3        | 3(bar)6 |      9(bar1)10   11(baz1)12\n              -----|-----\n     4          4(baz)5\n\n.. testcode::\n\n   for item in categories:\n       print(item)\n       pprint(item.path_to_root().all())\n       print()\n\n.. testoutput::\n   :options: +NORMALIZE_WHITESPACE\n\n    <Category root>\n    [<Category root>]\n\n    <Category foo>\n    [<Category foo>, <Category root>]\n\n    <Category bar>\n    [<Category bar>, <Category foo>, <Category root>]\n\n    <Category baz>\n    [<Category baz>, <Category bar>, <Category foo>, <Category root>]\n\n    <Category foo1>\n    [<Category foo1>, <Category root>]\n\n    <Category bar1>\n    [<Category bar1>, <Category foo1>, <Category root>]\n\n    <Category baz1>\n    [<Category baz1>, <Category foo1>, <Category root>]\n\nFull code\n---------\n\n.. testcode::\n\n    from pprint import pprint\n    from flask import Flask\n    from flask_sqlalchemy import SQLAlchemy\n\n    from sqlalchemy_mptt.mixins import BaseNestedSets\n\n    app = Flask(__name__)\n    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'\n    db = SQLAlchemy(app)\n\n\n    class Category(db.Model, BaseNestedSets):\n        __tablename__ = 'categories'\n        id = db.Column(db.Integer, primary_key=True)\n        name = db.Column(db.String(400), index=True, unique=True)\n        items = db.relationship(\"Product\", backref='item', lazy='dynamic')\n\n        def __repr__(self):\n            return '<Category {}>'.format(self.name)\n\n\n    class Product(db.Model):\n        __tablename__ = 'products'\n        id = db.Column(db.Integer, primary_key=True)\n        category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))\n        name = db.Column(db.String(475), index=True)\n\n    app.app_context().push()\n    db.session.add(Category(name=\"root\"))  # root node\n    db.session.add_all(  # first branch of tree\n        [\n            Category(name=\"foo\", parent_id=1),\n            Category(name=\"bar\", parent_id=2),\n            Category(name=\"baz\", parent_id=3),\n        ]\n    )\n    db.session.add_all(  # second branch of tree\n        [\n            Category(name=\"foo1\", parent_id=1),\n            Category(name=\"bar1\", parent_id=5),\n            Category(name=\"baz1\", parent_id=5),\n        ]\n    )\n\n    '''\n    \"id\"  \"name\"  \"lft\"  \"rgt\"  \"level\"  \"parent_id\"  \"tree_id\"\n    1     \"root\"  1      14     1        1\n    2     \"foo\"   2      7      2        1            1\n    3     \"bar\"   3      6      3        2            1\n    4     \"baz\"   4      5      4        3            1\n    5     \"foo1\"  8      13     2        1            1\n    6     \"bar1\"  9      10     3        5            1\n    7     \"baz1\"  11     12     3        5            1\n\n    root lft everytime = 1\n    root rgt = qty_nodes * 2\n\n    level\n      1                  1(root)14\n                             |\n                    ---------------------\n                    |                   |\n      2          2(foo)7             8(foo1)13\n                    |               /         \\\n      3          3(bar)6        9(bar1)10   11(baz1)12\n                    |\n      4          4(baz)5\n    '''\n\n    db.drop_all()\n    db.create_all()\n    db.session.commit()\n\n    categories = Category.query.all()\n\n    for item in categories:\n        print(item)\n        pprint(item.drilldown_tree())\n        print()\n\n    '''\n    <Category root>\n    [{'children': [{'children': [{'children': [{'node': <Category baz>}],\n                                  'node': <Category bar>}],\n                    'node': <Category foo>},\n                   {'children': [{'node': <Category bar1>},\n                                 {'node': <Category baz1>}],\n                    'node': <Category foo1>}],\n      'node': <Category root>}]\n\n    <Category foo>\n    [{'children': [{'children': [{'node': <Category baz>}],\n                    'node': <Category bar>}],\n      'node': <Category foo>}]\n\n    <Category bar>\n    [{'children': [{'node': <Category baz>}], 'node': <Category bar>}]\n\n    <Category baz>\n    [{'node': <Category baz>}]\n\n    <Category foo1>\n    [{'children': [{'node': <Category bar1>}, {'node': <Category baz1>}],\n      'node': <Category foo1>}]\n\n    <Category bar1>\n    [{'node': <Category bar1>}]\n\n    <Category baz1>\n    [{'node': <Category baz1>}]\n    '''\n\n    for item in categories:\n        print(item)\n        pprint(item.path_to_root().all())\n        print()\n\n    '''\n    <Category root>\n    [<Category root>]\n\n    <Category foo>\n    [<Category foo>, <Category root>]\n\n    <Category bar>\n    [<Category bar>, <Category foo>, <Category root>]\n\n    <Category baz>\n    [<Category baz>, <Category bar>, <Category foo>, <Category root>]\n\n    <Category foo1>\n    [<Category foo1>, <Category root>]\n\n    <Category bar1>\n    [<Category bar1>, <Category foo1>, <Category root>]\n\n    <Category baz1>\n    [<Category baz1>, <Category foo1>, <Category root>]\n    '''\n\n.. testoutput::\n    :options: +NORMALIZE_WHITESPACE\n    :hide:\n\n     <Category root>\n     [{'children': [{'children': [{'children': [{'node': <Category baz>}],\n                                     'node': <Category bar>}],\n                      'node': <Category foo>},\n                     {'children': [{'node': <Category bar1>},\n                                    {'node': <Category baz1>}],\n                      'node': <Category foo1>}],\n        'node': <Category root>}]\n\n     <Category foo>\n     [{'children': [{'children': [{'node': <Category baz>}],\n                      'node': <Category bar>}],\n        'node': <Category foo>}]\n\n     <Category bar>\n     [{'children': [{'node': <Category baz>}], 'node': <Category bar>}]\n\n     <Category baz>\n     [{'node': <Category baz>}]\n\n     <Category foo1>\n     [{'children': [{'node': <Category bar1>}, {'node': <Category baz1>}],\n        'node': <Category foo1>}]\n\n     <Category bar1>\n     [{'node': <Category bar1>}]\n\n     <Category baz1>\n     [{'node': <Category baz1>}]\n\n     <Category root>\n     [<Category root>]\n\n     <Category foo>\n     [<Category foo>, <Category root>]\n\n     <Category bar>\n     [<Category bar>, <Category foo>, <Category root>]\n\n     <Category baz>\n     [<Category baz>, <Category bar>, <Category foo>, <Category root>]\n\n     <Category foo1>\n     [<Category foo1>, <Category root>]\n\n     <Category bar1>\n     [<Category bar1>, <Category foo1>, <Category root>]\n\n     <Category baz1>\n     [<Category baz1>, <Category foo1>, <Category root>]\n"
  },
  {
    "path": "noxfile.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2025 Fayaz Yusuf Khan <fayaz.yusuf.khan@gmail.com>\n#\n# Distributed under terms of the MIT license.\n#\n# /// script\n# requires-python = \">=3.9\"\n# dependencies = [\n#     \"nox\",\n#     \"nox-uv\",\n#     \"requests\",\n# ]\n# ///\n\"\"\" Entry point script for testing, linting, and development of the package.\n\n    This project uses Nox to create isolated environments.\n\n    Requirements:\n    - uv\n\n    Usage:\n\n      Run all tests and linting:\n        $ uv run noxfile.py\n      Run tests for a specific SQLAlchemy version:\n        $ uv run noxfile.py -t sqla12\n      Run tests for a specific Python version:\n        $ uv run noxfile.py -s test -p 3.X\n\n      Set up a development environment with the default Python version (3.8):\n        $ uv run noxfile.py -s dev\n      Set up a development environment with a specific Python version:\n        $ uv run noxfile.py -s dev -P 3.X\n\"\"\"\nimport sys\nfrom itertools import groupby\n\nimport nox\nimport requests\nfrom packaging.requirements import Requirement\nfrom packaging.version import Version\n\n# Python versions supported and tested against: 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.14\nPYTHON_MINOR_VERSION_MIN = 8\nPYTHON_MINOR_VERSION_MAX = 14\n\nnox.options.default_venv_backend = \"uv\"\n\n\n@nox.session()\ndef lint(session):\n    \"\"\"Run flake8.\"\"\"\n    session.install(\"flake8\")\n    # stop the linter if there are Python syntax errors or undefined names\n    session.run(\"flake8\", \"--select=E9,F63,F7,F82\", \"--show-source\")\n    # exit-zero treats all errors as warnings\n    session.run(\"flake8\", \"--exit-zero\", \"--max-complexity=10\")\n\n\ndef parametrize_test_versions():\n    \"\"\"Parametrize the session with all supported Python & SQLAlchemy versions.\"\"\"\n    print(\"Requesting all SQLAlchemy versions from PyPI...\", file=sys.stderr)\n    response = requests.get(\"https://pypi.org/pypi/SQLAlchemy/json\")\n    print(\"Preparing test version candidates...\", file=sys.stderr)\n    response.raise_for_status()\n    data = response.json()\n    all_major_and_minor_sqlalchemy_versions = [\n        Version(f\"{major}.{minor}\")\n        for (major, minor), _ in groupby(\n            sorted(Version(version) for version in data[\"releases\"].keys()),\n            key=lambda v: (v.major, v.minor)\n        )\n    ]\n\n    with open(\"requirements.txt\", \"r\") as f:\n        requirement = Requirement(f.read().strip())\n    filtered_sqlalchemy_versions = [\n        version for version in all_major_and_minor_sqlalchemy_versions\n        if version in requirement.specifier\n    ]\n\n    return [\n        nox.param(\n            f\"3.{python_minor}\", str(sqlalchemy_version),\n            tags=[f\"sqla{sqlalchemy_version.major}{sqlalchemy_version.minor}\"]\n        )\n        for python_minor in range(PYTHON_MINOR_VERSION_MIN, PYTHON_MINOR_VERSION_MAX + 1)\n        for sqlalchemy_version in filtered_sqlalchemy_versions\n        # SQLA 1.1 or below doesn't seem to support Python 3.10+\n        # SQLA 1.2 doesn't seem to support Python 3.14+\n        if ((sqlalchemy_version >= Version(\"1.2\") or python_minor <= 9)\n            and (sqlalchemy_version >= Version(\"1.3\") or python_minor <= 13))]\n\n\nPARAMETRIZED_TEST_VERSIONS = parametrize_test_versions()\n\n\ndef install_dependencies(session, session_name, sqlalchemy_version):\n    \"\"\"Install dependencies for the given session.\"\"\"\n    session.install(\n        \"-r\", f\"requirements-{session_name}.txt\",\n        f\"sqlalchemy~={sqlalchemy_version}.0\",\n        \"-e\", \".\"\n    )\n\n\n@nox.session()\n@nox.parametrize(\"python,sqlalchemy\", PARAMETRIZED_TEST_VERSIONS)\ndef test(session, sqlalchemy):\n    \"\"\"Run tests with pytest.\n\n    You can pass arguments to pytest using the `--` option.\n\n        $ uv run noxfile.py -s test -- sqlalchemy_mptt/tests/test_events.py\n\n    If no arguments are provided, it defaults to running all tests in the package.\n\n    For running tests for a specific SQLAlchemy version, use the tags option:\n\n        $ uv run noxfile.py -s test -t sqla12\n\n    For fine-grained control over running the tests, refer the nox documentation: https://nox.thea.codes/en/stable/usage.html\n    \"\"\"\n    install_dependencies(session, \"test\", sqlalchemy)\n    pytest_args = session.posargs or [\"--pyargs\", \"sqlalchemy_mptt\"]\n    session.run(\"pytest\", *pytest_args, env={\"SQLALCHEMY_WARN_20\": \"1\"})\n\n\n@nox.session()\n@nox.parametrize(\"python,sqlalchemy\", PARAMETRIZED_TEST_VERSIONS[-1:])\ndef doctest(session, sqlalchemy):\n    \"\"\"Run doctests in the documentation.\"\"\"\n    install_dependencies(session, \"doctest\", sqlalchemy)\n    session.run(\"sphinx-build\", \"-b\", \"doctest\", \"docs\", \"docs/_build\")\n\n\n@nox.session(default=False)\ndef dev(session):\n    \"\"\"Set up a development environment.\n    This will create a virtual environment and install the package in editable mode in .venv.\n\n    To use a specific Python version, use the -P option:\n    $ uv run noxfile.py -s dev -P 3.X\n    \"\"\"\n    session.run(\"uv\", \"venv\", \"--python\", session.python or f\"3.{PYTHON_MINOR_VERSION_MIN}\", \"--seed\")\n    session.run(\".venv/bin/pip\", \"install\", \"-r\", \"requirements-test.txt\", external=True)\n    session.run(\".venv/bin/pip\", \"install\", \"-e\", \".\", external=True)\n\n\n@nox.session(default=False)\ndef build(session):\n    \"\"\"Build the package.\"\"\"\n    session.install(\"build\")\n    session.run(\"python\", \"-m\", \"build\")\n\n\nif __name__ == \"__main__\":\n    nox.main()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.black]\nline-length = 79\ninclude = '\\.pyi?$'\nexclude = '''\n/(\n    \\.git\n  | \\.hg\n  | \\.mypy_cache\n  | \\.tox\n  | \\.venv\n  | _build\n  | buck-out\n  | build\n  | dist\n)/\n'''\n\n[tool.pytest.ini_options]\nfilterwarnings = [\n  \"error:::sqlalchemy_mptt\"\n]\naddopts = \"--cov sqlalchemy_mptt --cov-report term-missing:skip-covered\"\n"
  },
  {
    "path": "requirements-doctest.txt",
    "content": "flask-sqlalchemy\nsphinx\n"
  },
  {
    "path": "requirements-test.txt",
    "content": "hypothesis\npytest\npytest-cov\n"
  },
  {
    "path": "requirements.txt",
    "content": "SQLAlchemy>=1.0.0,<3.0\n"
  },
  {
    "path": "setup.py",
    "content": "import os\n\nfrom setuptools import setup\n\nthis = os.path.dirname(os.path.realpath(__file__))\n\n\ndef read(name):\n    with open(os.path.join(this, name)) as f:\n        return f.read()\n\n\nsetup(\n    name=\"sqlalchemy_mptt\",\n    version=\"0.6.0\",\n    url=\"http://github.com/uralbash/sqlalchemy_mptt/\",\n    author=\"Svintsov Dmitry\",\n    author_email=\"sacrud@uralbash.ru\",\n    maintainer=\"Fayaz Khan\",\n    maintainer_email=\"fayaz.yusuf.khan@gmail.com\",\n    packages=[\"sqlalchemy_mptt\"],\n    include_package_data=True,\n    zip_safe=False,\n    license=\"MIT\",\n    description=(\n        \"SQLAlchemy mixins for implementing tree-like models\"\n        \" using Modified Pre-order Tree Traversal (MPTT) / Nested Sets\"),\n    long_description=read(\"README.rst\") + \"\\n\" + read(\"CHANGES.rst\"),\n    install_requires=read(\"requirements.txt\"),\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Environment :: Web Environment\",\n        \"Intended Audience :: Developers\",\n        \"Natural Language :: English\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\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        \"Framework :: Pyramid\",\n        \"Framework :: Flask\",\n        \"Topic :: Internet\",\n        \"Topic :: Database\",\n        \"License :: OSI Approved :: MIT License\",\n    ],\n)\n"
  },
  {
    "path": "sqlalchemy_mptt/__init__.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright (c) 2014 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\nfrom .events import TreesManager\nfrom .mixins import BaseNestedSets\n\n__mixins__ = [BaseNestedSets]\n__all__ = ['BaseNestedSets', 'mptt_sessionmaker']\n\ntree_manager = TreesManager(BaseNestedSets)\ntree_manager.register_events()\nmptt_sessionmaker = tree_manager.register_factory\n"
  },
  {
    "path": "sqlalchemy_mptt/events.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2014 uralbash <root@uralbash.ru>\n# Copyright (c) 2025 Fayaz Yusuf Khan <fayaz.yusuf.khan@gmail.com>\n#\n# Distributed under terms of the MIT license.\n\n\"\"\"\nSQLAlchemy events extension\n\"\"\"\n# standard library\nimport weakref\n\n# SQLAlchemy\nfrom sqlalchemy import and_, event, inspection\nfrom sqlalchemy.orm import object_session\nfrom sqlalchemy.sql import func\nfrom sqlalchemy.orm.base import NO_VALUE\n\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\n\n\ndef _insert_subtree(\n        table,\n        connection,\n        node_size,\n        node_pos_left,\n        node_pos_right,\n        parent_pos_left,\n        parent_pos_right,\n        subtree,\n        parent_tree_id,\n        parent_level,\n        node_level,\n        left_sibling,\n        table_pk\n):\n    # step 1: rebuild inserted subtree\n    delta_lft = left_sibling['lft'] + 1\n    if not left_sibling['is_parent']:\n        delta_lft = left_sibling['rgt'] + 1\n    delta_rgt = delta_lft + node_size - 1\n\n    connection.execute(\n        table.update()\n        .where(table_pk.in_(subtree))\n        .values(\n            lft=table.c.lft - node_pos_left + delta_lft,\n            rgt=table.c.rgt - node_pos_right + delta_rgt,\n            level=table.c.level - node_level + parent_level + 1,\n            tree_id=parent_tree_id\n        )\n    )\n\n    # step 2: update key of right side\n    connection.execute(\n        table.update()\n        .where(table.c.rgt > delta_lft - 1)\n        .where(table_pk.notin_(subtree))\n        .where(table.c.tree_id == parent_tree_id)\n        .values(\n            rgt=table.c.rgt + node_size,\n            lft=compat_layer.case(\n                (table.c.lft > left_sibling['lft'], table.c.lft + node_size),\n                else_=table.c.lft\n            )\n        )\n    )\n\n\ndef _get_tree_table(mapper):\n    for table in mapper.tables:\n        if all(key in table.c for key in ['level', 'lft', 'rgt', 'parent_id']):\n            return table\n\n\ndef mptt_before_insert(mapper, connection, instance):\n    \"\"\" Based on example\n    https://bitbucket.org/zzzeek/sqlalchemy/src/73095b353124/examples/nested_sets/nested_sets.py?at=master\n    \"\"\"\n    table = _get_tree_table(mapper)\n    db_pk = instance.get_pk_column()\n    table_pk = getattr(table.c, db_pk.name)\n\n    if instance.parent_id is None:\n        instance.left = 1\n        instance.right = 2\n        instance.level = instance.get_default_level()\n        tree_id = connection.scalar(\n            compat_layer.select(\n                func.max(table.c.tree_id) + 1\n            )\n        ) or 1\n        instance.tree_id = tree_id\n    else:\n        (parent_pos_left,\n         parent_pos_right,\n         parent_tree_id,\n         parent_level) = connection.execute(\n            compat_layer.select(\n                table.c.lft,\n                table.c.rgt,\n                table.c.tree_id,\n                table.c.level\n            ).where(\n                table_pk == instance.parent_id\n            )\n        ).fetchone()\n\n        # Update key of right side\n        connection.execute(\n            table.update()\n            .where(table.c.rgt >= parent_pos_right)\n            .where(table.c.tree_id == parent_tree_id)\n            .values(\n                lft=compat_layer.case(\n                    (table.c.lft > parent_pos_right, table.c.lft + 2),\n                    else_=table.c.lft\n                ),\n                rgt=compat_layer.case(\n                    (table.c.rgt >= parent_pos_right, table.c.rgt + 2),\n                    else_=table.c.rgt\n                )\n            )\n        )\n\n        instance.level = parent_level + 1\n        instance.tree_id = parent_tree_id\n        instance.left = parent_pos_right\n        instance.right = parent_pos_right + 1\n\n\ndef mptt_before_delete(mapper, connection, instance, delete=True):\n    table = _get_tree_table(mapper)\n    tree_id = instance.tree_id\n    pk = getattr(instance, instance.get_pk_name())\n    db_pk = instance.get_pk_column()\n    table_pk = getattr(table.c, db_pk.name)\n    lft, rgt = connection.execute(\n        compat_layer.select(\n            table.c.lft,\n            table.c.rgt\n        ).where(\n            table_pk == pk\n        )\n    ).fetchone()\n    delta = rgt - lft + 1\n\n    if delete:\n        mapper.base_mapper.confirm_deleted_rows = False\n        connection.execute(\n            table.delete().where(\n                table_pk == pk\n            )\n        )\n\n    if instance.parent_id is not None or not delete:\n        \"\"\" Update key of current tree\n\n            UPDATE tree\n            SET left_id = CASE\n                    WHEN left_id > $leftId THEN left_id - $delta\n                    ELSE left_id\n                END,\n                right_id = CASE\n                    WHEN right_id >= $rightId THEN right_id - $delta\n                    ELSE right_id\n                END\n        \"\"\"\n        connection.execute(\n            table.update()\n            .where(table.c.rgt > rgt)\n            .where(table.c.tree_id == tree_id)\n            .values(\n                lft=compat_layer.case(\n                    (table.c.lft > lft, table.c.lft - delta),\n                    else_=table.c.lft\n                ),\n                rgt=compat_layer.case(\n                    (table.c.rgt >= rgt, table.c.rgt - delta),\n                    else_=table.c.rgt\n                )\n            )\n        )\n\n\ndef mptt_before_update(mapper, connection, instance):\n    \"\"\" Based on this example:\n        http://stackoverflow.com/questions/889527/move-node-in-nested-set\n    \"\"\"\n    node_id = getattr(instance, instance.get_pk_name())\n    table = _get_tree_table(mapper)\n    db_pk = instance.get_pk_column()\n    default_level = instance.get_default_level()\n    table_pk = getattr(table.c, db_pk.name)\n    mptt_move_inside = None\n    left_sibling = None\n    left_sibling_tree_id = None\n\n    if hasattr(instance, 'mptt_move_inside'):\n        mptt_move_inside = instance.mptt_move_inside\n\n    if hasattr(instance, 'mptt_move_before'):\n        (\n            right_sibling_left,\n            right_sibling_right,\n            right_sibling_parent,\n            right_sibling_level,\n            right_sibling_tree_id\n        ) = connection.execute(\n            compat_layer.select(\n                table.c.lft,\n                table.c.rgt,\n                table.c.parent_id,\n                table.c.level,\n                table.c.tree_id\n            ).where(\n                table_pk == instance.mptt_move_before\n            )\n        ).fetchone()\n        current_lvl_nodes = connection.execute(\n            compat_layer.select(\n                table.c.lft,\n                table.c.rgt,\n                table.c.parent_id,\n                table.c.tree_id\n            ).where(\n                and_(\n                    table.c.level == right_sibling_level,\n                    table.c.tree_id == right_sibling_tree_id,\n                    table.c.lft < right_sibling_left\n                )\n            )\n        ).fetchall()\n        if current_lvl_nodes:\n            (\n                left_sibling_left,\n                left_sibling_right,\n                left_sibling_parent,\n                left_sibling_tree_id\n            ) = current_lvl_nodes[-1]\n            instance.parent_id = left_sibling_parent\n            left_sibling = {\n                'lft': left_sibling_left,\n                'rgt': left_sibling_right,\n                'is_parent': False\n            }\n        # if move_before to top level\n        elif not right_sibling_parent:\n            left_sibling_tree_id = right_sibling_tree_id - 1\n\n    # if placed after a particular node\n    if hasattr(instance, 'mptt_move_after'):\n        (\n            left_sibling_left,\n            left_sibling_right,\n            left_sibling_parent,\n            left_sibling_tree_id\n        ) = connection.execute(\n            compat_layer.select(\n                table.c.lft,\n                table.c.rgt,\n                table.c.parent_id,\n                table.c.tree_id\n            ).where(\n                table_pk == instance.mptt_move_after\n            )\n        ).fetchone()\n        instance.parent_id = left_sibling_parent\n        left_sibling = {\n            'lft': left_sibling_left,\n            'rgt': left_sibling_right,\n            'is_parent': False\n        }\n\n    \"\"\" Get subtree from node\n\n        SELECT id, name, level FROM my_tree\n        WHERE left_key >= $left_key AND right_key <= $right_key\n        ORDER BY left_key\n    \"\"\"\n    subtree = connection.execute(\n        compat_layer.select(table_pk)\n        .where(\n            and_(\n                table.c.lft >= instance.left,\n                table.c.rgt <= instance.right,\n                table.c.tree_id == instance.tree_id\n            )\n        ).order_by(\n            table.c.lft\n        )\n    ).fetchall()\n    subtree = [x[0] for x in subtree]\n\n    \"\"\" step 0: Initialize parameters.\n\n        Put there left and right position of moving node\n    \"\"\"\n    (\n        node_pos_left,\n        node_pos_right,\n        node_tree_id,\n        node_parent_id,\n        node_level\n    ) = connection.execute(\n        compat_layer.select(\n            table.c.lft,\n            table.c.rgt,\n            table.c.tree_id,\n            table.c.parent_id,\n            table.c.level\n        ).where(\n            table_pk == node_id\n        )\n    ).fetchone()\n\n    # if instance just update w/o move\n    # XXX why this str() around parent_id comparison?\n    if not left_sibling \\\n            and str(node_parent_id) == str(instance.parent_id) \\\n            and not mptt_move_inside:\n        if left_sibling_tree_id is None:\n            return\n\n    # fix tree shorting\n    if instance.parent_id is not None:\n        (\n            parent_id,\n            parent_pos_right,\n            parent_pos_left,\n            parent_tree_id,\n            parent_level\n        ) = connection.execute(\n            compat_layer.select(\n                table_pk,\n                table.c.rgt,\n                table.c.lft,\n                table.c.tree_id,\n                table.c.level\n            ).where(\n                table_pk == instance.parent_id\n            )\n        ).fetchone()\n        if node_parent_id is None and node_tree_id == parent_tree_id:\n            instance.parent_id = None\n            return\n\n    # delete from old tree\n    mptt_before_delete(mapper, connection, instance, False)\n\n    if instance.parent_id is not None:\n        \"\"\" Put there right position of new parent node (there moving node\n            should be moved)\n        \"\"\"\n        (\n            parent_id,\n            parent_pos_right,\n            parent_pos_left,\n            parent_tree_id,\n            parent_level\n        ) = connection.execute(\n            compat_layer.select(\n                table_pk,\n                table.c.rgt,\n                table.c.lft,\n                table.c.tree_id,\n                table.c.level\n            ).where(\n                table_pk == instance.parent_id\n            )\n        ).fetchone()\n        # 'size' of moving node (including all it's sub nodes)\n        node_size = node_pos_right - node_pos_left + 1\n\n        # left sibling node\n        if not left_sibling:\n            left_sibling = {\n                'lft': parent_pos_left,\n                'rgt': parent_pos_right,\n                'is_parent': True\n            }\n\n        # insert subtree in exist tree\n        instance.tree_id = parent_tree_id\n        _insert_subtree(\n            table,\n            connection,\n            node_size,\n            node_pos_left,\n            node_pos_right,\n            parent_pos_left,\n            parent_pos_right,\n            subtree,\n            parent_tree_id,\n            parent_level,\n            node_level,\n            left_sibling,\n            table_pk\n        )\n    else:\n        # if insert after\n        if left_sibling_tree_id or left_sibling_tree_id == 0:\n            tree_id = left_sibling_tree_id + 1\n            connection.execute(\n                table.update()\n                .where(table.c.tree_id > left_sibling_tree_id)\n                .values(\n                    tree_id=table.c.tree_id + 1\n                )\n            )\n        # if just insert\n        else:\n            tree_id = connection.scalar(\n                compat_layer.select(\n                    func.max(table.c.tree_id) + 1\n                )\n            )\n\n        connection.execute(\n            table.update()\n            .where(table_pk.in_(subtree))\n            .values(\n                lft=table.c.lft - node_pos_left + 1,\n                rgt=table.c.rgt - node_pos_left + 1,\n                level=table.c.level - node_level + default_level,\n                tree_id=tree_id\n            )\n        )\n\n\nclass _WeakDefaultDict(weakref.WeakKeyDictionary):\n    \"\"\"A weak reference dictionary that returns a new `WeakSet` as a default\n    value for missing keys.\"\"\"\n\n    def __getitem__(self, key):\n        try:\n            return super(_WeakDefaultDict, self).__getitem__(key)\n        except KeyError:\n            self[key] = value = weakref.WeakSet()\n            return value\n\n\nclass TreesManager(object):\n    \"\"\"\n    Manages events dispatching for all subclasses of a given class.\n    \"\"\"\n    def __init__(self, base_class):\n        self.base_class = base_class\n        self.classes = set()\n        self.instances = _WeakDefaultDict()\n\n    def register_events(self, remove=False):\n        for e, h in (\n            ('before_insert', self.before_insert),\n            ('before_update', self.before_update),\n            ('before_delete', self.before_delete),\n        ):\n            is_event_exist = event.contains(self.base_class, e, h)\n            if remove and is_event_exist:\n                event.remove(self.base_class, e, h)\n            elif not is_event_exist:\n                event.listen(self.base_class, e, h, propagate=True)\n        return self\n\n    def register_factory(self, sessionmaker):\n        \"\"\"\n        Registers this TreesManager instance to respond on\n        `after_flush_postexec` events on the given session or session factory.\n        This method returns the original argument, so that it can be used by\n        wrapping an already existing instance:\n\n        .. code-block:: python\n            :linenos:\n\n            from sqlalchemy import create_engine\n            from sqlalchemy.orm import sessionmaker, mapper\n            from sqlalchemy_mptt.mixins import BaseNestedSets\n\n            engine = create_engine('...')\n\n            trees_manager = TreesManager(BaseNestedSets)\n            trees_manager.register_mapper(mapper)\n\n            Session = tree_manager.register_factory(\n                sessionmaker(bind=engine)\n            )\n\n        A reference to this method, bound to a default instance of this class\n        and already registered to a mapper, is importable directly from\n        `sqlalchemy_mptt`:\n\n        .. code-block:: python\n            :linenos:\n\n            from sqlalchemy import create_engine\n            from sqlalchemy.orm import sessionmaker\n            from sqlalchemy_mptt import mptt_sessionmaker\n\n            engine = create_engine('...')\n            Session = mptt_sessionmaker(sessionmaker(bind=engine))\n        \"\"\"\n        event.listen(sessionmaker, 'after_flush_postexec',\n                     self.after_flush_postexec)\n        return sessionmaker\n\n    def before_insert(self, mapper, connection, instance):\n        session = object_session(instance)\n        self.instances[session].add(instance)\n        mptt_before_insert(mapper, connection, instance)\n\n    def before_update(self, mapper, connection, instance):\n        session = object_session(instance)\n        self.instances[session].add(instance)\n        mptt_before_update(mapper, connection, instance)\n\n    def before_delete(self, mapper, connection, instance):\n        session = object_session(instance)\n        self.instances[session].discard(instance)\n        mptt_before_delete(mapper, connection, instance)\n\n    def after_flush_postexec(self, session, context):\n        \"\"\"\n        Event listener to recursively expire `left` and `right` attributes the\n        parents of all modified instances part of this flush.\n        \"\"\"\n        instances = self.instances[session]\n        while True:\n            try:\n                instance = instances.pop()\n            except KeyError:\n                break\n            if instance not in session:\n                continue\n            parent = self.get_parent_value(instance)\n\n            while parent != NO_VALUE and parent is not None:\n                instances.discard(parent)\n                session.expire(parent, ['left', 'right', 'tree_id', 'level'])\n                parent = self.get_parent_value(parent)\n            else:\n                session.expire(instance, ['left', 'right', 'tree_id', 'level'])\n                self.expire_session_for_children(session, instance)\n\n    @staticmethod\n    def get_parent_value(instance):\n        return inspection.inspect(instance).attrs.parent.loaded_value\n\n    @staticmethod\n    def expire_session_for_children(session, instance):\n        children = instance.children\n\n        def expire_recursively(node):\n            children = node.children\n            for item in children:\n                session.expire(item, ['left', 'right', 'tree_id', 'level'])\n                expire_recursively(item)\n\n        if children != NO_VALUE and children is not None:\n            for item in children:\n                session.expire(item, ['left', 'right', 'tree_id', 'level'])\n                expire_recursively(item)\n"
  },
  {
    "path": "sqlalchemy_mptt/mixins.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2014 uralbash <root@uralbash.ru>\n# Copyright © 2016 Jiri Kuncar <jiri.kuncar@gmail.com>\n#\n# Distributed under terms of the MIT license.\n\n\"\"\"\nSQLAlchemy nested sets mixin\n\n.. testsetup::\n\n    engine = create_engine('sqlite:///:memory:')\n    session = Session(bind=engine)\n\n\"\"\"\n# SQLAlchemy\nfrom sqlalchemy import Column, Integer, ForeignKey, asc, desc\nfrom sqlalchemy.orm import backref, relationship, object_session\nfrom sqlalchemy.ext.hybrid import hybrid_method\nfrom sqlalchemy.orm.session import Session\nfrom sqlalchemy.ext.declarative import declared_attr\n\n# local\nfrom .events import _get_tree_table\n\n\nclass BaseNestedSets(object):\n    \"\"\" Base mixin for MPTT model.\n\n    Example:\n\n    .. testcode::\n\n        from sqlalchemy import Boolean, Column, create_engine, Integer\n        from sqlalchemy.ext.declarative import declarative_base\n        from sqlalchemy.orm import sessionmaker\n\n        from sqlalchemy_mptt.mixins import BaseNestedSets\n\n        Base = declarative_base()\n\n\n        class Tree(Base, BaseNestedSets):\n            __tablename__ = \"tree\"\n\n            id = Column(Integer, primary_key=True)\n            visible = Column(Boolean)\n\n            def __repr__(self):\n                return \"<Node (%s)>\" % self.id\n\n    .. testcode::\n        :hide:\n\n        # This is some more setup code.\n        Base.metadata.create_all(engine)\n        node = Tree()\n        session.add(node)\n        session.flush()\n        node7 = Tree(parent=node)\n        session.add(node7)\n        session.flush()\n        node8 = Tree(parent=node7)\n        session.add(node8)\n        session.flush()\n        node10 = Tree(parent=node7)\n        session.add(node10)\n        session.flush()\n        node11 = Tree(parent=node10)\n        session.add(node11)\n    \"\"\"\n\n    @classmethod\n    def __declare_first__(cls):\n        cls.__mapper__.batch = False\n\n    @classmethod\n    def get_default_level(cls):\n        \"\"\"\n        Compatibility with Django MPTT: level value for root node.\n        See https://github.com/uralbash/sqlalchemy_mptt/issues/56\n        \"\"\"\n        return getattr(cls, \"sqlalchemy_mptt_default_level\", 1)\n\n    @classmethod\n    def get_pk_name(cls):\n        return getattr(cls, \"sqlalchemy_mptt_pk_name\", \"id\")\n\n    @classmethod\n    def get_pk_column(cls):\n        return getattr(cls, cls.get_pk_name())\n\n    def get_pk_value(self):\n        return getattr(self, self.get_pk_name())\n\n    @declared_attr\n    def tree_id(cls):\n        return Column(\"tree_id\", Integer)\n\n    @declared_attr\n    def parent_id(cls):\n        pk = cls.get_pk_column()\n        if not pk.name:\n            pk.name = cls.get_pk_name()\n\n        return Column(\n            \"parent_id\",\n            pk.type,\n            ForeignKey(\n                \"{}.{}\".format(cls.__tablename__, pk.name), ondelete=\"CASCADE\"\n            ),\n        )\n\n    @declared_attr\n    def parent(self):\n        return relationship(\n            self,\n            order_by=lambda: self.left,\n            foreign_keys=[self.parent_id],\n            remote_side=\"{}.{}\".format(self.__name__, self.get_pk_name()),\n            backref=backref(\n                \"children\",\n                cascade=\"all,delete\",\n                order_by=lambda: (self.tree_id, self.left),\n            ),\n        )\n\n    @declared_attr\n    def left(cls):\n        return Column(\"lft\", Integer, nullable=False, index=True)\n\n    @declared_attr\n    def right(cls):\n        return Column(\"rgt\", Integer, nullable=False, index=True)\n\n    @declared_attr\n    def level(cls):\n        return Column(\"level\", Integer, nullable=False, default=0, index=True)\n\n    @hybrid_method\n    def is_ancestor_of(self, other, inclusive=False):\n        \"\"\" class or instance level method which returns True if self is\n        ancestor (closer to root) of other else False. Optional flag\n        `inclusive` on whether or not to treat self as ancestor of self.\n\n        For example see:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.integrity.test_hierarchy_structure`\n        \"\"\"\n        if inclusive:\n            return (\n                (self.tree_id == other.tree_id)\n                & (self.left <= other.left)\n                & (other.right <= self.right)\n            )\n        return (\n            (self.tree_id == other.tree_id)\n            & (self.left < other.left)\n            & (other.right < self.right)\n        )\n\n    @hybrid_method\n    def is_descendant_of(self, other, inclusive=False):\n        \"\"\" class or instance level method which returns True if self is\n        descendant (farther from root) of other else False.  Optional flag\n        `inclusive` on whether or not to treat self as descendant of self.\n\n        For example see:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.integrity.test_hierarchy_structure`\n        \"\"\"\n        return other.is_ancestor_of(self, inclusive)\n\n    def move_inside(self, parent_id):\n        \"\"\" Moving one node of tree inside another\n\n        For example see:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_inside_function`\n        * :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_inside_to_the_same_parent_function`\n        \"\"\"  # noqa\n        session = Session.object_session(self)\n        self.parent_id = parent_id\n        self.mptt_move_inside = parent_id\n        session.add(self)\n\n    def move_after(self, node_id):\n        \"\"\" Moving one node of tree after another\n\n        For example see :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_after_function`\n        \"\"\"  # noqa\n        session = Session.object_session(self)\n        self.parent_id = self.parent_id\n        self.mptt_move_after = node_id\n        session.add(self)\n\n    def move_before(self, node_id):\n        \"\"\" Moving one node of tree before another\n\n        For example see:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_before_function`\n        * :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_before_to_other_tree`\n        * :mod:`sqlalchemy_mptt.tests.cases.move_node.test_move_before_to_top_level`\n        \"\"\"  # noqa\n        session = Session.object_session(self)\n        table = _get_tree_table(self.__mapper__)\n        pk = getattr(table.c, self.get_pk_column().name)\n        node = session.query(table).filter(pk == node_id).one()\n        self.parent_id = node.parent_id\n        self.mptt_move_before = node_id\n        session.add(self)\n\n    def leftsibling_in_level(self):\n        \"\"\" Node to the left of the current node at the same level\n\n        For example see\n        :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_leftsibling_in_level`\n        \"\"\"  # noqa\n        table = _get_tree_table(self.__mapper__)\n        session = Session.object_session(self)\n        current_lvl_nodes = (\n            session.query(table)\n            .filter_by(level=self.level)\n            .filter_by(tree_id=self.tree_id)\n            .filter(table.c.lft < self.left)\n            .order_by(table.c.lft)\n            .all()\n        )\n        if current_lvl_nodes:\n            return current_lvl_nodes[-1]\n        return None\n\n    @classmethod\n    def _node_to_dict(cls, node, json, json_fields):\n        \"\"\" Helper method for ``get_tree``.\n        \"\"\"\n        if json:\n            pk_name = node.get_pk_name()\n            # jqTree or jsTree format\n            result = {\"id\": getattr(node, pk_name), \"label\": node.__repr__()}\n            if json_fields:\n                result.update(json_fields(node))\n        else:\n            result = {\"node\": node}\n        return result\n\n    @classmethod\n    def _base_query(cls, session=None):\n        return session.query(cls)\n\n    def _base_query_obj(self, session=None):\n        if not session:\n            session = object_session(self)\n        return self._base_query(session)\n\n    @classmethod\n    def _base_order(cls, query, order=asc):\n        return (\n            query.order_by(order(cls.tree_id))\n            .order_by(order(cls.level))\n            .order_by(order(cls.left))\n        )\n\n    @classmethod\n    def get_tree(cls, session=None, json=False, json_fields=None, query=None):\n        \"\"\" This method generate tree of current node table in dict or json\n        format. You can make custom query with attribute ``query``. By default\n        it return all nodes in table.\n\n        Args:\n            session (:mod:`sqlalchemy.orm.session.Session`): SQLAlchemy session\n\n        Kwargs:\n            json (bool): if True return JSON jqTree format\n            json_fields (function): append custom fields in JSON\n            query (function): it takes :class:`sqlalchemy.orm.query.Query`\n            object as an argument, and returns in a modified form\n\n                .. testcode::\n\n                    def query(nodes):\n                        return nodes.filter(node.__class__.tree_id.is_(node.tree_id))\n\n                    node.get_tree(session=session, json=True, query=query)\n\n        Example:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_get_tree`\n        * :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_get_json_tree`\n        * :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_get_json_tree_with_custom_field`\n        \"\"\"  # noqa\n        tree = []\n        nodes_of_level = {}\n\n        # handle custom query\n        nodes = cls._base_query(session)\n        if query:\n            nodes = query(nodes)\n        nodes = cls._base_order(nodes).all()\n\n        # search minimal level of nodes.\n        min_level = min([node.level for node in nodes] or [None])\n\n        def get_node_id(node):\n            return getattr(node, node.get_pk_name())\n\n        for node in nodes:\n            result = cls._node_to_dict(node, json, json_fields)\n            parent_id = node.parent_id\n            if node.level != min_level:  # for children\n                # Find parent in the tree\n                if parent_id not in nodes_of_level.keys():\n                    continue\n                if \"children\" not in nodes_of_level[parent_id]:\n                    nodes_of_level[parent_id][\"children\"] = []\n                # Append node to parent\n                nl = nodes_of_level[parent_id][\"children\"]\n                nl.append(result)\n                nodes_of_level[get_node_id(node)] = nl[-1]\n            else:  # for top level nodes\n                tree.append(result)\n                nodes_of_level[get_node_id(node)] = tree[-1]\n        return tree\n\n    def _drilldown_query(self, nodes=None):\n        table = self.__class__\n        if not nodes:\n            nodes = self._base_query_obj()\n        return nodes.filter(self.is_ancestor_of(table, inclusive=True))\n\n    def drilldown_tree(self, session=None, json=False, json_fields=None):\n        \"\"\" This method generate a branch from a tree, beginning with current\n        node.\n\n        For example:\n\n            .. testcode::\n\n                node7.drilldown_tree()\n\n            .. code::\n\n                level           Nested sets example\n                1                    1(1)22       ---------------------\n                        _______________|_________|_________            |\n                       |               |         |         |           |\n                2    2(2)5           6(4)11      |      12(7)21        |\n                       |               ^         |         ^           |\n                3    3(3)4       7(5)8   9(6)10  | 13(8)16   17(10)20  |\n                                                 |    |          |     |\n                4                                | 14(9)15   18(11)19  |\n                                                 |                     |\n                                                  ---------------------\n\n        Example in tests:\n\n            * :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_drilldown_tree`\n        \"\"\"\n        if not session:\n            session = object_session(self)\n        return self.get_tree(\n            session,\n            json=json,\n            json_fields=json_fields,\n            query=self._drilldown_query,\n        )\n\n    def path_to_root(self, session=None, order=desc):\n        r\"\"\"Generate path from a leaf or intermediate node to the root.\n\n        For example:\n\n            .. testcode::\n\n                node11.path_to_root()\n\n            .. code::\n\n                level           Nested sets example\n\n                                 -----------------------------------------\n                1               |    1(1)22                               |\n                        ________|______|_____________________             |\n                       |        |      |                     |            |\n                       |         ------+---------            |            |\n                2    2(2)5           6(4)11      | --     12(7)21         |\n                       |               ^             |   /      \\         |\n                3    3(3)4       7(5)8   9(6)10      ---/----    \\        |\n                                                    13(8)16 |  17(10)20   |\n                                                       |    |     |       |\n                4                                   14(9)15 | 18(11)19    |\n                                                            |             |\n                                                             -------------\n        \"\"\"\n        table = self.__class__\n        query = self._base_query_obj(session=session)\n        query = query.filter(table.is_ancestor_of(self, inclusive=True))\n        return self._base_order(query, order=order)\n\n    def get_siblings(self, include_self=False, session=None):\n        r\"\"\"\n        * https://github.com/uralbash/sqlalchemy_mptt/issues/64\n        * https://django-mptt.readthedocs.io/en/latest/models.html#get-siblings-include-self-false\n\n        Creates a query containing siblings of this model\n        instance. Root nodes are considered to be siblings of other root\n        nodes.\n\n        For example:\n\n            .. testcode::\n\n                node10.get_siblings() #-> [Node(8)]\n\n            Only one node is sibling of node10\n\n            .. code::\n\n                level           Nested sets example\n\n                1                   1(1)22\n                        ______________|____________________\n                       |              |                    |\n                       |              |                    |\n                2    2(2)5          6(4)11              12(7)21\n                       |              ^                /       \\            |\n                3    3(3)4      7(5)8   9(6)10        /         \\           |\n                                                   13(8)16   17(10)20       |\n                                                      |         |           |\n                4                                  14(9)15   18(11)19       |\n\n\n        \"\"\"\n        table = self.__class__\n        query = self._base_query_obj(session=session)\n        query = query.filter(table.parent_id == self.parent_id)\n        if not include_self:\n            query = query.filter(self.get_pk_column() != self.get_pk_value())\n        return query\n\n    def get_children(self, session=None):\n        r\"\"\"\n        * https://github.com/uralbash/sqlalchemy_mptt/issues/64\n        * https://github.com/django-mptt/django-mptt/blob/fd76a816e05feb5fb0fc23126d33e514460a0ead/mptt/models.py#L563\n\n        Returns a query containing the immediate children of this\n        model instance, in tree order.\n\n        For example:\n\n            .. testcode::\n\n                node7.get_children() #-> [Node(8), Node(10)]\n\n            .. code::\n\n                level           Nested sets example\n\n                1                   1(1)22\n                        ______________|____________________\n                       |              |                    |\n                       |              |                    |\n                2    2(2)5          6(4)11              12(7)21\n                       |              ^                /       \\             |\n                3    3(3)4      7(5)8   9(6)10        /         \\            |\n                                                   13(8)16   17(10)20        |\n                                                      |         |            |\n                4                                  14(9)15   18(11)19        |\n\n\n        \"\"\"\n        table = self.__class__\n        query = self._base_query_obj(session=session)\n        query = query.filter(table.parent_id == self.get_pk_value())\n        return query\n\n    @classmethod\n    def rebuild_tree(cls, session, tree_id):\n        \"\"\" This method rebuild tree.\n\n        Args:\n            session (:mod:`sqlalchemy.orm.session.Session`): SQLAlchemy session\n            tree_id (int or str): id of tree\n\n        Example:\n\n        * :mod:`sqlalchemy_mptt.tests.cases.get_tree.test_rebuild`\n        \"\"\"\n        session.query(cls).filter_by(tree_id=tree_id).update(\n            {cls.left: 0, cls.right: 0, cls.level: 0}\n        )\n        top = (\n            session.query(cls)\n            .filter_by(parent_id=None)\n            .filter_by(tree_id=tree_id)\n            .one()\n        )\n        top.left = left = 1\n        top.right = right = 2\n        top.level = level = cls.get_default_level()\n\n        def recursive(children, left, right, level):\n            level = level + 1\n            for i, node in enumerate(children):\n                same_level_right = children[i - 1].right\n                left = left + 1\n\n                if i > 0:\n                    left = left + 1\n                if same_level_right:\n                    left = same_level_right + 1\n\n                right = left + 1\n                node.left = left\n                node.right = right\n                parent = node.parent\n\n                j = 0\n                while parent:\n                    parent.right = right + 1 + j\n                    parent = parent.parent\n                    j += 1\n\n                node.level = level\n                recursive(node.children, left, right, level)\n\n        recursive(top.children, left, right, level)\n\n    @classmethod\n    def rebuild(cls, session, tree_id=None):\n        \"\"\" This function rebuild tree.\n\n        Args:\n            session (:mod:`sqlalchemy.orm.session.Session`): SQLAlchemy session\n\n        Kwargs:\n            tree_id (int or str): id of tree, default None\n\n        Example:\n\n        * :mod:`sqlalchemy_mptt.tests.TestTree.test_rebuild`\n        \"\"\"\n\n        trees = session.query(cls).filter_by(parent_id=None)\n        if tree_id:\n            trees = trees.filter_by(tree_id=tree_id)\n        for tree in trees:\n            cls.rebuild_tree(session, tree.tree_id)\n"
  },
  {
    "path": "sqlalchemy_mptt/sqlalchemy_compat.py",
    "content": "# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright (c) 2025 Fayaz Yusuf Khan <fayaz.yusuf.khan@gmail.com>\n# Distributed under terms of the MIT license.\n\"\"\"Compatibility layer for SQLAlchemy versions.\"\"\"\nimport sqlalchemy as sa\n\n\nclass LegacySQLAlchemyAPI:\n    \"\"\"A class to provide compatibility for legacy SQLAlchemy versions (1.0 - 1.3).\"\"\"\n\n    @staticmethod\n    def declarative_base(*args, **kwargs):\n        from sqlalchemy.ext.declarative import declarative_base\n        return declarative_base(*args, **kwargs)\n\n    @staticmethod\n    def select(*args, **kwargs):\n        return sa.select(args, **kwargs)\n\n    @staticmethod\n    def case(*args, **kwargs):\n        return sa.case(args, **kwargs)\n\n    @staticmethod\n    def get(session, model, id):\n        return session.query(model).get(id)\n\n\nclass ModernSQLAlchemyAPI:\n    \"\"\"A class to provide compatibility for modern SQLAlchemy versions (1.4+).\"\"\"\n\n    @staticmethod\n    def declarative_base(*args, **kwargs):\n        from sqlalchemy.orm import declarative_base\n        return declarative_base(*args, **kwargs)\n\n    @staticmethod\n    def select(*args, **kwargs):\n        return sa.select(*args, **kwargs)\n\n    @staticmethod\n    def case(*args, **kwargs):\n        return sa.case(*args, **kwargs)\n\n    @staticmethod\n    def get(session, model, id):\n        return session.get(model, id)\n\n\nif sa.__version__ < '1.4':\n    compat_layer = LegacySQLAlchemyAPI()\nelse:\n    compat_layer = ModernSQLAlchemyAPI()\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/__init__.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2014 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\n\"\"\" Base mptt tree\n\n.. code::\n\n    level           Nested sets tree1\n        1                    1(1)22\n                _______________|___________________\n               |               |                   |\n        2    2(2)5           6(4)11             12(7)21\n               |               ^                   ^\n        3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                              |          |\n        4                                  14(9)15   18(11)19\n\n        level           Nested sets tree2\n        1                    1(12)22\n                _______________|___________________\n               |               |                   |\n        2    2(13)5         6(15)11             12(18)21\n               |               ^                    ^\n        3    3(14)4     7(16)8   9(17)10   13(19)16   17(21)20\n                                               |          |\n        4                                  14(20)15   18(22)19\n\n\"\"\"\n# standard library\nimport contextlib\nimport json\nimport os\nimport sys\nimport typing\nimport unittest\n\n# SQLAlchemy\nimport sqlalchemy as sa\nfrom sqlalchemy import create_engine, event\nfrom sqlalchemy.orm import sessionmaker\n\nfrom sqlalchemy_mptt import mptt_sessionmaker\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\n\nfrom .cases.edit_node import Changes\nfrom .cases.get_node import GetNodes\nfrom .cases.get_tree import Tree\nfrom .cases.initialize import Initialize\nfrom .cases.integrity import DataIntegrity\nfrom .cases.move_node import MoveAfter, MoveBefore, MoveInside\n\nBaseType = unittest.TestCase if typing.TYPE_CHECKING else object\n\n\ndef failures_expected_on(*, sqlalchemy_versions=[], python_versions=[]):\n    \"\"\"\n    Decorator to mark tests that are expected to fail on specific versions of\n    SQLAlchemy and/or Python.\n\n    If a parameter is not provided, it is assumed that the failure is expected on all versions.\n    If more than one parameter is provided, it is assumed that the failure is expected on all combinations of those parameters.\n    \"\"\"\n    def decorator(test_method):\n        if sqlalchemy_versions:\n            if not any(sa.__version__.startswith(v) for v in sqlalchemy_versions):\n                return test_method\n        if python_versions:\n            if not any(sys.version.startswith(v) for v in python_versions):\n                return test_method\n        # If we reach here, it means the test is expected to fail\n        return unittest.expectedFailure(test_method)\n    return decorator\n\n\nclass DatabaseSetupMixin(BaseType):\n    base: compat_layer.declarative_base()  # type: ignore\n\n    def setUp(self):\n        with contextlib.suppress(AttributeError):\n            super().setUp()\n        self.engine: sa.engine.Engine = create_engine(\"sqlite:///:memory:\")\n        Session = mptt_sessionmaker(sessionmaker(bind=self.engine))\n        self.session = Session()\n        self.base.metadata.create_all(self.engine)\n\n    def tearDown(self):\n        with contextlib.suppress(AttributeError):\n            super().tearDown()\n        self.session.close()\n        self.engine.dispose()\n\n\nclass Fixtures(object):\n    def __init__(self, session):\n        self.session = session\n\n    def add(self, model, fixtures):\n        here = os.path.dirname(os.path.realpath(__file__))\n        with open(os.path.join(here, fixtures)) as file:\n            fixtures = json.loads(file.read())\n        for fixture in fixtures:\n            if hasattr(model, \"sqlalchemy_mptt_pk_name\"):\n                fixture[model.sqlalchemy_mptt_pk_name] = fixture.pop(\"id\")\n            self.session.add(model(**fixture))\n            self.session.flush()\n\n\nclass TreeTestingMixin(\n    Initialize,\n    Changes,\n    MoveAfter,\n    DataIntegrity,\n    MoveBefore,\n    MoveInside,\n    Tree,\n    GetNodes,\n    DatabaseSetupMixin\n):\n    base = None\n    model = None\n\n    def catch_queries(self, conn, cursor, statement, *args):\n        self.stmts.append(statement)\n\n    def start_query_counter(self):\n        self.stmts = []\n        event.listen(\n            self.session.bind.engine, \"before_cursor_execute\", self.catch_queries\n        )\n\n    def stop_query_counter(self):\n        event.remove(\n            self.session.bind.engine, \"before_cursor_execute\", self.catch_queries\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.fixture = Fixtures(self.session)\n        self.fixture.add(\n            self.model, os.path.join(\"fixtures\", getattr(self, \"fixtures\", \"tree.json\"))\n        )\n\n        self.result = self.session.query(\n            self.model.get_pk_column(),\n            self.model.left,\n            self.model.right,\n            self.model.level,\n            self.model.parent_id,\n            self.model.tree_id,\n        )\n\n    def test_session_expire_for_move_after_to_new_tree(self):\n        \"\"\"\n        https://github.com/uralbash/sqlalchemy_mptt/issues/33\n        \"\"\"\n        node = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 4).one()\n        )\n        children = (\n            self.session.query(self.model)\n            .filter(self.model.get_pk_column().in_((5, 6)))\n            .all()\n        )\n        node.move_after(\"1\")\n        self.session.flush()\n\n        _level = node.get_default_level()\n        self.assertEqual(node.tree_id, 2)\n        self.assertEqual(node.level, _level)\n        self.assertEqual(node.parent_id, None)\n\n        self.assertEqual(children[0].tree_id, 2)\n        self.assertEqual(children[0].parent_id, 4)\n        self.assertEqual(children[0].level, _level + 1)\n\n        self.assertEqual(children[1].tree_id, 2)\n        self.assertEqual(children[1].parent_id, 4)\n        self.assertEqual(children[1].level, _level + 1)\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/__init__.py",
    "content": ""
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/edit_node.py",
    "content": "class Changes(object):\n\n    def test_update_wo_move(self):\n        \"\"\" Update node w/o move\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.visible = True\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())  # flake8: noqa\n\n    def test_update_wo_move_like_sacrud_save(self):\n        \"\"\" Just change attr from node w/o move\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.parent_id = '1'\n        node.visible = True\n        self.session.add(node)\n        _level = node.get_default_level()\n        #                 id lft rgt lvl parent tree\n        self.assertEqual([(1,   1, 22, _level + 0, None, 1),\n                          (2,   2,  5, _level + 1,  1, 1),\n                          (3,   3,  4, _level + 2,  2, 1),\n                          (4,   6, 11, _level + 1,  1, 1),\n                          (5,   7,  8, _level + 2,  4, 1),\n                          (6,   9, 10, _level + 2,  4, 1),\n                          (7,  12, 21, _level + 1,  1, 1),\n                          (8,  13, 16, _level + 2,  7, 1),\n                          (9,  14, 15, _level + 3,  8, 1),\n                          (10, 17, 20, _level + 2,  7, 1),\n                          (11, 18, 19, _level + 3, 10, 1),\n\n                          (12,  1, 22, _level + 0, None, 2),\n                          (13,  2,  5, _level + 1, 12, 2),\n                          (14,  3,  4, _level + 2, 13, 2),\n                          (15,  6, 11, _level + 1, 12, 2),\n                          (16,  7,  8, _level + 2, 15, 2),\n                          (17,  9, 10, _level + 2, 15, 2),\n                          (18, 12, 21, _level + 1, 12, 2),\n                          (19, 13, 16, _level + 2, 18, 2),\n                          (20, 14, 15, _level + 3, 19, 2),\n                          (21, 17, 20, _level + 2, 18, 2),\n                          (22, 18, 19, _level + 3, 21, 2)], self.result.all())\n\n    def test_insert_node(self):\n        \"\"\" Insert node with parent==6\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n            level     Insert node with parent_id == 6\n            1                    1(1)24\n                    _______________|_________________\n                   |               |                 |\n            2    2(2)5           6(4)13           14(7)23\n                   |           ____|____          ___|____\n                   |          |         |        |        |\n            3    3(3)4      7(5)8    9(6)12  15(8)18   19(10)22\n                                       |        |         |\n            4                      10(23)11  16(9)17  20(11)21\n        \"\"\"\n        node = self.model(parent_id=6)\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 24, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 13, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 12, _level + 2,  4, 1),\n                (7,  14, 23, _level + 1,  1, 1),\n                (8,  15, 18, _level + 2,  7, 1),\n                (9,  16, 17, _level + 3,  8, 1),\n                (10, 19, 22, _level + 2,  7, 1),\n                (11, 20, 21, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2),\n\n                (23, 10, 11, _level + 3, 6, 1)\n            ],\n            self.result.all())\n\n    def test_insert_node_near_subtree(self):\n        \"\"\" Insert node with parent==4\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n            level     Insert node with parent_id == 4\n            1                    1(1)24\n                    _______________|_____________________\n                   |               |                     |\n            2    2(2)5           6(4)13               14(7)23\n                   |         ______|________           __|______\n                   |        |      |        |         |         |\n            3    3(3)4    7(5)8  9(6)10  11(23)12  15(8)18   19(10)22\n                                                      |         |\n            4                                      16(9)17   20(11)21\n        \"\"\"\n        node = self.model(parent_id=4)\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 24, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 13, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  14, 23, _level + 1,  1, 1),\n                (8,  15, 18, _level + 2,  7, 1),\n                (9,  16, 17, _level + 3,  8, 1),\n                (10, 19, 22, _level + 2,  7, 1),\n                (11, 20, 21, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2),\n\n                (23, 11, 12, _level + 2,  4, 1)\n            ],\n            self.result.all())\n\n    def test_insert_after_node(self):\n        pass\n\n    def test_delete_node(self):\n        \"\"\" Delete node(4)\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Test delete node\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n            level         Delete node == 4\n            1                    1(1)16\n                    _______________|_____\n                   |                     |\n            2    2(2)5                 6(7)15\n                   |                     ^\n            3    3(3)4            7(8)10   11(10)14\n                                    |          |\n            4                     8(9)9    12(11)13\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        self.session.delete(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 16, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (7,   6, 15, _level + 1,  1, 1),\n                (8,   7, 10, _level + 2,  7, 1),\n                (9,   8,  9, _level + 3,  8, 1),\n                (10, 11, 14, _level + 2,  7, 1),\n                (11, 12, 13, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())\n\n    def test_update_node(self):\n        \"\"\" Set parent_id==5 for node(8)\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Test update node\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n            level               Move 8 - > 5\n                1                     1(1)22\n                         _______________|__________________\n                        |               |                  |\n                2     2(2)5           6(4)15            16(7)21\n                        |               ^                  |\n                3     3(3)4      7(5)12   13(6)14      17(10)20\n                                   |                        |\n                4                8(8)11                18(11)19\n                                   |\n                5                9(9)10\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.parent_id = 5\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1, 1, 22,   _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 15, _level + 1,  1, 1),\n                (5,   7, 12, _level + 2,  4, 1),\n                (6,  13, 14, _level + 2,  4, 1),\n                (7,  16, 21, _level + 1,  1, 1),\n                (8,   8, 11, _level + 3,  5, 1),\n                (9,   9, 10, _level + 4,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())\n\n        \"\"\" level               Move 8 - > 5\n                1                     1(1)22\n                         _______________|__________________\n                        |               |                  |\n                2     2(2)5           6(4)15            16(7)21\n                        |               ^                  |\n                3     3(3)4      7(5)12   13(6)14      17(10)20\n                                   |                        |\n                4                8(8)11                18(11)19\n                                   |\n                5                9(9)10\n            level               Move 4 - > 2\n                1                     1(1)22\n                                ________|_____________\n                               |                      |\n                2            2(2)15                16(7)21\n                           ____|_____                 |\n                          |          |                |\n                3       3(4)12    13(3)14         17(10)20\n                          ^                           |\n                4  4(5)9    10(6)11               18(11)19\n                     |\n                5  5(8)8\n                     |\n                6  6(9)7\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.parent_id = 2\n        self.session.add(node)\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2, 15, _level + 1,  1, 1),\n                (3,  13, 14, _level + 2,  2, 1),\n                (4,   3, 12, _level + 2,  2, 1),\n                (5,   4,  9, _level + 3,  4, 1),\n                (6,  10, 11, _level + 3,  4, 1),\n                (7,  16, 21, _level + 1,  1, 1),\n                (8,   5,  8, _level + 4,  5, 1),\n                (9,   6,  7, _level + 5,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())\n\n        \"\"\" level               Move 4 - > 2\n                1                     1(1)22\n                                ________|_____________\n                               |                      |\n                2            2(2)15                16(7)21\n                           ____|_____                 |\n                          |          |                |\n                3       3(4)12    13(3)14         17(10)20\n                          ^                           |\n                4   4(5)9   10(6)11               18(11)19\n                      |\n                5   5(8)8\n                      |\n                6   6(9)7\n            level               Move 8 - > 10\n                1                     1(1)22\n                                ________|_____________\n                               |                      |\n                2            2(2)11                12(7)21\n                         ______|_____                 |\n                        |            |                |\n                3     3(4)8        9(3)10         13(10)20\n                      __|____                        _|______\n                     |       |                      |        |\n                4  4(5)5   6(6)7                 14(8)17  18(11)19\n                                                    |\n                5                                15(9)16\n        \"\"\"\n\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.parent_id = 10\n        self.session.add(node)\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2, 11, _level + 1,  1, 1),\n                (3,   9, 10, _level + 2,  2, 1),\n                (4,   3,  8, _level + 2,  2, 1),\n                (5,   4,  5, _level + 3,  4, 1),\n                (6,   6,  7, _level + 3,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  14, 17, _level + 3, 10, 1),\n                (9,  15, 16, _level + 4,  8, 1),\n                (10, 13, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())\n\n    def test_rebuild(self):\n        \"\"\" Rebuild tree with tree_id==1\n\n        .. code::\n\n            level      Nested sets w/o left & right (or broken left & right)\n                1                     (1)\n                        _______________|___________________\n                       |               |                   |\n                2     (2)             (4)                 (7)\n                       |               ^                   ^\n                3     (3)          (5)   (6)           (8)   (10)\n                                                        |      |\n                4                                      (9)   (11)\n\n\n                level           Nested sets after rebuild\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n        \"\"\"\n\n        self.session.query(self.model).update({\n            self.model.left: 0,\n            self.model.right: 0,\n            self.model.level: 0\n        })\n        self.model.rebuild(self.session, 1)\n        _level = self.model.get_default_level()\n        self.assertEqual(\n            self.result.all(),\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1, 1,  1),\n                (3,   3,  4, _level + 2, 2,  1),\n                (4,   6, 11, _level + 1, 1,  1),\n                (5,   7,  8, _level + 2, 4,  1),\n                (6,   9, 10, _level + 2, 4,  1),\n                (7,  12, 21, _level + 1, 1,  1),\n                (8,  13, 16, _level + 2, 7,  1),\n                (9,  14, 15, _level + 3, 8,  1),\n                (10, 17, 20, _level + 2, 7,  1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  0,  0, 0, None, 2),\n                (13,  0,  0, 0, 12, 2),\n                (14,  0,  0, 0, 13, 2),\n                (15,  0,  0, 0, 12, 2),\n                (16,  0,  0, 0, 15, 2),\n                (17,  0,  0, 0, 15, 2),\n                (18,  0,  0, 0, 12, 2),\n                (19,  0,  0, 0, 18, 2),\n                (20,  0,  0, 0, 19, 2),\n                (21,  0,  0, 0, 18, 2),\n                (22,  0,  0, 0, 21, 2)\n            ]\n        )\n\n        self.model.rebuild(self.session)\n        self.assertEqual(\n            self.result.all(),\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1, 1,  1),\n                (3,   3,  4, _level + 2, 2,  1),\n                (4,   6, 11, _level + 1, 1,  1),\n                (5,   7,  8, _level + 2, 4,  1),\n                (6,   9, 10, _level + 2, 4,  1),\n                (7,  12, 21, _level + 1, 1,  1),\n                (8,  13, 16, _level + 2, 7,  1),\n                (9,  14, 15, _level + 3, 8,  1),\n                (10, 17, 20, _level + 2, 7,  1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ]\n        )\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/get_node.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2015 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\n\n\nclass GetNodes(object):\n    def test_get_siblings(self):\n        \"\"\"\n        Get siblings of node\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22                              (12)\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n        \"\"\"\n        node10 = (\n            self.session.query(self.model)\n            .filter(self.model.get_pk_column() == 10)\n            .one()\n        )\n        points = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 8).all()\n        )\n        self.assertEqual(points, node10.get_siblings().all())  # flake8: noqa\n\n        node9 = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 9).one()\n        )\n        self.assertEqual([], node9.get_siblings().all())  # flake8: noqa\n\n        node1 = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 1).one()\n        )\n        points = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 12).all()\n        )\n        self.assertEqual(points, node1.get_siblings().all())\n\n    def test_get_children(self):\n        \"\"\"\n        Get children of node\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n        \"\"\"\n        node7 = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 7).one()\n        )\n        points = self.session.query(self.model).filter(self.model.parent_id == 7).all()\n        self.assertEqual(points, node7.get_children().all())  # flake8: noqa\n\n        node9 = (\n            self.session.query(self.model).filter(self.model.get_pk_column() == 9).one()\n        )\n        self.assertEqual([], node9.get_children().all())  # flake8: noqa\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/get_tree.py",
    "content": "from sqlalchemy import asc\n\n\ndef get_obj(session, model, id):\n    return session.query(model).filter(model.get_pk_column() == id).one()\n\n\nclass Tree(object):\n\n    def test_get_empty_tree(self):\n        \"\"\"\n            No rows in database.\n        \"\"\"\n        self.session.query(self.model).delete()\n        self.session.flush()\n        tree = self.model.get_tree(self.session)\n        self.assertEqual(tree, [])\n\n    def test_get_empty_tree_with_custom_query(self):\n        \"\"\"\n            No rows with id < 0.\n        \"\"\"\n        query = lambda x: x.filter(self.model.get_pk_column() < 0)  # noqa\n        tree = self.model.get_tree(self.session, query=query)\n        self.assertEqual(tree, [])\n\n    def test_get_tree(self):\n        \"\"\".. note::\n\n            See [source] for full example\n\n        Return tree as list of dict\n\n        .. code::\n\n            tree = Tree.get_tree(self.session)\n        \"\"\"\n        tree = self.model.get_tree(self.session)\n\n        def go(id):\n            return get_obj(self.session, self.model, id)\n\n        reference_tree = [\n            {'node': go(1),\n             'children':\n             [{'node': go(2),\n               'children': [{'node': go(3)}]},\n              {'node': go(4),\n               'children': [{'node': go(5)},\n                            {'node': go(6)}]},\n              {'node': go(7),\n               'children':\n               [{'node': go(8), 'children': [{'node': go(9)}]},\n                {'node': go(10), 'children': [{'node': go(11)}]}]}]},\n            {'node': go(12),\n             'children': [{'node': go(13), 'children': [{'node': go(14)}]},\n                          {'node': go(15), 'children': [{'node': go(16)},\n                                                        {'node': go(17)}]},\n                          {'node': go(18),\n                           'children': [{'node': go(19),\n                                         'children': [{'node': go(20)}]},\n                                        {'node': go(21),\n                                         'children': [{'node': go(22)}]}]}]}]\n\n        self.assertEqual(tree, reference_tree)\n\n    def test_get_tree_count_query(self):\n        \"\"\"\n        Count num of queries to the database.\n        See https://github.com/uralbash/sqlalchemy_mptt/issues/39\n        \"\"\"\n        # from datetime import datetime\n        self.session.commit()\n\n        # Get tree by for cycle\n        self.start_query_counter()\n        self.assertEqual(0, len(self.stmts))\n        # startTime = datetime.now()\n        self.model.get_tree(self.session)\n        # delta = datetime.now() - startTime\n        # print(\"Get tree: {!s:>26}\".format(delta))\n        self.assertEqual(1, len(self.stmts))\n        self.stop_query_counter()\n\n    def test_get_json_tree(self):\n        \"\"\".. note::\n\n            See [source] for full example\n\n        Return tree as JSON of jqTree format\n\n        .. code::\n\n            tree = Tree.get_tree(self.session, json=True)\n        \"\"\"\n        reference_tree = [\n            {'children': [{'children': [{'id': 3, 'label': '<Node (3)>'}],\n                           'id': 2, 'label': '<Node (2)>'},\n                          {'children': [{'id': 5, 'label': '<Node (5)>'},\n                                        {'id': 6, 'label': '<Node (6)>'}],\n                           'id': 4, 'label': '<Node (4)>'},\n                          {'children':\n                           [{'children': [{'id': 9, 'label': '<Node (9)>'}],\n                             'id': 8, 'label': '<Node (8)>'},\n                            {'children': [{'id': 11, 'label': '<Node (11)>'}],\n                             'id': 10, 'label': '<Node (10)>'}],\n                           'id': 7, 'label': '<Node (7)>'}], 'id': 1,\n             'label': '<Node (1)>'},\n            {'children': [{'children': [{'id': 14, 'label': '<Node (14)>'}],\n                           'id': 13, 'label': '<Node (13)>'},\n                          {'children': [{'id': 16, 'label': '<Node (16)>'},\n                                        {'id': 17, 'label': '<Node (17)>'}],\n                           'id': 15, 'label': '<Node (15)>'},\n                          {'children': [{'children':\n                                         [{'id': 20, 'label': '<Node (20)>'}],\n                                         'id': 19, 'label': '<Node (19)>'},\n                                        {'children':\n                                         [{'id': 22, 'label': '<Node (22)>'}],\n                                         'id': 21, 'label': '<Node (21)>'}],\n                           'id': 18, 'label': '<Node (18)>'}],\n             'id': 12, 'label': '<Node (12)>'}]\n\n        tree = self.model.get_tree(self.session, json=True)\n        self.assertEqual(tree, reference_tree)\n\n    def test_get_json_tree_with_custom_field(self):\n        \"\"\".. note::\n\n            See [source] for full example\n\n        Return tree as JSON of jqTree format with additional field\n\n        .. code-block:: python\n            :linenos:\n\n            def fields(node):\n                return {'visible': node.visible}\n\n            tree = Tree.get_tree(self.session, json=True, json_fields=fields)\n        \"\"\"\n        self.maxDiff = None\n\n        def fields(node):\n            return {'visible': node.visible}\n\n        reference_tree = [\n            {'visible': None, 'children':\n             [{'visible': True, 'children':\n               [{'visible': True, 'id': 3, 'label': '<Node (3)>'}],\n               'id': 2, 'label': '<Node (2)>'},\n              {'visible': True, 'children':\n               [{'visible': True, 'id': 5, 'label': '<Node (5)>'},\n                {'visible': True, 'id': 6, 'label': '<Node (6)>'}],\n               'id': 4, 'label': '<Node (4)>'},\n              {'visible': True, 'children':\n               [{'visible': True, 'children':\n                 [{'visible': None, 'id': 9, 'label': '<Node (9)>'}],\n                 'id': 8, 'label': '<Node (8)>'},\n                {'visible': None, 'children':\n                 [{'visible': None, 'id': 11, 'label': '<Node (11)>'}],\n                 'id': 10, 'label': '<Node (10)>'}],\n               'id': 7, 'label': '<Node (7)>'}],\n             'id': 1, 'label': '<Node (1)>'},\n            {'visible': None, 'children':\n             [{'visible': None, 'children':\n               [{'visible': None, 'id': 14, 'label': '<Node (14)>'}],\n               'id': 13, 'label': '<Node (13)>'},\n              {'visible': None, 'children':\n               [{'visible': None, 'id': 16, 'label': '<Node (16)>'},\n                {'visible': None, 'id': 17, 'label': '<Node (17)>'}],\n               'id': 15, 'label': '<Node (15)>'},\n              {'visible': None, 'children':\n               [{'visible': None, 'children':\n                 [{'visible': None, 'id': 20, 'label': '<Node (20)>'}],\n                 'id': 19, 'label': '<Node (19)>'},\n                {'visible': None, 'children':\n                 [{'visible': None, 'id': 22, 'label': '<Node (22)>'}],\n                 'id': 21, 'label': '<Node (21)>'}],\n               'id': 18, 'label': '<Node (18)>'}],\n             'id': 12, 'label': '<Node (12)>'}]\n\n        tree = self.model.get_tree(self.session, json=True, json_fields=fields)\n        self.assertEqual(tree, reference_tree)\n\n    def test_leftsibling_in_level(self):\n        \"\"\" Node to the left of the current node at the same level\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level1 = [1]\n            level2 = [2, 4, 7]\n            level3 = [3, 5, 6, 8, 10]\n            level4 = [9, 11]\n\n            leftsibling_in_level_of_node_3 = None\n            leftsibling_in_level_of_node_5 = 3\n            leftsibling_in_level_of_node_6 = 5\n            leftsibling_in_level_of_node_8 = 6\n            leftsibling_in_level_of_node_11 = 9\n        \"\"\"\n        q = self.session.query(self.model)\n        node3 = q.filter(self.model.get_pk_column() == 3).one()\n        node5 = q.filter(self.model.get_pk_column() == 5).one()\n        node6 = q.filter(self.model.get_pk_column() == 6).one()\n        node8 = q.filter(self.model.get_pk_column() == 8).one()\n        node10 = q.filter(self.model.get_pk_column() == 10).one()\n\n        pk_name = self.model.get_pk_name()\n        pk_column_name = self.model.get_pk_column().name\n\n        self.assertEqual(\n            getattr(node10.leftsibling_in_level(), pk_column_name),\n            getattr(node8, pk_name)\n        )\n\n        self.assertEqual(\n            getattr(node8.leftsibling_in_level(), pk_column_name),\n            getattr(node6, pk_name)\n        )\n        self.assertEqual(\n            getattr(node6.leftsibling_in_level(), pk_column_name),\n            getattr(node5, pk_name)\n        )\n        self.assertEqual(node3.leftsibling_in_level(), None)\n\n    def test_drilldown_tree(self):\n        \"\"\"\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22       ---------------------\n                    _______________|_________|_________            |\n                   |               |         |         |           |\n            2    2(2)5           6(4)11      |      12(7)21        |\n                   |               ^         |         ^           |\n            3    3(3)4       7(5)8   9(6)10  | 13(8)16   17(10)20  |\n                                             |    |          |     |\n            4                                | 14(9)15   18(11)19  |\n                                             |                     |\n                                              ---------------------\n        \"\"\"\n        def go(id):\n            return get_obj(self.session, self.model, id)\n\n        node = go(7)\n        tree = node.drilldown_tree(self.session)\n        reference_tree = [\n            {'node': go(7),\n             'children': [\n                 {'node': go(8),\n                  'children': [\n                      {'node': go(9)}]},\n                 {'node': go(10),\n                  'children': [\n                      {'node': go(11)}]}]\n             }\n        ]\n        self.assertEqual(tree, reference_tree)\n\n    def test_drilldown_tree_without_session(self):\n        def go(id):\n            return get_obj(self.session, self.model, id)\n        node = go(7)\n        tree = node.drilldown_tree()\n        reference_tree = [\n            {'node': go(7),\n             'children': [\n                 {'node': go(8),\n                  'children': [\n                      {'node': go(9)}]},\n                 {'node': go(10),\n                  'children': [\n                      {'node': go(11)}]}]\n             }\n        ]\n        self.assertEqual(tree, reference_tree)\n\n    def test_path_to_root(self):\n        r\"\"\"Generate path from a leaf or intermediate node to the root.\n\n        For example:\n\n            node11.path_to_root()\n\n            .. code::\n\n                level           Nested sets example\n\n                                 -----------------------------------------\n                1               |    1(1)22                               |\n                        ________|______|_____________________             |\n                       |        |      |                     |            |\n                       |          -----+---------            |            |\n                2    2(2)5           6(4)11      | --     12(7)21         |\n                       |               ^             |    /     \\         |\n                3    3(3)4       7(5)8   9(6)10      ---/----    \\        |\n                                                    13(8)16 |  17(10)20   |\n                                                       |    |     |       |\n                4                                   14(9)15 | 18(11)19    |\n                                                            |             |\n                                                             -------------\n        \"\"\"\n        def go(id):\n            return get_obj(self.session, self.model, id)\n\n        node11 = go(11)\n        node8 = go(8)\n        node6 = go(6)\n        node1 = go(1)\n        path_11_to_root = node11.path_to_root(self.session).all()\n        path_8_to_root = node8.path_to_root(self.session).all()\n        path_6_to_root = node6.path_to_root(self.session).all()\n        path_1_to_root = node1.path_to_root(self.session).all()\n        self.assertEqual(path_11_to_root, [go(11), go(10), go(7), go(1)])\n        self.assertEqual(path_8_to_root, [go(8), go(7), go(1)])\n        self.assertEqual(path_6_to_root, [go(6), go(4), go(1)])\n        self.assertEqual(path_1_to_root, [go(1)])\n\n        asc_path_11_to_root = node11.path_to_root(self.session, order=asc).all()\n        self.assertEqual(asc_path_11_to_root, [go(1), go(7), go(10), go(11)])\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/initialize.py",
    "content": "from sqlalchemy.exc import IntegrityError\n\n\nclass Initialize(object):\n\n    def test_tree_orm_initialize(self):\n        pk_name = self.model.get_pk_name()\n        t0 = self.model(**{pk_name: 30})\n        t1 = self.model(**{pk_name: 31, 'parent': t0})\n        t2 = self.model(**{pk_name: 32, 'parent': t1})\n        t3 = self.model(**{pk_name: 33, 'parent': t1})\n\n        self.session.add(t0)\n        self.session.flush()\n\n        self.assertEqual(t0.left, 1)\n        self.assertEqual(t0.right, 8)\n\n        self.assertEqual(t1.left, 2)\n        self.assertEqual(t1.right, 7)\n\n        self.assertEqual(t2.left, 3)\n        self.assertEqual(t2.right, 4)\n\n        self.assertEqual(t3.left, 5)\n        self.assertEqual(t3.right, 6)\n\n        t0 = self.model(**{pk_name: 40})\n        t1 = self.model(**{pk_name: 41, 'parent': t0})\n        t2 = self.model(**{pk_name: 42, 'parent': t1})\n        t3 = self.model(**{pk_name: 43, 'parent': t2})\n        t4 = self.model(**{pk_name: 44, 'parent': t3})\n        t5 = self.model(**{pk_name: 45, 'parent': t4})\n\n        self.session.add(t3)\n        self.session.flush()\n\n        self.assertEqual(t0.left, 1)\n        self.assertEqual(t0.right, 12)\n\n        self.assertEqual(t1.left, 2)\n        self.assertEqual(t1.right, 11)\n\n        self.assertEqual(t2.left, 3)\n        self.assertEqual(t2.right, 10)\n\n        self.assertEqual(t3.left, 4)\n        self.assertEqual(t3.right, 9)\n\n        self.assertEqual(t4.left, 5)\n        self.assertEqual(t4.right, 8)\n\n        self.assertEqual(t5.left, 6)\n        self.assertEqual(t5.right, 7)\n\n    def test_flush_with_transient_nodes_present(self):\n        \"\"\"\n        https://github.com/uralbash/sqlalchemy_mptt/issues/34\n        \"\"\"\n        pk_name = self.model.get_pk_name()\n        transient_node = self.model(**{pk_name: 1, 'parent': None})\n        self.session.add(transient_node)\n        try:\n            self.session.flush()\n        except IntegrityError:\n            pass\n        self.session.rollback()\n        self.session.add(self.model(**{pk_name: 46, 'parent': None}))\n        self.session.flush()\n\n    def test_tree_initialize(self):\n        \"\"\" Initial state of the trees\n\n        .. code::\n\n            level               Tree 1\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n\n            level               Tree 2\n            1                    1(12)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(13)5         6(15)11              12(18)21\n                   |               ^                    ^\n            3    3(14)4     7(16)8   9(17)10   13(19)16   17(21)20\n                                                   |          |\n            4                                  14(20)15   18(22)19\n\n        \"\"\"\n        _level = self.model.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())  # flake8: noqa\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/integrity.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2015 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\nfrom sqlalchemy.sql import func\n\n\nclass DataIntegrity(object):\n\n    def test_left_is_always_less_than_right(self):\n        \"\"\" The left key is always less than the right.\n\n        The following example should return an empty result.\n\n        .. code-block:: sql\n\n            SELECT id FROM tree WHERE left >= right\n        \"\"\"\n        table = self.model\n        nop = self.session.query(table).filter(table.left >= table.right).all()\n        self.assertEqual(nop, [])\n\n    def test_lowest_left_is_always_1(self):\n        \"\"\" The lowest left key is always 1.\n\n        The following example should return 1.\n\n        .. code-block:: sql\n\n            SELECT MIN(left) FROM tree\n        \"\"\"\n        table = self.model\n        one = self.session.query(func.min(table.left)).scalar()\n        self.assertEqual(one, 1)\n\n    def test_greatest_right_is_always_double_number_of_nodes(self):\n        \"\"\" The greatest right key is always double the number of nodes.\n\n        The following example should match COUNT(id) * 2 equal MAX(right).\n\n        .. code-block:: sql\n\n            SELECT COUNT(id), MAX(right) FROM tree\n        \"\"\"\n        table = self.model\n        result = self.session.query(\n            func.count(table.get_pk_name()),\n            func.max(table.right)).group_by(table.tree_id).all()\n        for tree in result:\n            self.assertEqual(tree[0] * 2, tree[1])\n\n    def test_right_minus_left_always_odd(self):\n        \"\"\" Difference between left and right keys are always an odd number.\n\n        The following example should return an empty result.\n\n        .. code-block:: sql\n\n            SELECT MOD((right - left) / 2) AS modulo\n            FROM tree WHERE modulo = 0\n        \"\"\"\n        table = self.model\n        modulo = (table.right - table.left) % 2\n        nop = self.session.query(table).filter(modulo == 0).all()\n        self.assertEqual(nop, [])\n\n    def test_level_odd_when_left_odd_and_vice_versa(self):\n        \"\"\" If the node number is odd then the left key is always an odd\n        number, and the same goes for the even numbers.\n\n        The following example should return an empty result.\n\n        .. code-block:: sql\n\n            SELECT id, MOD((left - level + 2) / 2) AS modulo FROM tree\n            WHERE modulo = 1\n        \"\"\"\n        table = self.model\n        level_delta = pow(0, table.get_default_level() % 2)\n        modulo = (table.left - table.level + level_delta + 2) % 2\n        nop = self.session.query(table).filter(modulo == 1).all()\n        self.assertEqual(nop, [])\n\n    def test_left_and_right_always_unique_number(self):\n        \"\"\" left and right always is unique.\n        \"\"\"\n        table = self.model\n        left = self.session.query(table.left)\n        right = self.session.query(table.right)\n        keys = [x[0] for x in left.union(right)]\n        self.assertEqual(len(keys), len(set(keys)))\n\n    def test_hierarchy_structure(self):\n        \"\"\" Nodes with left < self and right > self are considered ancestors,\n        while nodes with left > self and right < self are considered\n        descendants\n        \"\"\"\n        table = self.model\n        pivot = self.session.query(table).filter(\n            table.right - table.left != 1\n        ).filter(table.parent_id != None).first()  # noqa\n\n        # Exclusive Tests\n        ancestors = self.session.query(table).filter(\n            table.is_ancestor_of(pivot)\n        ).all()\n        for ancestor in ancestors:\n            self.assertTrue(ancestor.is_ancestor_of(pivot))\n        self.assertNotIn(pivot, ancestors)\n\n        descendants = self.session.query(table).filter(\n            table.is_descendant_of(pivot)\n        ).all()\n        for descendant in descendants:\n            self.assertTrue(descendant.is_descendant_of(pivot))\n        self.assertNotIn(pivot, descendants)\n\n        self.assertEqual(set(), set(ancestors).intersection(set(descendants)))\n\n        # Inclusive Tests - because sometimes inclusivity is nice, like with\n        # self joins\n        ancestors = self.session.query(table).filter(\n            table.is_ancestor_of(pivot, inclusive=True)\n        ).all()\n        for ancestor in ancestors:\n            self.assertTrue(ancestor.is_ancestor_of(pivot, inclusive=True))\n        self.assertIn(pivot, ancestors)\n\n        descendants = self.session.query(table).filter(\n            table.is_descendant_of(pivot, inclusive=True)\n        ).all()\n        for descendant in descendants:\n            self.assertTrue(descendant.is_descendant_of(pivot, inclusive=True))\n        self.assertIn(pivot, descendants)\n\n        self.assertEqual(\n            set([pivot]),\n            set(ancestors).intersection(set(descendants))\n        )\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/cases/move_node.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2015 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\nimport os\n\n\nclass MoveBefore(object):\n\n    def test_move_before_to_top_level(self):\n        \"\"\" For example move node(4) before node(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level            move 4 before 1\n                1         1(4)6              1(1)16\n                            ^           _______|_______\n                      2(5)3   4(6)5    |               |\n                2                    2(2)5           6(7)15\n                                       |               ^\n                3                    3(3)4      7(8)10   11(10)14\n                                                  |          |\n                4                               8(9)9    12(11)13\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.move_before(1)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 16, _level + 0, None, 2),\n                (2,   2,  5, _level + 1,  1, 2),\n                (3,   3,  4, _level + 2,  2, 2),\n\n                (4,   1,  6, _level + 0,  None, 1),\n                (5,   2,  3, _level + 1,  4, 1),\n                (6,   4,  5, _level + 1,  4, 1),\n\n                (7,   6, 15, _level + 1,  1, 2),\n                (8,   7, 10, _level + 2,  7, 2),\n                (9,   8,  9, _level + 3,  8, 2),\n                (10, 11, 14, _level + 2,  7, 2),\n                (11, 12, 13, _level + 3, 10, 2),\n\n                (12,  1, 22, _level + 0, None, 3),\n                (13,  2,  5, _level + 1, 12, 3),\n                (14,  3,  4, _level + 2, 13, 3),\n                (15,  6, 11, _level + 1, 12, 3),\n                (16,  7,  8, _level + 2, 15, 3),\n                (17,  9, 10, _level + 2, 15, 3),\n                (18, 12, 21, _level + 1, 12, 3),\n                (19, 13, 16, _level + 2, 18, 3),\n                (20, 14, 15, _level + 3, 19, 3),\n                (21, 17, 20, _level + 2, 18, 3),\n                (22, 18, 19, _level + 3, 21, 3)\n            ],\n            self.result.all())  # flake8: noqa\n\n    def test_move_one_tree_before_another(self):\n        \"\"\" For example move node(12) before node(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n                                        <--------------------------------\n                                                                        |\n            level           Nested sets tree1                           |\n                1                    1(1)22                             |\n                        _______________|___________________             |\n                       |               |                   |            |\n                2    2(2)5           6(4)11             12(7)21         |\n                       |               ^                   ^            |\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20   |\n                                                      |          |      |\n                4                                  14(9)15   18(11)19   |\n                                                                        |\n                level           Nested sets tree2                       |\n                1                    1(12)22 ----------------------------\n                        _______________|___________________\n                       |               |                   |\n                2    2(13)5         6(15)11             12(18)21\n                       |               ^                    ^\n                3    3(14)4     7(16)8   9(17)10   13(19)16   17(21)20\n                                                       |          |\n                4                                  14(20)15   18(22)19\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 12).one()\n        node.move_before(\"1\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 2),\n                (2,   2,  5, _level + 1,  1, 2),\n                (3,   3,  4, _level + 2,  2, 2),\n                (4,   6, 11, _level + 1,  1, 2),\n                (5,   7,  8, _level + 2,  4, 2),\n                (6,   9, 10, _level + 2,  4, 2),\n                (7,  12, 21, _level + 1,  1, 2),\n                (8,  13, 16, _level + 2,  7, 2),\n                (9,  14, 15, _level + 3,  8, 2),\n                (10, 17, 20, _level + 2,  7, 2),\n                (11, 18, 19, _level + 3, 10, 2),\n\n                (12,  1, 22, _level + 0, None, 1),\n                (13,  2,  5, _level + 1, 12, 1),\n                (14,  3,  4, _level + 2, 13, 1),\n                (15,  6, 11, _level + 1, 12, 1),\n                (16,  7,  8, _level + 2, 15, 1),\n                (17,  9, 10, _level + 2, 15, 1),\n                (18, 12, 21, _level + 1, 12, 1),\n                (19, 13, 16, _level + 2, 18, 1),\n                (20, 14, 15, _level + 3, 19, 1),\n                (21, 17, 20, _level + 2, 18, 1),\n                (22, 18, 19, _level + 3, 21, 1)\n            ],\n            self.result.all())\n\n    def test_move_before_function(self):\n        \"\"\" For example move node(8) before node(4)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level           move 8 before 4\n            1                    1(1)22\n                    _______________|___________________\n                   |        |            |             |\n            2    2(2)5    6(8)9       10(4)15       16(7)21\n                   |        |            ^             |\n            3    3(3)4    7(9)8   11(5)12 13(6)14  17(10)20\n                                                       |\n            4                                      18(11)19\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.move_before(\"4\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,  10, 15, _level + 1,  1, 1),\n                (5,  11, 12, _level + 2,  4, 1),\n                (6,  13, 14, _level + 2,  4, 1),\n                (7,  16, 21, _level + 1,  1, 1),\n                (8,   6,  9, _level + 1,  1, 1),\n                (9,   7,  8, _level + 2,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all())\n\n    def test_move_one_tree_before_other_tree(self):\n        self.fixture.add(\n            self.model,\n            os.path.join('fixtures', 'tree_3.json')\n        )\n        self.session.commit()\n        self.maxDiff = None\n\n        node = self.session.query(self.model).\\\n            filter(self.model.get_pk_column() == 12).one()\n        node.move_before(\"1\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            self.result.all(),\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 2),\n                (2,   2,  5, _level + 1,  1, 2),\n                (3,   3,  4, _level + 2,  2, 2),\n                (4,   6, 11, _level + 1,  1, 2),\n                (5,   7,  8, _level + 2,  4, 2),\n                (6,   9, 10, _level + 2,  4, 2),\n                (7,  12, 21, _level + 1,  1, 2),\n                (8,  13, 16, _level + 2,  7, 2),\n                (9,  14, 15, _level + 3,  8, 2),\n                (10, 17, 20, _level + 2,  7, 2),\n                (11, 18, 19, _level + 3, 10, 2),\n\n                (12,  1, 22, _level + 0, None, 1),\n                (13,  2,  5, _level + 1, 12, 1),\n                (14,  3,  4, _level + 2, 13, 1),\n                (15,  6, 11, _level + 1, 12, 1),\n                (16,  7,  8, _level + 2, 15, 1),\n                (17,  9, 10, _level + 2, 15, 1),\n                (18, 12, 21, _level + 1, 12, 1),\n                (19, 13, 16, _level + 2, 18, 1),\n                (20, 14, 15, _level + 3, 19, 1),\n                (21, 17, 20, _level + 2, 18, 1),\n                (22, 18, 19, _level + 3, 21, 1),\n\n                (23,  1, 22, _level + 0, None, 4),\n                (24,  2,  5, _level + 1, 23, 4),\n                (25,  3,  4, _level + 2, 24, 4),\n                (26,  6, 11, _level + 1, 23, 4),\n                (27,  7,  8, _level + 2, 26, 4),\n                (28,  9, 10, _level + 2, 26, 4),\n                (29, 12, 21, _level + 1, 23, 4),\n                (30, 13, 16, _level + 2, 29, 4),\n                (31, 14, 15, _level + 3, 30, 4),\n                (32, 17, 20, _level + 2, 29, 4),\n                (33, 18, 19, _level + 3, 32, 4)\n            ]\n        )\n\n        node = self.session.query(self.model).\\\n            filter(self.model.get_pk_column() == 23).one()\n        node.move_before(\"1\")\n\n        _level = node.get_default_level()\n        self.assertEqual(\n            self.result.all(),\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 3),\n                (2,   2,  5, _level + 1,  1, 3),\n                (3,   3,  4, _level + 2,  2, 3),\n                (4,   6, 11, _level + 1,  1, 3),\n                (5,   7,  8, _level + 2,  4, 3),\n                (6,   9, 10, _level + 2,  4, 3),\n                (7,  12, 21, _level + 1,  1, 3),\n                (8,  13, 16, _level + 2,  7, 3),\n                (9,  14, 15, _level + 3,  8, 3),\n                (10, 17, 20, _level + 2,  7, 3),\n                (11, 18, 19, _level + 3, 10, 3),\n\n                (12,  1, 22, _level + 0, None, 1),\n                (13,  2,  5, _level + 1, 12, 1),\n                (14,  3,  4, _level + 2, 13, 1),\n                (15,  6, 11, _level + 1, 12, 1),\n                (16,  7,  8, _level + 2, 15, 1),\n                (17,  9, 10, _level + 2, 15, 1),\n                (18, 12, 21, _level + 1, 12, 1),\n                (19, 13, 16, _level + 2, 18, 1),\n                (20, 14, 15, _level + 3, 19, 1),\n                (21, 17, 20, _level + 2, 18, 1),\n                (22, 18, 19, _level + 3, 21, 1),\n\n                (23,  1, 22, _level + 0, None, 2),\n                (24,  2,  5, _level + 1, 23, 2),\n                (25,  3,  4, _level + 2, 24, 2),\n                (26,  6, 11, _level + 1, 23, 2),\n                (27,  7,  8, _level + 2, 26, 2),\n                (28,  9, 10, _level + 2, 26, 2),\n                (29, 12, 21, _level + 1, 23, 2),\n                (30, 13, 16, _level + 2, 29, 2),\n                (31, 14, 15, _level + 3, 30, 2),\n                (32, 17, 20, _level + 2, 29, 2),\n                (33, 18, 19, _level + 3, 32, 2)\n            ]\n        )\n\n        node = self.session.query(self.model).\\\n            filter(self.model.get_pk_column() == 1).one()\n        node.move_before(\"12\")\n\n        _level = node.get_default_level()\n        self.assertEqual(\n            self.result.all(),\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2),\n\n                (23,  1, 22, _level + 0, None, 3),\n                (24,  2,  5, _level + 1, 23, 3),\n                (25,  3,  4, _level + 2, 24, 3),\n                (26,  6, 11, _level + 1, 23, 3),\n                (27,  7,  8, _level + 2, 26, 3),\n                (28,  9, 10, _level + 2, 26, 3),\n                (29, 12, 21, _level + 1, 23, 3),\n                (30, 13, 16, _level + 2, 29, 3),\n                (31, 14, 15, _level + 3, 30, 3),\n                (32, 17, 20, _level + 2, 29, 3),\n                (33, 18, 19, _level + 3, 32, 3)\n            ]\n        )\n\n    def test_move_before_to_other_tree(self):\n        \"\"\" For example move node(8) before node(15)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Move 8 before 15\n            1                    1(1)18\n                     _______________|___________________\n                    |               |                   |\n            2     2(2)5           6(4)11             12(7)17\n                    |               ^                   |\n            3     3(3)4       7(5)8   9(6)10        13(10)16\n                                                        |\n            4                                       14(11)15\n\n            level\n            1                    1(12)26\n                     _______________|______________________________\n                    |         |               |                    |\n            2    2(13)5     6(8)9         10(15)15             16(18)25\n                    |         |               ^                    ^\n            3    3(14)4     7(9)8    11(16)12  13(17)14   17(19)20   21(21)24\n                                                              |          |\n            4                                             18(20)19   22(22)23\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.move_before(\"15\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 18, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 17, _level + 1,  1, 1),\n\n                (8,   6,  9, _level + 1,  12, 2),\n                (9,   7,  8, _level + 2,   8, 2),\n\n                (10, 13, 16, _level + 2,  7, 1),\n                (11, 14, 15, _level + 3, 10, 1),\n\n                (12,  1, 26, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15, 10, 15, _level + 1, 12, 2),\n                (16, 11, 12, _level + 2, 15, 2),\n                (17, 13, 14, _level + 2, 15, 2),\n                (18, 16, 25, _level + 1, 12, 2),\n                (19, 17, 20, _level + 2, 18, 2),\n                (20, 18, 19, _level + 3, 19, 2),\n                (21, 21, 24, _level + 2, 18, 2),\n                (22, 22, 23, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n\nclass MoveAfter(object):\n\n    def test_move_after_function(self):\n        \"\"\" For example move node(8) after node(5)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n                level               Initial state\n                    1                    1(1)22\n                            _______________|___________________\n                           |               |                   |\n                    2    2(2)5           6(4)11             12(7)21\n                           |               ^                   ^\n                    3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                          |          |\n                    4                                  14(9)15   18(11)19\n\n                level               Move 8 after 5\n                    1                     1(1)22\n                            _______________|__________________\n                           |               |                  |\n                    2     2(2)5           6(4)15            16(7)21\n                            |               ^                  |\n                    3     3(3)4    7(5)8  9(8)12  13(6)14   17(10)20\n                                            |                  |\n                    4                    10(9)11            18(11)19\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.move_after(\"5\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 15, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,  13, 14, _level + 2,  4, 1),\n                (7,  16, 21, _level + 1,  1, 1),\n                (8,   9, 12, _level + 2,  4, 1),\n                (9,  10, 11, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_move_to_toplevel_where_much_trees_from_right_side(self):\n        \"\"\" Move 20 after 1\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           tree_id = 1\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level           tree_id = 2\n            1                     1(15)6\n                                     ^\n            2                 2(16)3   4(17)5\n\n            level           tree_id = 3\n            1                    1(12)16\n                     _______________|\n                    |               |\n            2    2(13)5          6(18)15\n                    |               ^\n            3    3(14)4     7(19)10   11(21)14\n                               |          |\n            4               8(20)9    12(22)13\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 15).one()\n        node.move_after(\"1\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12, 1, 16,  _level + 0, None, 3),\n                (13, 2,  5,  _level + 1, 12,   3),\n                (14, 3,  4,  _level + 2, 13,   3),\n\n                (15, 1, 6,   _level + 0, None, 2),\n                (16, 2, 3,   _level + 1, 15,   2),\n                (17, 4, 5,   _level + 1, 15,   2),\n\n                (18,  6, 15, _level + 1, 12, 3),\n                (19,  7, 10, _level + 2, 18, 3),\n                (20,  8,  9, _level + 3, 19, 3),\n                (21, 11, 14, _level + 2, 18, 3),\n                (22, 12, 13, _level + 3, 21, 3)\n            ],\n            self.result.all()\n        )\n\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 20).one()\n        node.move_after(\"1\")\n        \"\"\" level           tree_id = 1\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level           tree_id = 2\n            1                   1(20)2\n\n            level           tree_id = 3\n            1                     1(15)6\n                                     ^\n            2                 2(16)3   4(17)5\n\n            level           tree_id = 4\n            1                    1(12)14\n                     _______________|\n                    |               |\n            2    2(13)5          6(18)13\n                    |               ^\n            3    3(14)4     7(19)8     9(21)12\n                                          |\n            4                         10(22)11\n\n        \"\"\"\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12, 1, 14,  _level + 0, None, 4),\n                (13, 2,  5,  _level + 1, 12,   4),\n                (14, 3,  4,  _level + 2, 13,   4),\n\n                (15, 1, 6,   _level + 0, None, 3),\n                (16, 2, 3,   _level + 1, 15,   3),\n                (17, 4, 5,   _level + 1, 15,   3),\n\n                (18,  6, 13, _level + 1, 12, 4),\n                (19,  7,  8, _level + 2, 18, 4),\n                (20,  1,  2, _level + 0, None, 2),\n                (21,  9, 12, _level + 2, 18, 4),\n                (22, 10, 11, _level + 3, 21, 4)\n            ],\n            self.result.all()\n        )\n\n    def test_move_to_toplevel(self):\n        \"\"\" Move node(8) to top level after node(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 8 after 1\n                1                     1(1)18                     1(8)4\n                         _______________|______________            |\n                        |               |              |           |\n                2     2(2)5           6(4)11        12(7)17      2(9)3\n                        |               ^              |\n                3     3(3)4       7(5)8   9(6)10   13(10)16\n                                                       |\n                4                                  14(11)15\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.move_after(\"1\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 18, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 17, _level + 1,  1, 1),\n\n                (8,   1,  4, _level + 0, None, 2),\n                (9,   2,  3, _level + 1,  8, 2),\n\n                (10, 13, 16, _level + 2,  7, 1),\n                (11, 14, 15, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 3),\n                (13,  2,  5, _level + 1, 12, 3),\n                (14,  3,  4, _level + 2, 13, 3),\n                (15,  6, 11, _level + 1, 12, 3),\n                (16,  7,  8, _level + 2, 15, 3),\n                (17,  9, 10, _level + 2, 15, 3),\n                (18, 12, 21, _level + 1, 12, 3),\n                (19, 13, 16, _level + 2, 18, 3),\n                (20, 14, 15, _level + 3, 19, 3),\n                (21, 17, 20, _level + 2, 18, 3),\n                (22, 18, 19, _level + 3, 21, 3)\n            ],\n            self.result.all()\n        )\n\n    def test_move_to_toplevel2(self):\n        \"\"\" Move node(8) to top level after node(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 8 after 1\n                1                     1(1)18                     1(8)4\n                         _______________|______________            |\n                        |               |              |           |\n                2     2(2)5           6(4)11        12(7)17      2(9)3\n                        |               ^              |\n                3     3(3)4       7(5)8   9(6)10   13(10)16\n                                                       |\n                4                                  14(11)15\n\n                          id lft rgt lvl parent tree\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 8).one()\n        node.parent_id = None\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 18, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 17, _level + 1,  1, 1),\n\n                (8,   1,  4, _level + 0, None, 3),\n                (9,   2,  3, _level + 1,  8, 3),\n\n                (10, 13, 16, _level + 2,  7, 1),\n                (11, 14, 15, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_move_to_toplevel_big_subtree(self):\n        \"\"\" Move node(7) (big subtree) to top level after node(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 7 to toplevel\n                1                     1(1)12             1(7)10\n                         _______________|              ____|____\n                        |               |             |         |\n                2     2(2)5           6(4)11        2(8)5     6(10)9\n                        |               ^             |         |\n                3     3(3)4       7(5)8   9(6)10    3(9)4     7(11)8\n\n                          id lft rgt lvl parent tree\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 7).one()\n        node.parent_id = None\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 12, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n\n                (7,   1, 10, _level + 0, None, 3),\n                (8,   2,  5, _level + 1,  7,   3),\n                (9,   3,  4, _level + 2,  8,   3),\n                (10,  6,  9, _level + 1,  7,   3),\n                (11,  7,  8, _level + 2, 10,   3),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_move_after_between_tree(self):\n        \"\"\" Move node(7) (big subtree) to top level after node(1) and before node(12)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets example\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level               Move 7 to toplevel\n                1                     1(1)12             1(7)10\n                         _______________|              ____|____\n                        |               |             |         |\n                2     2(2)5           6(4)11        2(8)5     6(10)9\n                        |               ^             |         |\n                3     3(3)4       7(5)8   9(6)10    3(9)4     7(11)8\n\n                          id lft rgt lvl parent tree\n        \"\"\"\n        node = self.session.query(self.model).\\\n            filter(self.model.get_pk_column() == 7).one()\n        node.move_after(\"1\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 12, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n\n                (7,   1, 10, _level + 0, None, 2),\n                (8,   2,  5, _level + 1,  7,   2),\n                (9,   3,  4, _level + 2,  8,   2),\n                (10,  6,  9, _level + 1,  7,   2),\n                (11,  7,  8, _level + 2, 10,   2),\n\n                (12,  1, 22, _level + 0, None, 3),\n                (13,  2,  5, _level + 1, 12, 3),\n                (14,  3,  4, _level + 2, 13, 3),\n                (15,  6, 11, _level + 1, 12, 3),\n                (16,  7,  8, _level + 2, 15, 3),\n                (17,  9, 10, _level + 2, 15, 3),\n                (18, 12, 21, _level + 1, 12, 3),\n                (19, 13, 16, _level + 2, 18, 3),\n                (20, 14, 15, _level + 3, 19, 3),\n                (21, 17, 20, _level + 2, 18, 3),\n                (22, 18, 19, _level + 3, 21, 3)\n            ],\n            self.result.all()\n        )\n\n\nclass MoveInside(object):\n\n    def test_move_between_tree(self):\n        \"\"\" Move node(4) to other tree inside node(15)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets tree1\n            1                    1(1)16\n                    _______________|_____________________\n                   |                                     |\n            2    2(2)5                                 6(7)15\n                   |                                     ^\n            3    3(3)4                            7(8)10   11(10)14\n                                                    |          |\n            4                                     8(9)9    12(11)13\n\n            level           Nested sets tree2\n            1                     1(12)28\n                     ________________|_______________________\n                    |                |                       |\n            2    2(13)5            6(15)17                18(18)27\n                   |                 ^                        ^\n            3    3(14)4    7(4)12 13(16)14  15(17)16  19(19)22  23(21)26\n                             ^                            |         |\n            4          8(5)9  10(6)11                 20(20)21  24(22)25\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.parent_id = 15\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 16, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,   1,  1),\n                (3,   3,  4, _level + 2,   2,  1),\n\n                (4,   7, 12, _level + 2,  15, 2),\n                (5,   8,  9, _level + 3,   4, 2),\n                (6,  10, 11, _level + 3,   4, 2),\n\n                (7,   6, 15, _level + 1,   1,  1),\n                (8,   7, 10, _level + 2,   7,  1),\n                (9,   8,  9, _level + 3,   8,  1),\n                (10, 11, 14, _level + 2,   7,  1),\n                (11, 12, 13, _level + 3,  10,  1),\n\n                (12,  1, 28, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 17, _level + 1, 12, 2),\n                (16, 13, 14, _level + 2, 15, 2),\n                (17, 15, 16, _level + 2, 15, 2),\n                (18, 18, 27, _level + 1, 12, 2),\n                (19, 19, 22, _level + 2, 18, 2),\n                (20, 20, 21, _level + 3, 19, 2),\n                (21, 23, 26, _level + 2, 18, 2),\n                (22, 24, 25, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_move_tree_to_another_tree(self):\n        \"\"\" Move tree(2) inside tree(1)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Move tree2 to tree1\n            1                    1(1)44\n                     _______________|_________________________________\n                    |               |                                 |\n            2     2(2)5           6(4)11                            12(7)43\n                    |            ___|___                             __|_____________________________________\n                    |           |       |                           |                             |          |\n            3     3(3)4       7(5)8   9(6)10                    13(12)34                       35(8)38   39(10)42\n                                                    _______________|___________________           |          |\n                                                   |               |                   |       36(9)37   40(11)41\n            4                                   14(13)17        18(15)23             24(18)33\n                                                    |               ^                    ^\n            5                                   15(14)16   19(16)20   21(17)22   25(19)28  29(21)32\n                                                                                     |         |\n            6                                                                    26(20)27  30(22)31\n\n        \"\"\"  # noqa\n        node = self.session.query(self.model).\\\n            filter(self.model.get_pk_column() == 12).one()\n        node.parent_id = 7\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 44, _level + 0, None, 1),\n                (2,   2,  5, _level + 1, 1, 1),\n                (3,   3,  4, _level + 2, 2, 1),\n                (4,   6, 11, _level + 1, 1, 1),\n                (5,   7,  8, _level + 2, 4, 1),\n                (6,   9, 10, _level + 2, 4, 1),\n                (7,  12, 43, _level + 1, 1, 1),\n                (8,  35, 38, _level + 2, 7, 1),\n                (9,  36, 37, _level + 3, 8, 1),\n                (10, 39, 42, _level + 2, 7, 1),\n                (11, 40, 41, _level + 3, 10, 1),\n                (12, 13, 34, _level + 2, 7, 1),\n                (13, 14, 17, _level + 3, 12, 1),\n                (14, 15, 16, _level + 4, 13, 1),\n                (15, 18, 23, _level + 3, 12, 1),\n                (16, 19, 20, _level + 4, 15, 1),\n                (17, 21, 22, _level + 4, 15, 1),\n                (18, 24, 33, _level + 3, 12, 1),\n                (19, 25, 28, _level + 4, 18, 1),\n                (20, 26, 27, _level + 5, 19, 1),\n                (21, 29, 32, _level + 4, 18, 1),\n                (22, 30, 31, _level + 5, 21, 1)\n            ],\n            self.result.all()\n        )\n\n    def test_move_inside_function(self):\n        \"\"\" For example move node(4) inside node(15)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level           Nested sets tree1\n            1                    1(1)16\n                    _______________|_____________________\n                   |                                     |\n            2    2(2)5                                 6(7)15\n                   |                                     ^\n            3    3(3)4                            7(8)10   11(10)14\n                                                    |          |\n            4                                     8(9)9    12(11)13\n\n            level           Nested sets tree2\n            1                     1(12)28\n                     ________________|_______________________\n                    |                |                       |\n            2    2(13)5            6(15)17                18(18)27\n                   |                 ^                        ^\n            3    3(14)4    7(4)12 13(16)14  15(17)16  19(19)22  23(21)26\n                             ^                            |         |\n            4          8(5)9  10(6)11                 20(20)21  24(22)25\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 4).one()\n        node.move_inside(\"15\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 16, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,   1,  1),\n                (3,   3,  4, _level + 2,   2,  1),\n\n                (4,   7, 12, _level + 2,  15, 2),\n                (5,   8,  9, _level + 3,   4, 2),\n                (6,  10, 11, _level + 3,   4, 2),\n\n                (7,   6, 15, _level + 1,   1,  1),\n                (8,   7, 10, _level + 2,   7,  1),\n                (9,   8,  9, _level + 3,   8,  1),\n                (10, 11, 14, _level + 2,   7,  1),\n                (11, 12, 13, _level + 3,  10,  1),\n\n                (12,  1, 28, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 17, _level + 1, 12, 2),\n                (16, 13, 14, _level + 2, 15, 2),\n                (17, 15, 16, _level + 2, 15, 2),\n                (18, 18, 27, _level + 1, 12, 2),\n                (19, 19, 22, _level + 2, 18, 2),\n                (20, 20, 21, _level + 3, 19, 2),\n                (21, 23, 26, _level + 2, 18, 2),\n                (22, 24, 25, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_tree_shorting(self):\n        \"\"\" Try to move top level node(1) inside tree\n\n        .. code::\n\n            level           Nested sets example\n            1                    1(1)22\n                    _______________|___________________\n                   |               |                   |\n            2    2(2)5           6(4)11             12(7)21\n                   |               ^                   ^\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                  |          |\n            4                                  14(9)15   18(11)19\n\n            level           Nested sets example\n\n                                    __parent_id______________________\n                                   |                                 |\n            1                    1(1)22                              |\n                    _______________|___________________              |\n                   |               |                   |             |\n            2    2(2)5           6(4)11             12(7)21         (X)\n                   |               ^                   ^             |\n            3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20    |\n                                                  |          |       |\n            4                                  14(9)15   18(11)19    |\n                                                            ↑        |\n                                                            ↑________|\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 1).one()\n        node.parent_id = 11\n        self.session.add(node)\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   7,  8, _level + 2,  4, 1),\n                (6,   9, 10, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n\n    def test_move_inside_to_the_same_parent_function(self):\n        \"\"\" For example move node(6) inside node(4)\n\n        initial state of the tree :mod:`sqlalchemy_mptt.tests.add_mptt_tree`\n\n        .. code::\n\n            level               Initial state\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(5)8   9(6)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n            level           move 6 inside 4\n                1                    1(1)22\n                        _______________|___________________\n                       |               |                   |\n                2    2(2)5           6(4)11             12(7)21\n                       |               ^                   ^\n                3    3(3)4       7(6)8   9(5)10    13(8)16   17(10)20\n                                                      |          |\n                4                                  14(9)15   18(11)19\n\n        \"\"\"\n        node = self.session.query(self.model)\\\n            .filter(self.model.get_pk_column() == 6).one()\n        node.move_inside(\"4\")\n        _level = node.get_default_level()\n        self.assertEqual(\n            [\n                # id lft rgt lvl parent tree\n                (1,   1, 22, _level + 0, None, 1),\n                (2,   2,  5, _level + 1,  1, 1),\n                (3,   3,  4, _level + 2,  2, 1),\n                (4,   6, 11, _level + 1,  1, 1),\n                (5,   9, 10, _level + 2,  4, 1),\n                (6,   7,  8, _level + 2,  4, 1),\n                (7,  12, 21, _level + 1,  1, 1),\n                (8,  13, 16, _level + 2,  7, 1),\n                (9,  14, 15, _level + 3,  8, 1),\n                (10, 17, 20, _level + 2,  7, 1),\n                (11, 18, 19, _level + 3, 10, 1),\n\n                (12,  1, 22, _level + 0, None, 2),\n                (13,  2,  5, _level + 1, 12, 2),\n                (14,  3,  4, _level + 2, 13, 2),\n                (15,  6, 11, _level + 1, 12, 2),\n                (16,  7,  8, _level + 2, 15, 2),\n                (17,  9, 10, _level + 2, 15, 2),\n                (18, 12, 21, _level + 1, 12, 2),\n                (19, 13, 16, _level + 2, 18, 2),\n                (20, 14, 15, _level + 3, 19, 2),\n                (21, 17, 20, _level + 2, 18, 2),\n                (22, 18, 19, _level + 3, 21, 2)\n            ],\n            self.result.all()\n        )\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/fixtures/tmp_tree.json",
    "content": "[\n    {\n        \"id\": \"1\",\n        \"parent_id\": null\n    },\n    {\n        \"id\": \"2\",\n        \"parent_id\": \"1\"\n    },\n    {\n        \"id\": \"3\",\n        \"parent_id\": \"1\"\n    },\n    {\n        \"id\": \"4\",\n        \"parent_id\": \"1\"\n    },\n    {\n        \"id\": \"5\",\n        \"parent_id\": \"2\"\n    },\n    {\n        \"id\": \"6\",\n        \"parent_id\": \"2\"\n    },\n    {\n        \"id\": \"7\",\n        \"parent_id\": \"4\"\n    },\n    {\n        \"id\": \"8\",\n        \"parent_id\": \"4\"\n    },\n    {\n        \"id\": \"9\",\n        \"parent_id\": \"4\"\n    },\n    {\n        \"id\": \"10\",\n        \"parent_id\": \"5\"\n    },\n    {\n        \"id\": \"11\",\n        \"parent_id\": \"6\"\n    },\n    {\n        \"id\": \"12\",\n        \"parent_id\": \"6\"\n    },\n    {\n        \"id\": \"13\",\n        \"parent_id\": \"10\"\n    },\n    {\n        \"id\": \"14\",\n        \"parent_id\": \"10\"\n    }\n]\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/fixtures/tree.json",
    "content": "[\n    {\n        \"parent_id\": null,\n        \"id\": \"1\"\n    },\n    {\n        \"parent_id\": \"1\",\n        \"id\": \"2\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"2\",\n        \"id\": \"3\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"1\",\n        \"id\": \"4\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"4\",\n        \"id\": \"5\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"4\",\n        \"id\": \"6\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"1\",\n        \"id\": \"7\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"7\",\n        \"id\": \"8\",\n        \"visible\": 1\n    },\n    {\n        \"parent_id\": \"8\",\n        \"id\": \"9\"\n    },\n    {\n        \"parent_id\": \"7\",\n        \"id\": \"10\"\n    },\n    {\n        \"parent_id\": \"10\",\n        \"id\": \"11\"\n    },\n    {\n        \"parent_id\": null,\n        \"id\": \"12\"\n    },\n    {\n        \"parent_id\": \"12\",\n        \"id\": \"13\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"13\",\n        \"id\": \"14\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"12\",\n        \"id\": \"15\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"15\",\n        \"id\": \"16\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"15\",\n        \"id\": \"17\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"12\",\n        \"id\": \"18\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"18\",\n        \"id\": \"19\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"19\",\n        \"id\": \"20\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"18\",\n        \"id\": \"21\",\n        \"tree_id\": \"2\"\n    },\n    {\n        \"parent_id\": \"21\",\n        \"id\": \"22\",\n        \"tree_id\": \"2\"\n    }\n]\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/fixtures/tree_3.json",
    "content": "[\n    {\n        \"parent_id\": null,\n        \"id\": 23,\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"23\",\n        \"id\": \"24\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"24\",\n        \"id\": \"25\"\n    },\n    {\n        \"parent_id\": \"23\",\n        \"id\": \"26\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"26\",\n        \"id\": \"27\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"26\",\n        \"id\": 28,\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"23\",\n        \"id\": \"29\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"29\",\n        \"id\": \"30\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"30\",\n        \"id\": \"31\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"29\",\n        \"id\": \"32\",\n        \"tree_id\": \"3\"\n    },\n    {\n        \"parent_id\": \"32\",\n        \"id\": \"33\",\n        \"tree_id\": \"3\"\n    }\n]\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/test_events.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2014 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\n\n\"\"\"\ntest tree\n\"\"\"\n\nimport unittest\n\nfrom sqlalchemy import Boolean, Column, Integer\nfrom sqlalchemy.event import contains\n\nfrom sqlalchemy_mptt.mixins import BaseNestedSets\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\nfrom sqlalchemy_mptt.tests import DatabaseSetupMixin, TreeTestingMixin\n\nBase = compat_layer.declarative_base()\n\n\nclass Tree(Base, BaseNestedSets):\n    __tablename__ = \"tree\"\n\n    id = Column(Integer, primary_key=True)\n    visible = Column(Boolean)\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.id\n\n\nclass TreeWithCustomId(Base, BaseNestedSets):\n    __tablename__ = \"tree2\"\n\n    ppk = Column('idd', Integer, primary_key=True)\n    visible = Column(Boolean)\n\n    sqlalchemy_mptt_pk_name = 'ppk'\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.ppk\n\n\nclass TreeWithCustomLevel(Base, BaseNestedSets):\n    __tablename__ = \"tree_custom_level\"\n\n    id = Column(Integer, primary_key=True)\n    visible = Column(Boolean)\n\n    sqlalchemy_mptt_default_level = 0\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.id\n\n\nclass TestTree(TreeTestingMixin, unittest.TestCase):\n    base = Base\n    model = Tree\n\n\nclass TestTreeWithCustomId(TreeTestingMixin, unittest.TestCase):\n    base = Base\n    model = TreeWithCustomId\n\n\nclass TestTreeWithCustomLevel(TreeTestingMixin, unittest.TestCase):\n    base = Base\n    model = TreeWithCustomLevel\n\n\nclass Events(unittest.TestCase):\n\n    def test_register(self):\n        from sqlalchemy_mptt import tree_manager\n        tree_manager.register_events()\n        self.assertTrue(\n            contains(\n                BaseNestedSets,\n                'before_insert',\n                tree_manager.before_insert\n            )\n        )\n        self.assertTrue(\n            contains(\n                BaseNestedSets,\n                'before_update',\n                tree_manager.before_update\n            )\n        )\n        self.assertTrue(\n            contains(\n                BaseNestedSets,\n                'before_delete',\n                tree_manager.before_delete\n            )\n        )\n\n    def test_register_and_remove(self):\n        from sqlalchemy_mptt import tree_manager\n        tree_manager.register_events()\n        tree_manager.register_events(remove=True)\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_insert',\n                tree_manager.before_insert\n            )\n        )\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_update',\n                tree_manager.before_update\n            )\n        )\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_delete',\n                tree_manager.before_delete\n            )\n        )\n        tree_manager.register_events()\n\n    def test_remove(self):\n        from sqlalchemy_mptt import tree_manager\n        tree_manager.register_events(remove=True)\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_insert',\n                tree_manager.before_insert\n            )\n        )\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_update',\n                tree_manager.before_update\n            )\n        )\n        self.assertFalse(\n            contains(\n                Tree,\n                'before_delete',\n                tree_manager.before_delete\n            )\n        )\n        tree_manager.register_events()\n\n\nclass Tree0Id(DatabaseSetupMixin, unittest.TestCase):\n    \"\"\"Test case where node id is provided and starts with 0\n\n    See comments in https://github.com/uralbash/sqlalchemy_mptt/issues/57\n    \"\"\"\n\n    base = Base\n\n    def test(self):\n        root = Tree(id=0)\n        child = Tree(id=1, parent_id=0)\n\n        self.session.add(root)\n        self.session.add(child)\n        self.session.commit()\n\n        self.assertEqual(root.tree_id, 1)\n        self.assertEqual(child.tree_id, 1)\n\n\nclass InitialInsert(DatabaseSetupMixin, unittest.TestCase):\n    \"\"\"Test case for initial insertion of node as specified in\n    docs/initialize.rst\n    \"\"\"\n\n    base = Base\n\n    def test_documented_initial_insert(self):\n        from sqlalchemy_mptt import tree_manager\n\n        tree_manager.register_events(remove=True)  # Disable MPTT events\n\n        _tree_id = 1\n\n        for node_id, parent_id in [(1, None), (2, 1), (3, 1), (4, 2)]:\n            item = Tree(\n                id=node_id,\n                parent_id=parent_id,\n                left=0,\n                right=0,\n                tree_id=_tree_id\n            )\n            self.session.add(item)\n        self.session.commit()\n\n        tree_manager.register_events()  # enabled MPTT events back\n        Tree.rebuild_tree(\n            self.session,\n            _tree_id\n        )  # rebuild lft, rgt value automatically\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/test_inheritance.py",
    "content": "import unittest\n\nimport sqlalchemy as sa\n\nfrom sqlalchemy_mptt.mixins import BaseNestedSets\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\nfrom sqlalchemy_mptt.tests import (DatabaseSetupMixin, TreeTestingMixin,\n                                   failures_expected_on)\n\nBase = compat_layer.declarative_base()\n\n\nclass GenericTree(Base, BaseNestedSets):\n    __tablename__ = \"generic\"\n\n    ppk = sa.Column('idd', sa.Integer, primary_key=True)\n    type = sa.Column(sa.Integer, default=0)\n    visible = sa.Column(sa.Boolean)\n\n    sqlalchemy_mptt_pk_name = 'ppk'\n\n    __mapper_args__ = {\n        'polymorphic_identity': 0,\n        'polymorphic_on': type,\n    }\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.ppk\n\n\nclass SpecializedTree(GenericTree):\n    __tablename__ = \"specialized\"\n\n    ppk = sa.Column(\n        'idd',\n        sa.Integer,\n        sa.ForeignKey(GenericTree.ppk),\n        primary_key=True\n    )\n\n    __mapper_args__ = {\n        'polymorphic_identity': 1,\n    }\n    __table_args__ = tuple()\n\n\nclass TestTree(DatabaseSetupMixin, unittest.TestCase):\n\n    base = Base\n\n    def test_create_generic(self):\n        self.session.add(GenericTree(ppk=1))\n        self.session.commit()\n\n        tree = compat_layer.get(self.session, GenericTree, 1)\n        self.assertEqual(tree.ppk, 1)\n        self.assertEqual(tree.tree_id, 1)\n\n    def test_create_spec(self):\n        self.session.add(SpecializedTree(ppk=1))\n        self.session.commit()\n\n        tree = compat_layer.get(self.session, SpecializedTree, 1)\n        self.assertEqual(tree.ppk, 1)\n        self.assertEqual(tree.tree_id, 1)\n\n    def test_create_delete(self):\n        parent = SpecializedTree(ppk=1)\n\n        child1 = SpecializedTree(ppk=2, parent=parent)\n        child2 = GenericTree(ppk=3, parent=parent)\n\n        GenericTree(ppk=4, parent=child2)\n        SpecializedTree(ppk=5, parent=child2)\n\n        self.session.add(parent)\n        self.session.commit()\n\n        tree = compat_layer.get(self.session, SpecializedTree, 1)\n        self.assertEqual(tree.ppk, 1)\n        self.assertEqual(tree.tree_id, 1)\n\n        self.session.delete(child1)\n        self.session.commit()\n\n        self.assertEqual(None, compat_layer.get(self.session, SpecializedTree, 2))\n\n        self.session.delete(child2)\n        self.session.commit()\n\n        self.assertEqual(None, compat_layer.get(self.session, SpecializedTree, 3))\n        self.assertEqual(None, compat_layer.get(self.session, SpecializedTree, 4))\n        self.assertEqual(None, compat_layer.get(self.session, SpecializedTree, 5))\n\n\nclass TestGenericTree(TreeTestingMixin, unittest.TestCase):\n    base = Base\n    model = GenericTree\n\n\nclass TestSpecializedTree(TreeTestingMixin, unittest.TestCase):\n    base = Base\n    model = SpecializedTree\n\n    @unittest.expectedFailure\n    def test_rebuild(self):\n        # This test will always fail on specialized classes.\n        super().test_rebuild()\n\n\nBase2 = compat_layer.declarative_base()\n\n\nclass BaseInheritance(Base2):\n    __tablename__ = \"base_inheritance\"\n\n    ppk = sa.Column('idd', sa.Integer, primary_key=True)\n    type = sa.Column(sa.Integer, default=0)\n    visible = sa.Column(sa.Boolean)\n\n    __mapper_args__ = {\n        'polymorphic_identity': 0,\n        'polymorphic_on': type,\n    }\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.ppk\n\n\nclass InheritanceTree(BaseInheritance, BaseNestedSets):\n    __tablename__ = \"inheriance_tree\"\n\n    ppk = sa.Column('idd', sa.Integer, sa.ForeignKey(BaseInheritance.ppk),\n                    primary_key=True)\n    sqlalchemy_mptt_pk_name = 'ppk'\n\n    __mapper_args__ = {\n        'polymorphic_identity': 1,\n    }\n\n\nclass TestInheritanceTree(TreeTestingMixin, unittest.TestCase):\n    base = Base2\n    model = InheritanceTree\n\n    @failures_expected_on(sqlalchemy_versions=['1.0', '1.1', '1.2', '1.3'])\n    def test_rebuild(self):\n        super().test_rebuild()\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/test_mixins.py",
    "content": "#! /usr/bin/env python\n# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright © 2014 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\n\n\"\"\"\ntest tree\n\"\"\"\nimport unittest\n\nfrom sqlalchemy import Column, Integer\n\nfrom sqlalchemy_mptt.mixins import BaseNestedSets\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\n\n\nBase = compat_layer.declarative_base()\n\n\nclass Tree2(Base, BaseNestedSets):\n    __tablename__ = \"tree2\"\n\n    id = Column(Integer, primary_key=True)\n\n\nclass TestMixin(unittest.TestCase):\n    def test_mixin_parent_id(self):\n        self.assertEqual(\n            Tree2.parent_id.__class__.__name__,\n            'InstrumentedAttribute'\n        )\n"
  },
  {
    "path": "sqlalchemy_mptt/tests/test_stateful.py",
    "content": "# -*- coding: utf-8 -*-\n# vim:fenc=utf-8\n#\n# Copyright (c) 2025 Fayaz Yusuf Khan <fayaz.yusuf.khan@gmail.com>\n#\n# Distributed under terms of the MIT license.\n\"\"\"Test cases written using Hypothesis stateful testing framework.\"\"\"\nfrom hypothesis import HealthCheck, settings\nfrom hypothesis import strategies as st\nfrom hypothesis.stateful import (Bundle, RuleBasedStateMachine, consumes,\n                                 invariant, rule)\nfrom sqlalchemy import Boolean, Column, Integer\nfrom sqlalchemy.orm import joinedload\n\nfrom sqlalchemy_mptt import BaseNestedSets\nfrom sqlalchemy_mptt.sqlalchemy_compat import compat_layer\nfrom sqlalchemy_mptt.tests import DatabaseSetupMixin\n\nBase = compat_layer.declarative_base()\n\n\nclass Tree(Base, BaseNestedSets):\n    __tablename__ = \"tree\"\n\n    id = Column(Integer, primary_key=True)\n    visible = Column(Boolean)\n\n    def __repr__(self):\n        return \"<Node (%s)>\" % self.id\n\n\nclass TreeStateMachine(DatabaseSetupMixin, RuleBasedStateMachine):\n    \"\"\"A state machine with various possible actions and transitions for the Tree model.\"\"\"\n\n    base = Base\n\n    def __init__(self):\n        super().__init__()\n        self.setUp()\n\n    def teardown(self):\n        super().teardown()\n        self.tearDown()\n\n    node = Bundle('node')\n\n    @rule(target=node, visible=st.none() | st.booleans())\n    def add_root_node(self, visible):\n        node = Tree(visible=visible)\n        self.session.add(node)\n        self.session.commit()\n        assert node.left < node.right\n        return node\n\n    @rule(node=consumes(node))\n    def delete_node(self, node):\n        # Consume all descendants of the node\n        for name, value in list(self.names_to_values.items()):\n            if value not in self.session or node.is_ancestor_of(value):\n                for var_reference in self.bundles[\"node\"][:]:\n                    if var_reference.name == name:\n                        self.bundles[\"node\"].remove(var_reference)\n                # Remove the object as well for garbage collection\n                del self.names_to_values[name]\n        self.session.delete(node)\n        self.session.commit()\n\n    @rule(target=node, node=node, visible=st.none() | st.booleans())\n    def add_child(self, node, visible):\n        # Avoid cascade_backrefs here since it is deprecated.\n        child = Tree(visible=visible)\n        node.children.append(child)\n        self.session.commit()\n        assert node.left < child.left < child.right < node.right\n        return child\n\n    @invariant()\n    def check_get_tree_integrity(self):\n        \"\"\"Check that get_tree response is valid after each operation.\"\"\"\n        response = Tree.get_tree(\n            self.session,\n            query=lambda x: x.execution_options(populate_existing=True).options(joinedload(Tree.children)))\n        assert isinstance(response, list)\n        for node in response:\n            validate_get_tree_node(node)\n\n    @invariant()\n    def check_get_tree_with_custom_query(self):\n        \"\"\"Check that get_tree response is valid with custom queries.\"\"\"\n        for visible in [None, True, False]:\n            response = Tree.get_tree(\n                self.session,\n                query=lambda x: x.filter_by(visible=visible)\n                    .execution_options(populate_existing=True).options(joinedload(Tree.children)))\n            assert isinstance(response, list)\n            for node in response:\n                validate_get_tree_node_for_custom_query(node)\n\n\ndef validate_get_tree_node(node_response, level=1):\n    \"\"\"Validate the structure of a node response from get_tree.\"\"\"\n    node = node_response['node']\n    assert node.level == level\n    if len(node.children):\n        assert 'children' in node_response.keys()\n        children_response = node_response['children']\n        assert len(node.children) == len(children_response)\n        for child, child_response in zip(node.children, children_response):\n            assert child == child_response['node']\n            validate_get_tree_node(child_response, level=level + 1)\n\n\ndef validate_get_tree_node_for_custom_query(node_response):\n    \"\"\"Validate the structure of a node response from get_tree with custom query.\"\"\"\n    node = node_response['node']\n    if 'children' in node_response.keys():\n        for child_response in node_response['children']:\n            assert child_response['node'].parent == node\n            validate_get_tree_node_for_custom_query(child_response)\n\n\n# Export the stateful test case\nTestTreeStates = TreeStateMachine.TestCase\nTestTreeStates.settings = settings(\n    max_examples=75, stateful_step_count=25, suppress_health_check=[HealthCheck.too_slow]\n)\n"
  },
  {
    "path": "test.sh",
    "content": "#! /bin/bash\n#\n# test.sh\n# Copyright (C) 2015 uralbash <root@uralbash.ru>\n#\n# Distributed under terms of the MIT license.\n#\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nNC='\\033[0m' # No Color\nPROJECT_NAME='sqlalchemy_mptt'\n\nRST_FILES=`find . -name \"*.rst\" -printf \"%p \"`\nRST_CHECK=$(rstcheck $RST_FILES               \\\n              --ignore-directives code-block  \\\n              --report 2 3>&1 1>&2 2>&3 | tee >(cat - >&2)) # fd=STDERR_FILENO\nFLAKE8=$(flake8 ./$PROJECT_NAME/)\n\necho -e \"${RED}\"\n# if [ -n \"$RST_CHECK\" ] ||\nif [ -n \"$FLAKE8\" ]\nthen\n    echo -e \"RST_CHECK: ${RST_CHECK:-OK}\"\n    echo -e \"FLAKE8: ${FLAKE8:-OK}\"\n    exit 1\nelse\n    echo -e \"${GREEN}OK!\"\nfi\necho -e \"${NC}\"\n"
  }
]