[
  {
    "path": ".cirrus.yml",
    "content": "task:\n  matrix:\n    freebsd_instance:\n      image_family: freebsd-13-0\n    freebsd_instance:\n      image_family: freebsd-12-2\n      \n  install_script:\n    - pkg install -y python39 py39-sqlite3\n    # Print the Python version, only to be sure we are running the version we want\n    - python3.9 -c 'import platform; print(\"Python\", platform.python_version())'\n    # Check SQLite3 is installed\n    - python3.9 -c 'import sqlite3; print(\"SQLite3\", sqlite3.version)'\n  setup_script:\n    - python3.9 -m ensurepip\n    - python3.9 -m pip install -U pip\n    - python3.9 -m pip install -r requirements-tests.txt\n  lint_script:\n    - python3.9 -m ruff src\n  tests_script:\n    - python3.9 -bb -m pytest tests\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Language aware diff headers\n# https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more\n# https://gist.github.com/tekin/12500956bd56784728e490d8cef9cb81\n# https://github.com/git/git/blob/master/userdiff.c\n*.c diff=cpp\n*.py diff=python\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [BoboTiG]\npolar: tiger-222\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # GitHub Actions\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n"
  },
  {
    "path": ".github/workflows/build-and-publish.yml",
    "content": "# Because this library provides extension modules for macOS, but not for other\n# platforms, we want to provide built distributions for each macOS platform, but we\n# explicitly DON'T want to provide a cross-platform pure-Python wheel to fall back on.\n#\n# This is because in the event that a new Python version is released or a new\n# macOS platform is released, macOS users won't be able to install the built\n# distributions we've provided and should fall back to the source distribution,\n# but pip's behavior is to prefer a pure-Python wheel first, which will be\n# missing the extension modules.\n#\n# However, to provide built distributions for Linux and Windows (which don't\n# have extension modules) we can just build a pure-Python wheel on that\n# platform and override the platform name manually via wheel's --plat-name\n# feature, to provide a platform-specific wheel for all platforms.\n\nname: Build & Publish\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - \"**\"\n\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n\nconcurrency:\n  group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name != 'pull_request' && github.sha || '' }}\n  cancel-in-progress: true\n\njobs:\n  macos-built-distributions:\n    name: Build macOS wheels\n    runs-on: macos-latest\n    timeout-minutes: 20\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.event.inputs.branch }}\n      - name: Install Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - name: Install build dependencies\n        run: python -m pip install cibuildwheel\n      - name: Build wheels\n        run: python -m cibuildwheel\n        env:\n          CIBW_ARCHS_MACOS: \"x86_64 universal2 arm64\"\n      - name: Artifacts list\n        run: ls -l wheelhouse\n      - uses: actions/upload-artifact@v7\n        with:\n          name: python-package-distributions-macos\n          path: ./wheelhouse/*.whl\n\n  pure-built-distributions:\n    name: Build pure wheels\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.event.inputs.branch }}\n      - name: Install Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - name: Install build dependencies\n        run: python -m pip install -U setuptools wheel\n      - name: Build wheels\n        run: |\n          for platform in 'manylinux_2_17_x86_64' 'manylinux_2_17_aarch64' 'manylinux_2_17_i686' 'manylinux_2_17_ppc64le' 'manylinux_2_17_s390x' 'manylinux_2_17_armv7l' 'manylinux_2_17_ppc64' 'manylinux2014_x86_64' 'manylinux2014_i686' 'manylinux2014_aarch64' 'manylinux2014_armv7l' 'manylinux2014_ppc64' 'manylinux2014_ppc64le' 'manylinux2014_s390x' 'win32' 'win_amd64' 'win_ia64'\n          do\n            python setup.py bdist_wheel --plat-name $platform\n          done\n      - name: Artifacts list\n        run: ls -l dist\n      - uses: actions/upload-artifact@v7\n        with:\n          name: python-package-distributions-pure-wheels\n          path: ./dist/*.whl\n\n  source-distribution:\n    name: Build source distribution\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.event.inputs.branch }}\n      - name: Install Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - name: Build source distribution\n        run: python setup.py sdist\n      - name: Artifacts list\n        run: ls -l dist\n      - name: Store the source distribution\n        uses: actions/upload-artifact@v7\n        with:\n          name: python-package-distributions-source\n          path: dist/*.tar.gz\n\n  publish:\n    needs:\n      - macos-built-distributions\n      - pure-built-distributions\n      - source-distribution\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n      - name: Download all the dists\n        uses: actions/download-artifact@v8\n        with:\n          pattern: python-package-distributions-*\n          merge-multiple: true\n          path: dist/\n      - name: What will we publish?\n        run: ls -l dist\n      - name: Publish\n        if: github.event.inputs.branch != ''\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n          skip-existing: true\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name != 'pull_request' && github.sha || '' }}\n  cancel-in-progress: true\n\njobs:\n  quality:\n    name: 🧑‍🏭 Quality & Docs\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.13\"\n          cache: pip\n\n      - name: Install dependencies\n        run: python -m pip install tox\n\n      - name: Run linters\n        run: python -m tox -q -e types,lint\n\n      - name: Build the documentation\n        run: python -m tox -q -e docs\n\n  tox:\n    name: ${{ matrix.tox.name }} ${{ matrix.os.emoji }} ${{ matrix.os.name }} ${{ matrix.python }}\n    runs-on: ${{ matrix.os.runs-on }}\n    timeout-minutes: 15\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n          - name: Linux\n            matrix: linux\n            emoji: 🐧\n            runs-on: [ubuntu-latest]\n          - name: macOS\n            matrix: macos\n            emoji: 🍎\n            runs-on: [macos-latest]\n          - name: Windows\n            matrix: windows\n            emoji: 🪟\n            runs-on: [windows-latest]\n        python:\n          - \"3.9\"\n          - \"3.10\"\n          - \"3.11\"\n          - \"3.12\"\n          - \"3.13\"\n          - \"3.13t\"\n          - \"3.14\"\n          - \"3.14t\"\n          - \"pypy-3.9\"\n        exclude:\n          - os:\n              matrix: macos\n            python: \"pypy-3.9\"\n          - os:\n              matrix: windows\n            python: \"pypy-3.9\"\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Set up Python ${{ matrix.python }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python }}\n          allow-prereleases: true\n          cache: pip\n\n      - name: Install dependencies\n        run: python -m pip install tox\n\n      - name: Run tests\n        run: python -m tox -q -e py\n"
  },
  {
    "path": ".gitignore",
    "content": "# Ignore temporary files.\n*.bak\n*.bkp\n*.log\n*.py[co]\n*.swp\n*~\n.DS_Store\n.\\#*\n._*\n*.o\n*.so\nDesktop.ini\nThumbs.db\n\\#*\\#\n__MACOSX__\n\n# Ignore generated files and directories.\n*.egg-info/\n*.egg\n.installed.cfg\nbuild/\ndevelop-eggs/\ndist/\neggs/\nparts/\n__pycache__/\nMANIFEST\n\n# Project files for VS Code, idea, eclipse, and netbeans\nnbproject/\n.idea/\n.settings/\n.vscode/\n\n# Generated by tests.\n.coverage\n.coverage.*\nhtmlcov/\n.tox/\n.cache/\n.pytest_cache/\n\n# From virtualenv.\ninclude/\nlib/\n/bootstrap.py\nvenv/\n.venv/\n"
  },
  {
    "path": ".well-known/funding-manifest-urls",
    "content": "https://www.tiger-222.fr/funding.json\n"
  },
  {
    "path": "AUTHORS",
    "content": "Original Project Lead:\n----------------------\nYesudeep Mangalapilly <yesudeep@google.com>\n\nCurrent Project Lead:\n---------------------\nMickaël Schoentgen <contact@tiger-222.fr>\n\nContributors in alphabetical order:\n-----------------------------------\nAdrian Tejn Kern <tejnkern@gmail.com>\nAndrew Schaaf <andrew@andrewschaaf.com>\nDanilo de Jesus da Silva Bellini <danilo.bellini@gmail.com>\nDavid LaPalomento <dlapalomento@gmail.com>\ndvogel <dvogel@wlscapi.uwsc.wisc.edu>\nFilip Noetzel <filip@j03.de>\nGary van der Merwe <garyvdm@garyvdm.localdomain>\ngfxmonk <tim3d.junk@gmail.com>\nGora Khargosh <gora.khargosh@gmail.com>\nHannu Valtonen <hannu.valtonen@ohmu.fi>\nJesse Printz <jesse@jonypawks.net>\nKurt McKee <contactme@kurtmckee.org>\nLéa Klein <lklein@nuxeo.com>\nLuke McCarthy <luke@iogopro.co.uk>\nLukáš Lalinský <lalinsky@gmail.com>\nMalthe Borch <mborch@gmail.com>\nMartin Kreichgauer <kreichgauer@gmail.com>\nMartin Kreichgauer <martin@kreichgauer.com>\nMike Lundy <mike@fluffypenguin.org>\nNicholas Hairs <info+watchdog@nicholashairs.com>\nRaymond Hettinger <python@rcn.com>\nRoman Ovchinnikov <coolthecold@gmail.com>\nRotem Yaari <vmalloc@gmail.com>\nRyan Kelly <ryan@rfk.id.au>\nSenko Rasic <senko.rasic@dobarkod.hr>\nSenko Rašić <senko@senko.net>\nShane Hathaway <shane@hathawaymix.org>\nSimon Pantzare <simon@pewpewlabs.com>\nSimon Pantzare <simpa395@student.liu.se>\nSteven Samuel Cole <steven.samuel.cole@gmail.com>\nStéphane Klein <stephane@harobed.org>\nThomas Guest <tag@wordaligned.org>\nThomas Heller <theller@ctypes.org>\nTim Cuthbertson <tim+github@gfxmonk.net>\nTodd Whiteman <toddw@activestate.com>\nWill McGugan <will@willmcgugan.com>\nYesudeep Mangalapilly <gora.khargosh@gmail.com>\nYesudeep Mangalapilly <yesudeep@google.com>\n\nWe would like to thank these individuals for ideas:\n---------------------------------------------------\nTim Golden               <mail@timgolden.me.uk>\nSebastien Martini        <seb@dbzteam.org>\n\nInitially we used the flask theme for the documentation which was written by\n----------------------------------------------------------------------------\nArmin Ronacher           <armin.ronacher@active-4.com>\n\n\nWatchdog also includes open source libraries or adapted code\nfrom the following projects:\n\n- MacFSEvents - https://github.com/malthe/macfsevents\n- watch_directory.py - http://timgolden.me.uk/python/downloads/watch_directory.py\n- pyinotify - https://github.com/seb-m/pyinotify\n- fsmonitor - https://github.com/shaurz/fsmonitor\n- echo - http://wordaligned.org/articles/echo\n- Lukáš Lalinský's ordered set queue implementation:\n  https://stackoverflow.com/questions/1581895/how-check-if-a-task-is-already-in-python-queue\n- Armin Ronacher's flask-sphinx-themes for the documentation:\n  https://github.com/mitsuhiko/flask-sphinx-themes\n- pyfilesystem - https://github.com/PyFilesystem/pyfilesystem\n- get_FILE_NOTIFY_INFORMATION - http://blog.gmane.org/gmane.comp.python.ctypes/month=20070901\n"
  },
  {
    "path": "COPYING",
    "content": "Copyright 2018-2025 Mickaël Schoentgen & contributors\nCopyright 2014-2018 Thomas Amland & contributors\nCopyright 2012-2014 Google, Inc.\nCopyright 2011-2012 Yesudeep Mangalapilly\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude changelog.rst\ninclude LICENSE\ninclude COPYING\ninclude AUTHORS\nrecursive-include src *.py *.h *.c\ninclude src/watchdog/py.typed\ninclude tox.ini\ninclude docs/*.txt\ninclude docs/*.xml\ninclude docs/Makefile\ninclude docs/make.bat\ninclude requirements-tests.txt\nrecursive-include docs/source *\nrecursive-include tests *.py\n#global-exclude .DS_Store\n#global-exclude Thumbs.db\n#global-exclude Desktop.ini\n#global-exclude *.swp\n#global-exclude *~\n#global-exclude *.bak\n"
  },
  {
    "path": "README.rst",
    "content": "Watchdog\n========\n\n|PyPI Version|\n|PyPI Status|\n|PyPI Python Versions|\n|GitHub Build Status|\n|GitHub License|\n\n.. tip::\n\n    Become **my boss** to help me work on this awesome software, and make the world better:\n\n   |Patreon|\n\nPython API and shell utilities to monitor file system events.\n\nWorks on 3.9+.\n\nExample API Usage\n-----------------\n\nA simple program that uses watchdog to monitor directories specified\nas command-line arguments and logs events generated:\n\n.. code-block:: python\n\n    import time\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n    from watchdog.observers import Observer\n\n\n    class MyEventHandler(FileSystemEventHandler):\n        def on_any_event(self, event: FileSystemEvent) -> None:\n            print(event)\n\n\n    event_handler = MyEventHandler()\n    observer = Observer()\n    observer.schedule(event_handler, \".\", recursive=True)\n    observer.start()\n    try:\n        while True:\n            time.sleep(1)\n    finally:\n        observer.stop()\n        observer.join()\n\nAlternatively, you can use the observer as a context manager for cleaner code:\n\n.. code-block:: python\n\n    import time\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n    from watchdog.observers import Observer\n\n\n    class MyEventHandler(FileSystemEventHandler):\n        def on_any_event(self, event: FileSystemEvent) -> None:\n            print(event)\n\n\n    event_handler = MyEventHandler()\n    observer = Observer()\n    observer.schedule(event_handler, \".\", recursive=True)\n\n    with observer:\n        while True:\n            time.sleep(1)\n\n\nShell Utilities\n---------------\n\nWatchdog comes with an *optional* utility script called ``watchmedo``.\nPlease type ``watchmedo --help`` at the shell prompt to\nknow more about this tool.\n\nHere is how you can log the current directory recursively\nfor events related only to ``*.py`` and ``*.txt`` files while\nignoring all directory events:\n\n.. code-block:: bash\n\n    watchmedo log \\\n        --patterns='**/*.py;**/*.txt' \\\n        --ignore-directories \\\n        --recursive \\\n        --verbose \\\n        .\n\nYou can use the ``shell-command`` subcommand to execute shell commands in\nresponse to events:\n\n.. code-block:: bash\n\n    watchmedo shell-command \\\n        --patterns='**/*.py;**/*.txt' \\\n        --recursive \\\n        --command='echo \"${watch_src_path}\"' \\\n        .\n\nPlease see the help information for these commands by typing:\n\n.. code-block:: bash\n\n    watchmedo [command] --help\n\n\nAbout ``watchmedo`` Tricks\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``watchmedo`` can read ``tricks.yaml`` files and execute tricks within them in\nresponse to file system events. Tricks are actually event handlers that\nsubclass ``watchdog.tricks.Trick`` and are written by plugin authors. Trick\nclasses are augmented with a few additional features that regular event handlers\ndon't need.\n\nAn example ``tricks.yaml`` file:\n\n.. code-block:: yaml\n\n    tricks:\n    - watchdog.tricks.LoggerTrick:\n        patterns: [\"**/*.py\", \"**/*.js\"]\n    - watchmedo_webtricks.GoogleClosureTrick:\n        patterns: ['**/*.js']\n        hash_names: true\n        mappings_format: json                  # json|yaml|python\n        mappings_module: app/javascript_mappings\n        suffix: .min.js\n        compilation_level: advanced            # simple|advanced\n        source_directory: app/static/js/\n        destination_directory: app/public/js/\n        files:\n          index-page:\n          - app/static/js/vendor/jquery*.js\n          - app/static/js/base.js\n          - app/static/js/index-page.js\n          about-page:\n          - app/static/js/vendor/jquery*.js\n          - app/static/js/base.js\n          - app/static/js/about-page/**/*.js\n\nThe directory containing the ``tricks.yaml`` file will be monitored. Each trick\nclass is initialized with its corresponding keys in the ``tricks.yaml`` file as\narguments and events are fed to an instance of this class as they arrive.\n\nInstallation\n------------\nInstall from PyPI using ``pip``:\n\n.. code-block:: bash\n\n    $ python -m pip install -U watchdog\n\n    # or to install the watchmedo utility:\n    $ python -m pip install -U 'watchdog[watchmedo]'\n\nInstall from source:\n\n.. code-block:: bash\n\n    $ python -m pip install -e .\n\n    # or to install the watchmedo utility:\n    $ python -m pip install -e '.[watchmedo]'\n\n\nDocumentation\n-------------\n\nYou can browse the latest release documentation_ online.\n\nContribute\n----------\n\nFork the `repository`_ on GitHub and send a pull request, or file an issue\nticket at the `issue tracker`_. For general help and questions use\n`stackoverflow`_ with tag `python-watchdog`.\n\nCreate and activate your virtual environment, then::\n\n    python -m pip install tox\n    python -m tox [-q] [-e ENV]\n\nIf you are making a substantial change, add an entry to the \"Unreleased\" section\nof the `changelog`_.\n\nSupported Platforms\n-------------------\n\n* Linux 2.6 (inotify)\n* macOS (FSEvents, kqueue)\n* FreeBSD/BSD (kqueue)\n* Windows (ReadDirectoryChangesW with I/O completion ports;\n  ReadDirectoryChangesW worker threads)\n* OS-independent (polling the disk for directory snapshots and comparing them\n  periodically; slow and not recommended)\n\nNote that when using watchdog with kqueue, you need the\nnumber of file descriptors allowed to be opened by programs\nrunning on your system to be increased to more than the\nnumber of files that you will be monitoring. The easiest way\nto do that is to edit your ``~/.profile`` file and add\na line similar to::\n\n    ulimit -n 1024\n\nor::\n\n    ulimit -n unlimited\n\nThis is an inherent problem with kqueue because it uses\nfile descriptors to monitor files. That plus the enormous\namount of bookkeeping that watchdog needs to do in order\nto monitor file descriptors just makes this a painful way\nto monitor files and directories. In essence, kqueue is\nnot a very scalable way to monitor a deeply nested\ndirectory of files and directories with a large number of\nfiles.\n\nFree threaded support\n---------------------\n\n`watchdog` has support for being built and run under free-threaded CPython. However, a full thread safety audit has not been completed, in particular this affects the `macOS FSEvents` interface.\n\nAbout using watchdog with editors like Vim\n------------------------------------------\n\nVim does not modify files unless directed to do so.\nIt creates backup files and then swaps them in to replace\nthe files you are editing on the disk. This means that\nif you use Vim to edit your files, the on-modified events\nfor those files will not be triggered by watchdog.\nYou may need to configure Vim appropriately to disable\nthis feature.\n\n\nAbout using watchdog with CIFS\n------------------------------\n\nWhen you want to watch changes in CIFS, you need to explicitly tell watchdog to\nuse ``PollingObserver``, that is, instead of letting watchdog decide an\nappropriate observer like in the example above, do::\n\n    from watchdog.observers.polling import PollingObserver as Observer\n\n\nDependencies\n------------\n\n1. Python 3.9 or above.\n2. XCode_ (only on macOS when installing from sources)\n3. PyYAML_ (only for ``watchmedo``)\n\nLicensing\n---------\n\nWatchdog is licensed under the terms of the `Apache License, version 2.0`_.\n\n- Copyright 2018-2025 Mickaël Schoentgen & contributors\n- Copyright 2014-2018 Thomas Amland & contributors\n- Copyright 2012-2014 Google, Inc.\n- Copyright 2011-2012 Yesudeep Mangalapilly\n\nProject `source code`_ is available at Github. Please report bugs and file\nenhancement requests at the `issue tracker`_.\n\nWhy Watchdog?\n-------------\n\nToo many people tried to do the same thing and none did what I needed Python\nto do:\n\n* pnotify_\n* `unison fsmonitor`_\n* fsmonitor_\n* guard_\n* pyinotify_\n* `inotify-tools`_\n* jnotify_\n* treewatcher_\n* `file.monitor`_\n* pyfilesystem_\n\n.. links:\n.. _Yesudeep Mangalapilly: yesudeep@gmail.com\n.. _source code: https://github.com/gorakhargosh/watchdog\n.. _issue tracker: https://github.com/gorakhargosh/watchdog/issues\n.. _Apache License, version 2.0: https://www.apache.org/licenses/LICENSE-2.0\n.. _documentation: https://python-watchdog.readthedocs.io/\n.. _stackoverflow: https://stackoverflow.com/questions/tagged/python-watchdog\n.. _repository: https://github.com/gorakhargosh/watchdog\n.. _issue tracker: https://github.com/gorakhargosh/watchdog/issues\n.. _changelog: https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst\n\n.. _PyYAML: https://www.pyyaml.org/\n.. _XCode: https://developer.apple.com/technologies/tools/xcode.html\n\n.. _pnotify: http://mark.heily.com/pnotify\n.. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471\n.. _fsmonitor: https://github.com/shaurz/fsmonitor\n.. _guard: https://github.com/guard/guard\n.. _pyinotify: https://github.com/seb-m/pyinotify\n.. _inotify-tools: https://github.com/rvoicilas/inotify-tools\n.. _jnotify: http://jnotify.sourceforge.net/\n.. _treewatcher: https://github.com/jbd/treewatcher\n.. _file.monitor: https://github.com/pke/file.monitor\n.. _pyfilesystem: https://github.com/PyFilesystem/pyfilesystem\n\n.. |PyPI Version| image:: https://img.shields.io/pypi/v/watchdog.svg\n   :target: https://pypi.python.org/pypi/watchdog/\n.. |PyPI Status| image:: https://img.shields.io/pypi/status/watchdog.svg\n   :target: https://pypi.python.org/pypi/watchdog/\n.. |PyPI Python Versions| image:: https://img.shields.io/pypi/pyversions/watchdog.svg\n   :target: https://pypi.python.org/pypi/watchdog/\n.. |Github Build Status| image:: https://github.com/gorakhargosh/watchdog/workflows/Tests/badge.svg\n   :target: https://github.com/gorakhargosh/watchdog/actions?query=workflow%3ATests\n.. |GitHub License| image:: https://img.shields.io/github/license/gorakhargosh/watchdog.svg\n   :target: https://github.com/gorakhargosh/watchdog/blob/master/LICENSE\n.. |Patreon| image:: https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white\n   :target: https://www.patreon.com/mschoentgen\n"
  },
  {
    "path": "THIRD_PARTY_LICENSES.md",
    "content": "# Third Party Licenses\n\n## Python C-API compatibility header\n\nThis project includes the `pythoncapi_compat.h` header provided by the\n`pythoncapi-compat` project. It is located in the `src` directory.\n\nThis header is included to allow writing code using C API constructs only\navailable in newer Python versions.\n\n**License**: Zero Clause BSD\n**Copyright**: Contributors to the pythoncapi_compat project.\n**Source**: https://github.com/python/pythoncapi_compat\n\n## Python Standard Library Compatibility Code\n\nThis project includes the following unmodified functions from the Python 3.13 standard library:\n\n- `glob.translate` (from `Lib/glob.py`)\n- `fnmatch._translate` (from `Lib/fnmatch.py`)\n\nThese are included in `backwards_compat.py` to provide backwards compatibility with older Python versions.\n\n**License**: Python Software Foundation License Version 2\n**Copyright**: © 2001–2024 Python Software Foundation; All Rights Reserved\n**Source**: https://github.com/python/cpython\n\n---\n\n### Zero Clause BSD License\n\nBSD Zero Clause License\n\nCopyright Contributors to the pythoncapi_compat project.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n\n### Python Software Foundation License Version 2\n\n1. This LICENSE AGREEMENT is between the Python Software Foundation (\"PSF\"), and the Individual or Organization (\"Licensee\") accessing and otherwise using this software (\"Python\") in source or binary form and its associated documentation.\n\n2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., \"Copyright (c) 2001 Python Software Foundation; All Rights Reserved\" are retained in Python alone or in any derivative version prepared by Licensee.\n\n3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python.\n\n4. PSF is making Python available to Licensee on an \"AS IS\" basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.\n\n5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.\n\n6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.\n\n7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee.  This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party.\n\n8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement.\n\n"
  },
  {
    "path": "changelog.rst",
    "content": ".. :changelog:\n\nChangelog\n---------\n\n7.0.0-dev\n~~~~~~~~~\n\n202x-xx-xx • `full history <https://github.com/gorakhargosh/watchdog/compare/v6.0.0...HEAD>`__\n\n**Breaking Changes**\n\n- [core] Changed pattern matching mechanism from using ``path.match()`` to ``path.full_match()``. Users must update patterns to glob-like syntax: e.g., `*.py` to `**/*.py`. (`#1101 <https://github.com/gorakhargosh/watchdog/pull/1101>`__)\n\n**Other Changes**\n\n- [core] Add context manager support to ``Observer`` class. The observer can now be used with a ``with`` statement for automatic start/stop management. (`#1090 <https://github.com/gorakhargosh/watchdog/pull/1149>`__)\n- [freebsd] Supports ``inotify`` on FreeBSD 15+. (`#1147 <https://github.com/gorakhargosh/watchdog/pull/1147>`__)\n- [core] Adjust ``Observer.schedule()`` ``path`` type annotation to reflect the ``pathlib.Path`` support. (`#1096 <https://github.com/gorakhargosh/watchdog/pull/1096>`__)\n- [core] Add support for the ``follow_symlink`` keyword argument to ``ObservedWatch``. (`#1086 <https://github.com/gorakhargosh/watchdog/pull/1086>`__)\n- [fsevents] Add support for the ``follow_symlink`` keyword argument. (`#1086 <https://github.com/gorakhargosh/watchdog/pull/1086>`__)\n- [inotify] Reduce number of created inotify instance. (`#1099 <https://github.com/gorakhargosh/watchdog/pull/1099>`__)\n- [inotify] Add support for the ``follow_symlink`` keyword argument. (`#1086 <https://github.com/gorakhargosh/watchdog/pull/1086>`__)\n- [utils] Fixed ``repr(EmptyDirectorySnapshot)``, before that it was throwing an ``AttributeError: 'EmptyDirectorySnapshot' object has no attribute '_stat_info'``.\n- [utils] Implemented ``len(DirectorySnapshotDiff)`` to return the total number of changes.\n- [core] Fixed ``generate_sub_moved_events()`` corrupting paths when the directory name appears multiple times in the path. (`#1158 <https://github.com/gorakhargosh/watchdog/pull/1158>`__)\n- Thanks to our beloved contributors: @BoboTiG, @tybug, @Corentin-pro, @kirkhansen, @JoachimCoenen, @blitztide\n- [core] Call ``task_done()`` for the stop sentinel in ``dispatch_events()`` to prevent ``join()`` from hanging. (`#1159 <https://github.com/gorakhargosh/watchdog/pull/1159>`__)\n\n6.0.0\n~~~~~\n\n2024-11-01 • `full history <https://github.com/gorakhargosh/watchdog/compare/v5.0.3...v6.0.0>`__\n\n- Pin test dependencies.\n- [docs] Add typing info to quick start. (`#1082 <https://github.com/gorakhargosh/watchdog/pull/1082>`__)\n- [inotify] Use of ``select.poll()`` instead of deprecated ``select.select()``, if available. (`#1078 <https://github.com/gorakhargosh/watchdog/pull/1078>`__)\n- [inotify] Fix reading inotify file descriptor after closing it. (`#1081 <https://github.com/gorakhargosh/watchdog/pull/1081>`__)\n- [utils] The ``stop_signal`` keyword-argument type of the ``AutoRestartTrick`` class can now be either a ``signal.Signals`` or an ``int``.\n- [utils] Added the ``__repr__()`` method to the ``Trick`` class.\n- [utils] Removed the unused ``echo_class()`` function from the ``echo`` module.\n- [utils] Removed the unused ``echo_instancemethod()`` function from the ``echo`` module.\n- [utils] Removed the unused ``echo_module()`` function from the ``echo`` module.\n- [utils] Removed the unused ``is_class_private_name()`` function from the ``echo`` module.\n- [utils] Removed the unused ``is_classmethod()`` function from the ``echo`` module.\n- [utils] Removed the unused ``ic_method(met()`` function from the ``echo`` module.\n- [utils] Removed the unused ``method_name()`` function from the ``echo`` module.\n- [utils] Removed the unused ``name()`` function from the ``echo`` module.\n- [watchmedo] Fixed Mypy issues.\n- [watchmedo] Added the ``__repr__()`` method to the ``HelpFormatter`` class.\n- [watchmedo] Removed the ``--trace`` CLI argument from the ``watchmedo log`` command, useless since events are logged by default at the ``LoggerTrick`` class level.\n- [windows] Fixed Mypy issues.\n- Thanks to our beloved contributors: @BoboTiG, @g-pichler, @ethan-vanderheijden, @nhairs\n\n5.0.3\n~~~~~\n\n2024-09-27 • `full history <https://github.com/gorakhargosh/watchdog/compare/v5.0.2...v5.0.3>`__\n\n- [inotify] Improve cleaning up ``Inotify`` threads, and add ``eventlet`` test cases (`#1070 <https://github.com/gorakhargosh/watchdog/pull/1070>`__)\n- Thanks to our beloved contributors: @BoboTiG, @ethan-vanderheijden\n\n5.0.2\n~~~~~\n\n2024-09-03 • `full history <https://github.com/gorakhargosh/watchdog/compare/v5.0.1...v5.0.2>`__\n\n- Enable OS specific Mypy checks (`#1064 <https://github.com/gorakhargosh/watchdog/pull/1064>`__)\n- [watchmedo] Fix ``tricks`` argument type of ``schedule_tricks()`` (`#1063 <https://github.com/gorakhargosh/watchdog/pull/1063>`__)\n- Thanks to our beloved contributors: @gnought, @BoboTiG\n\n5.0.1\n~~~~~\n\n2024-09-02 • `full history <https://github.com/gorakhargosh/watchdog/compare/v5.0.0...v5.0.1>`__\n\n- [kqueue] Fix ``TypeError: kqueue.control() only accepts positional parameters``  (`#1062 <https://github.com/gorakhargosh/watchdog/pull/1062>`__)\n- Thanks to our beloved contributors: @apoirier, @BoboTiG\n\n5.0.0\n~~~~~\n\n2024-08-26 • `full history <https://github.com/gorakhargosh/watchdog/compare/v4.0.2...v5.0.0>`__\n\n**Breaking Changes**\n\n- Drop support for Python 3.8 (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)\n- [core] Enforced usage of proper keyword-arguments (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)\n- [core] Renamed the ``BaseObserverSubclassCallable`` class to ``ObserverType`` (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)\n- [inotify] Renamed the ``inotify_event_struct`` class to ``InotifyEventStruct`` (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)\n- [inotify] Renamed the ``UnsupportedLibc`` exception to ``UnsupportedLibcError`` (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)\n- [inotify] Removed the ``InotifyConstants.IN_CLOSE`` constant (`#1046 <https://github.com/gorakhargosh/watchdog/pull/1046>`__)\n- [watchmedo] Renamed the ``LogLevelException`` exception to ``LogLevelError`` (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)\n- [watchmedo] Renamed the ``WatchdogShutdown`` exception to ``WatchdogShutdownError`` (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)\n- [windows] Renamed the ``FILE_NOTIFY_INFORMATION`` class to ``FileNotifyInformation`` (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)\n- [windows] Removed the unused ``WATCHDOG_TRAVERSE_MOVED_DIR_DELAY`` constant (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)\n\n**Other Changes**\n\n- [core] Enable ``disallow_untyped_calls`` Mypy rule (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)\n- [core] Enable ``disallow_untyped_defs`` Mypy rule (`#1060 <https://github.com/gorakhargosh/watchdog/pull/1060>`__)\n- [core] Improve typing references for events (`#1040 <https://github.com/gorakhargosh/watchdog/issues/1040>`__)\n- [inotify] Add support for ``IN_CLOSE_NOWRITE`` events. A ``FileClosedNoWriteEvent`` event will be fired, and its ``on_closed_no_write()`` dispatcher has been introduced (`#1046 <https://github.com/gorakhargosh/watchdog/pull/1046>`__)\n- Thanks to our beloved contributors: @BoboTiG\n\n4.0.2\n~~~~~\n\n2024-08-11 • `full history <https://github.com/gorakhargosh/watchdog/compare/v4.0.1...v4.0.2>`__\n\n- Add support for Python 3.13 (`#1052 <https://github.com/gorakhargosh/watchdog/pull/1052>`__)\n- [core] Run ``ruff``, apply several fixes (`#1033 <https://github.com/gorakhargosh/watchdog/pull/1033>`__)\n- [core] Remove execution rights from ``events.py``\n- [documentation] Update ``PatternMatchingEventHandler`` docstrings (`#1048 <https://github.com/gorakhargosh/watchdog/pull/1048>`__)\n- [documentation] Simplify the quickstart example (`#1047 <https://github.com/gorakhargosh/watchdog/pull/1047>`__)\n- [fsevents] Add missing ``event_filter`` keyword-argument to ``FSEventsObserver.schedule()`` (`#1049 <https://github.com/gorakhargosh/watchdog/pull/1049>`__)\n- [utils] Fix a possible race condition in ``AutoRestartTrick`` (`#1002 <https://github.com/gorakhargosh/watchdog/pull/1002>`__)\n- [watchmedo] Remove execution rights from ``watchmedo.py``\n- Thanks to our beloved contributors: @BoboTiG, @nbelakovski, @ivg\n\n4.0.1\n~~~~~\n\n2024-05-23 • `full history <https://github.com/gorakhargosh/watchdog/compare/v4.0.0...v4.0.1>`__\n\n- [inotify] Fix missing ``event_filter`` for the full emitter (`#1032 <https://github.com/gorakhargosh/watchdog/pull/1032>`__)\n- Thanks to our beloved contributors: @mraspaud, @BoboTiG\n\n4.0.0\n~~~~~\n\n2024-02-06 • `full history <https://github.com/gorakhargosh/watchdog/compare/v3.0.0...v4.0.0>`__\n\n- Drop support for Python 3.7.\n- Add support for Python 3.12.\n- [snapshot] Add typing to ``dirsnapshot`` (`#1012 <https://github.com/gorakhargosh/watchdog/pull/1012>`__)\n- [snapshot] Added ``DirectorySnapshotDiff.ContextManager`` (`#1011 <https://github.com/gorakhargosh/watchdog/pull/1011>`__)\n- [events] ``FileSystemEvent``, and subclasses, are now ``dataclass``es, and their ``repr()`` has changed\n- [windows] ``WinAPINativeEvent`` is now a ``dataclass``, and its ``repr()`` has changed\n- [events] Log ``FileOpenedEvent``, and ``FileClosedEvent``, events in ``LoggingEventHandler``\n- [tests] Improve ``FileSystemEvent`` coverage\n- [watchmedo] Log all events in ``LoggerTrick``\n- [windows] The ``observers.read_directory_changes.WATCHDOG_TRAVERSE_MOVED_DIR_DELAY`` hack was removed. The constant will be kept to prevent breaking other software.\n- Thanks to our beloved contributors: @BoboTiG, @msabramo\n\n3.0.0\n~~~~~\n\n2023-03-20 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.3.1...v3.0.0>`__\n\n- Drop support for Python 3.6.\n- ``watchdog`` is now PEP 561 compatible, and tested with ``mypy``\n- Fix missing ``>`` in ``FileSystemEvent.__repr__()``  (`#980 <https://github.com/gorakhargosh/watchdog/pull/980>`__)\n- [ci] Lots of improvements\n- [inotify] Return from ``InotifyEmitter.queue_events()`` if not launched when thread is inactive (`#963 <https://github.com/gorakhargosh/watchdog/pull/963>`__)\n- [tests] Stability improvements\n- [utils] Remove handling of ``threading.Event.isSet`` spelling (`#962 <https://github.com/gorakhargosh/watchdog/pull/962>`__)\n- [watchmedo] Fixed tricks YAML generation (`#965 <https://github.com/gorakhargosh/watchdog/pull/965>`__)\n- Thanks to our beloved contributors: @kurtmckee, @altendky, @agroszer, @BoboTiG\n\n2.3.1\n~~~~~\n\n2023-02-28 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.3.0...v2.3.1>`__\n\n- Run ``black`` on the entire source code\n- Bundle the ``requirements-tests.txt`` file in the source distribution (`#939 <https://github.com/gorakhargosh/watchdog/pull/939>`__)\n- [watchmedo] Exclude ``FileOpenedEvent`` events from ``AutoRestartTrick``, and ``ShellCommandTrick``, to restore watchdog < 2.3.0 behavior. A better solution should be found in the future. (`#949 <https://github.com/gorakhargosh/watchdog/pull/949>`__)\n- [watchmedo] Log ``FileOpenedEvent``, and ``FileClosedEvent``, events in ``LoggerTrick``\n- Thanks to our beloved contributors: @BoboTiG\n\n2.3.0\n~~~~~\n\n2023-02-23 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.2.1...v2.3.0>`__\n\n- [inotify] Add support for ``IN_OPEN`` events: a ``FileOpenedEvent`` event will be fired (`#941 <https://github.com/gorakhargosh/watchdog/pull/941>`__)\n- [watchmedo] Add optional event debouncing for ``auto-restart``, only restarting once if many events happen in quick succession (``--debounce-interval``) (`#940 <https://github.com/gorakhargosh/watchdog/pull/940>`__)\n- [watchmedo] Exit gracefully on ``KeyboardInterrupt`` exception (Ctrl+C) (`#945 <https://github.com/gorakhargosh/watchdog/pull/945>`__)\n- [watchmedo] Add option to not auto-restart the command after it exits (``--no-restart-on-command-exit``) (`#946 <https://github.com/gorakhargosh/watchdog/pull/946>`__)\n- Thanks to our beloved contributors: @BoboTiG, @dstaple, @taleinat, @cernekj\n\n2.2.1\n~~~~~\n\n2023-01-01 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.2.0...v2.2.1>`__\n\n- Enable ``mypy`` to discover type hints as specified in PEP 561 (`#933 <https://github.com/gorakhargosh/watchdog/pull/933>`__)\n- [ci] Set the expected Python version when building release files\n- [ci] Update actions versions in use\n- [watchmedo] [regression] Fix usage of missing ``signal.SIGHUP`` attribute on non-Unix OSes (`#935 <https://github.com/gorakhargosh/watchdog/pull/935>`__)\n- Thanks to our beloved contributors: @BoboTiG, @simon04, @piotrpdev\n\n2.2.0\n~~~~~\n\n2022-12-05 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.9...v2.2.0>`__\n\n- [build] Wheels are now available for Python 3.11 (`#932 <https://github.com/gorakhargosh/watchdog/pull/932>`__)\n- [documentation] HTML documentation builds are now tested for errors (`#902 <https://github.com/gorakhargosh/watchdog/pull/902>`__)\n- [documentation] Fix typos here, and there (`#910 <https://github.com/gorakhargosh/watchdog/pull/910>`__)\n- [fsevents2] The ``fsevents2`` observer is now deprecated (`#909 <https://github.com/gorakhargosh/watchdog/pull/909>`__)\n- [tests] The error message returned by musl libc for error code ``-1`` is now allowed (`#923 <https://github.com/gorakhargosh/watchdog/pull/923>`__)\n- [utils] Remove unnecessary code in ``dirsnapshot.py`` (`#930 <https://github.com/gorakhargosh/watchdog/pull/930>`__)\n- [watchmedo] Handle shutdown events from ``SIGHUP`` (`#912 <https://github.com/gorakhargosh/watchdog/pull/912>`__)\n- Thanks to our beloved contributors: @kurtmckee, @babymastodon, @QuantumEnergyE, @timgates42, @BoboTiG\n\n2.1.9\n~~~~~\n\n2022-06-10 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.8...v2.1.9>`__\n\n- [fsevents] Fix flakey test to assert that there are no errors when stopping the emitter.\n- [inotify] Suppress occasional ``OSError: [Errno 9] Bad file descriptor`` at shutdown. (`#805 <https://github.com/gorakhargosh/watchdog/issues/805>`__)\n- [watchmedo] Make ``auto-restart`` restart the sub-process if it terminates. (`#896 <https://github.com/gorakhargosh/watchdog/pull/896>`__)\n- [watchmedo] Avoid zombie sub-processes when running ``shell-command`` without ``--wait``. (`#405 <https://github.com/gorakhargosh/watchdog/issues/405>`__)\n- Thanks to our beloved contributors: @samschott, @taleinat, @altendky, @BoboTiG\n\n2.1.8\n~~~~~\n\n2022-05-15 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.7...v2.1.8>`__\n\n- Fix adding failed emitters on observer schedule. (`#872 <https://github.com/gorakhargosh/watchdog/issues/872>`__)\n- [inotify] Fix hang when unscheduling watch on a path in an unmounted filesystem. (`#869 <https://github.com/gorakhargosh/watchdog/pull/869>`__)\n- [watchmedo] Fix broken parsing of ``--kill-after`` argument for the ``auto-restart`` command. (`#870 <https://github.com/gorakhargosh/watchdog/issues/870>`__)\n- [watchmedo] Fix broken parsing of boolean arguments. (`#887 <https://github.com/gorakhargosh/watchdog/issues/887>`__)\n- [watchmedo] Fix broken parsing of commands from ``auto-restart``, and ``shell-command``. (`#888 <https://github.com/gorakhargosh/watchdog/issues/888>`__)\n- [watchmedo] Support setting verbosity level via ``-q/--quiet`` and ``-v/--verbose`` arguments. (`#889 <https://github.com/gorakhargosh/watchdog/pull/889>`__)\n- Thanks to our beloved contributors: @taleinat, @kianmeng, @palfrey, @IlayRosenberg, @BoboTiG\n\n2.1.7\n~~~~~\n\n2022-03-25 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.6...v2.1.7>`__\n\n- Eliminate timeout in waiting on event queue. (`#861 <https://github.com/gorakhargosh/watchdog/pull/861>`__)\n- [inotify] Fix ``not`` equality implementation for ``InotifyEvent``. (`#848 <https://github.com/gorakhargosh/watchdog/pull/848>`__)\n- [watchmedo] Fix calling commands from within a Python script. (`#879 <https://github.com/gorakhargosh/watchdog/pull/879>`__)\n- [watchmedo] ``PyYAML`` is loaded only when strictly necessary. Simple usages of ``watchmedo`` are possible without the module being installed. (`#847 <https://github.com/gorakhargosh/watchdog/pull/847>`__)\n- Thanks to our beloved contributors: @sattlerc, @JanzenLiu, @BoboTiG\n\n2.1.6\n~~~~~\n\n2021-10-01 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.5...v2.1.6>`__\n\n- [bsd] Fixed returned paths in ``kqueue.py`` and restored the overall results of the test suite. (`#842 <https://github.com/gorakhargosh/watchdog/pull/842>`__)\n- [bsd] Updated FreeBSD CI support .(`#841 <https://github.com/gorakhargosh/watchdog/pull/841>`__)\n- [watchmedo] Removed the ``argh`` dependency in favor of the builtin ``argparse`` module. (`#836 <https://github.com/gorakhargosh/watchdog/pull/836>`__)\n- [watchmedo] Removed unexistant ``WindowsApiAsyncObserver`` references and ``--debug-force-winapi-async`` arguments.\n- [watchmedo] Improved the help output.\n- Thanks to our beloved contributors: @knobix, @AndreaRe9, @BoboTiG\n\n2.1.5\n~~~~~\n\n2021-08-23 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.4...v2.1.5>`__\n\n- Fix regression introduced in 2.1.4 (reverted \"Allow overriding or adding custom event handlers to event dispatch map. (`#814 <https://github.com/gorakhargosh/watchdog/pull/814>`__)\"). (`#830 <https://github.com/gorakhargosh/watchdog/pull/830>`__)\n- Convert regexes of type ``str`` to ``list``. (`831 <https://github.com/gorakhargosh/watchdog/pull/831>`__)\n- Thanks to our beloved contributors: @unique1o1, @BoboTiG\n\n2.1.4\n~~~~~\n\n2021-08-19 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.3...v2.1.4>`__\n\n- [watchmedo] Fix usage of ``os.setsid()`` and ``os.killpg()`` Unix-only functions. (`#809 <https://github.com/gorakhargosh/watchdog/pull/809>`__)\n- [mac] Fix missing ``FileModifiedEvent`` on permission or ownership changes of a file. (`#815 <https://github.com/gorakhargosh/watchdog/pull/815>`__)\n- [mac] Convert absolute watch path in ``FSEeventsEmitter`` with ``os.path.realpath()``. (`#822 <https://github.com/gorakhargosh/watchdog/pull/822>`__)\n- Fix a possible ``AttributeError`` in ``SkipRepeatsQueue._put()``. (`#818 <https://github.com/gorakhargosh/watchdog/pull/818>`__)\n- Allow overriding or adding custom event handlers to event dispatch map. (`#814 <https://github.com/gorakhargosh/watchdog/pull/814>`__)\n- Fix tests on big endian platforms. (`#828 <https://github.com/gorakhargosh/watchdog/pull/828>`__)\n- Thanks to our beloved contributors: @replabrobin, @BoboTiG, @SamSchott, @AndreiB97, @NiklasRosenstein, @ikokollari, @mgorny\n\n2.1.3\n~~~~~\n\n2021-06-26 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.2...v2.1.3>`__\n\n- Publish macOS ``arm64`` and ``universal2`` wheels. (`#740 <https://github.com/gorakhargosh/watchdog/pull/740>`__)\n- Thanks to our beloved contributors: @kainjow, @BoboTiG\n\n2.1.2\n~~~~~\n\n2021-05-19 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.1...v2.1.2>`__\n\n- [mac] Fix relative path handling for non-recursive watch. (`#797 <https://github.com/gorakhargosh/watchdog/pull/797>`__)\n- [windows] On PyPy, events happening right after ``start()`` were missed. Add a workaround for that. (`#796 <https://github.com/gorakhargosh/watchdog/pull/796>`__)\n- Thanks to our beloved contributors: @oprypin, @CCP-Aporia, @BoboTiG\n\n2.1.1\n~~~~~\n\n2021-05-10 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.0...v2.1.1>`__\n\n- [mac] Fix callback exceptions when the watcher is deleted but still receiving events (`#786 <https://github.com/gorakhargosh/watchdog/pull/786>`__)\n- Thanks to our beloved contributors: @rom1win, @BoboTiG, @CCP-Aporia\n\n\n2.1.0\n~~~~~\n\n2021-05-04 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.0.3...v2.1.0>`__\n\n- [inotify] Simplify ``libc`` loading (`#776 <https://github.com/gorakhargosh/watchdog/pull/776>`__)\n- [mac] Add support for non-recursive watches in ``FSEventsEmitter`` (`#779 <https://github.com/gorakhargosh/watchdog/pull/779>`__)\n- [watchmedo] Add support for ``--debug-force-*`` arguments to ``tricks`` (`#781 <https://github.com/gorakhargosh/watchdog/pull/781>`__)\n- Thanks to our beloved contributors: @CCP-Aporia, @aodj, @UnitedMarsupials, @BoboTiG\n\n\n2.0.3\n~~~~~\n\n2021-04-22 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.0.2...v2.0.3>`__\n\n- [mac] Use ``logger.debug()`` instead of ``logger.info()`` (`#774 <https://github.com/gorakhargosh/watchdog/pull/774>`__)\n- Updated documentation links (`#777 <https://github.com/gorakhargosh/watchdog/pull/777>`__)\n- Thanks to our beloved contributors: @globau, @imba-tjd, @BoboTiG\n\n\n2.0.2\n~~~~~\n\n2021-02-22 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.0.1...v2.0.2>`__\n\n- [mac] Add missing exception objects (`#766 <https://github.com/gorakhargosh/watchdog/pull/766>`__)\n- Thanks to our beloved contributors: @CCP-Aporia, @BoboTiG\n\n\n2.0.1\n~~~~~\n\n2021-02-17 • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.0.0...v2.0.1>`__\n\n- [mac] Fix a segmentation fault when dealing with unicode paths (`#763 <https://github.com/gorakhargosh/watchdog/pull/763>`__)\n- Moved the CI from Travis-CI to GitHub Actions (`#764 <https://github.com/gorakhargosh/watchdog/pull/764>`__)\n- Thanks to our beloved contributors: @SamSchott, @BoboTiG\n\n\n2.0.0\n~~~~~\n\n2021-02-11 • `full history <https://github.com/gorakhargosh/watchdog/compare/v1.0.2...v2.0.0>`__\n\n- Avoid deprecated ``PyEval_InitThreads`` on Python 3.7+ (`#746 <https://github.com/gorakhargosh/watchdog/pull/746>`__)\n- [inotify] Add support for ``IN_CLOSE_WRITE`` events. A ``FileCloseEvent`` event will be fired. Note that ``IN_CLOSE_NOWRITE`` events are not handled to prevent much noise. (`#184 <https://github.com/gorakhargosh/watchdog/pull/184>`__, `#245 <https://github.com/gorakhargosh/watchdog/pull/245>`__, `#280 <https://github.com/gorakhargosh/watchdog/pull/280>`__, `#313 <https://github.com/gorakhargosh/watchdog/pull/313>`__, `#690 <https://github.com/gorakhargosh/watchdog/pull/690>`__)\n- [inotify] Allow to stop the emitter multiple times (`#760 <https://github.com/gorakhargosh/watchdog/pull/760>`__)\n- [mac] Support coalesced filesystem events (`#734 <https://github.com/gorakhargosh/watchdog/pull/734>`__)\n- [mac] Drop support for macOS 10.12 and earlier (`#750 <https://github.com/gorakhargosh/watchdog/pull/750>`__)\n- [mac] Fix an issue when renaming an item changes only the casing (`#750 <https://github.com/gorakhargosh/watchdog/pull/750>`__)\n- Thanks to our beloved contributors: @bstaletic, @lukassup, @ysard, @SamSchott, @CCP-Aporia, @BoboTiG\n\n\n1.0.2\n~~~~~\n\n2020-12-18 • `full history <https://github.com/gorakhargosh/watchdog/compare/v1.0.1...v1.0.2>`__\n\n- Wheels are published for GNU/Linux, macOS and Windows (`#739 <https://github.com/gorakhargosh/watchdog/pull/739>`__)\n- [mac] Fix missing ``event_id`` attribute in ``fsevents`` (`#721 <https://github.com/gorakhargosh/watchdog/pull/721>`__)\n- [mac] Return byte paths if a byte path was given in ``fsevents`` (`#726 <https://github.com/gorakhargosh/watchdog/pull/726>`__)\n- [mac] Add compatibility with old macOS versions (`#733 <https://github.com/gorakhargosh/watchdog/pull/733>`__)\n- Uniformize event for deletion of watched dir (`#727 <https://github.com/gorakhargosh/watchdog/pull/727>`__)\n- Thanks to our beloved contributors: @SamSchott, @CCP-Aporia, @di, @BoboTiG\n\n\n1.0.1\n~~~~~\n\n2020-12-10 • Fix version with good metadatas.\n\n\n1.0.0\n~~~~~\n\n2020-12-10 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.10.4...v1.0.0>`__\n\n- Versioning is now following the `semver <https://semver.org/>`__\n- Drop support for Python 2.7, 3.4 and 3.5\n- [mac] Regression fixes for native ``fsevents`` (`#717 <https://github.com/gorakhargosh/watchdog/pull/717>`__)\n- [windows] ``winapi.BUFFER_SIZE`` now defaults to ``64000`` (instead of ``2048``) (`#700 <https://github.com/gorakhargosh/watchdog/pull/700>`__)\n- [windows] Introduced ``winapi.PATH_BUFFER_SIZE`` (defaults to ``2048``) to keep the old behavior with path-realted functions (`#700 <https://github.com/gorakhargosh/watchdog/pull/700>`__)\n- Use ``pathlib`` from the standard library, instead of pathtools (`#556 <https://github.com/gorakhargosh/watchdog/pull/556>`__)\n- Allow file paths on Unix that don't follow the file system encoding (`#703 <https://github.com/gorakhargosh/watchdog/pull/703>`__)\n- Removed the long-time deprecated ``events.LoggingFileSystemEventHandler`` class, use ``LoggingEventHandler`` instead\n- Thanks to our beloved contributors: @SamSchott, @bstaletic, @BoboTiG, @CCP-Aporia\n\n\n0.10.4\n~~~~~~\n\n2020-11-21 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.10.3...v0.10.4>`__\n\n- Add ``logger`` parameter for the ``LoggingEventHandler`` (`#676 <https://github.com/gorakhargosh/watchdog/pull/676>`__)\n- Replace mutable default arguments with ``if None`` implementation (`#677 <https://github.com/gorakhargosh/watchdog/pull/677>`__)\n- Expand tests to Python 2.7 and 3.5-3.10 for GNU/Linux, macOS and Windows\n- [mac] Performance improvements for the ``fsevents`` module (`#680 <https://github.com/gorakhargosh/watchdog/pull/680>`__)\n- [mac] Prevent compilation of ``watchdog_fsevents.c`` on non-macOS machines (`#687 <https://github.com/gorakhargosh/watchdog/pull/687>`__)\n- [watchmedo] Handle shutdown events from ``SIGTERM`` and ``SIGINT`` more reliably (`#693 <https://github.com/gorakhargosh/watchdog/pull/693>`__)\n- Thanks to our beloved contributors: @Sraw, @CCP-Aporia, @BoboTiG, @maybe-sybr\n\n\n0.10.3\n~~~~~~\n\n2020-06-25 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.10.2...v0.10.3>`__\n\n- Ensure ``ObservedWatch.path`` is a string (`#651 <https://github.com/gorakhargosh/watchdog/pull/651>`__)\n- [inotify] Allow to monitor single file (`#655 <https://github.com/gorakhargosh/watchdog/pull/655>`__)\n- [inotify] Prevent raising an exception when a file in a monitored folder has no permissions (`#669 <https://github.com/gorakhargosh/watchdog/pull/669>`__, `#670 <https://github.com/gorakhargosh/watchdog/pull/670>`__)\n- Thanks to our beloved contributors: @brant-ruan, @rec, @andfoy, @BoboTiG\n\n\n0.10.2\n~~~~~~\n\n2020-02-08 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.10.1...v0.10.2>`__\n\n- Fixed the ``build_ext`` command on macOS Catalina (`#628 <https://github.com/gorakhargosh/watchdog/pull/628>`__)\n- Fixed the installation of macOS requirements on non-macOS OSes (`#635 <https://github.com/gorakhargosh/watchdog/pull/635>`__)\n- Refactored ``dispatch()`` method of ``FileSystemEventHandler``,\n  ``PatternMatchingEventHandler`` and ``RegexMatchingEventHandler``\n- [bsd] Improved tests support on non Windows/Linux platforms (`#633 <https://github.com/gorakhargosh/watchdog/pull/633>`__, `#639 <https://github.com/gorakhargosh/watchdog/pull/639>`__)\n- [bsd] Added FreeBSD CI support (`#532 <https://github.com/gorakhargosh/watchdog/pull/532>`__)\n- [bsd] Restored full support (`#638 <https://github.com/gorakhargosh/watchdog/pull/638>`__, `#641 <https://github.com/gorakhargosh/watchdog/pull/641>`__)\n- Thanks to our beloved contributors: @BoboTiG, @evilham, @danilobellini\n\n\n0.10.1\n~~~~~~\n\n2020-01-30 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.10.0...v0.10.1>`__\n\n- Fixed Python 2.7 to 3.6 installation when the OS locale is set to POSIX (`#615 <https://github.com/gorakhargosh/watchdog/pull/615>`__)\n- Fixed the ``build_ext`` command on macOS  (`#618 <https://github.com/gorakhargosh/watchdog/pull/618>`__, `#620 <https://github.com/gorakhargosh/watchdog/pull/620>`__)\n- Moved requirements to ``setup.cfg``  (`#617 <https://github.com/gorakhargosh/watchdog/pull/617>`__)\n- [mac] Removed old C code for Python 2.5 in the `fsevents` C implementation\n- [snapshot] Added ``EmptyDirectorySnapshot`` (`#613 <https://github.com/gorakhargosh/watchdog/pull/613>`__)\n- Thanks to our beloved contributors: @Ajordat, @tehkirill, @BoboTiG\n\n\n0.10.0\n~~~~~~\n\n2020-01-26 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.9.0...v0.10.0>`__\n\n**Breaking Changes**\n\n- Dropped support for Python 2.6, 3.2 and 3.3\n- Emitters that failed to start are now removed\n- [snapshot] Removed the deprecated ``walker_callback`` argument,\n  use ``stat`` instead\n- [watchmedo] The utility is no more installed by default but via the extra\n  ``watchdog[watchmedo]``\n\n**Other Changes**\n\n- Fixed several Python 3 warnings\n- Identify synthesized events with ``is_synthetic`` attribute (`#369 <https://github.com/gorakhargosh/watchdog/pull/369>`__)\n- Use ``os.scandir()`` to improve memory usage (`#503 <https://github.com/gorakhargosh/watchdog/pull/503>`__)\n- [bsd] Fixed flavors of FreeBSD detection (`#529 <https://github.com/gorakhargosh/watchdog/pull/529>`__)\n- [bsd] Skip unprocessable socket files (`#509 <https://github.com/gorakhargosh/watchdog/issue/509>`__)\n- [inotify] Fixed events containing non-ASCII characters (`#516 <https://github.com/gorakhargosh/watchdog/issues/516>`__)\n- [inotify] Fixed the way ``OSError`` are re-raised (`#377 <https://github.com/gorakhargosh/watchdog/issues/377>`__)\n- [inotify] Fixed wrong source path after renaming a top level folder (`#515 <https://github.com/gorakhargosh/watchdog/pull/515>`__)\n- [inotify] Removed  delay from non-move events (`#477 <https://github.com/gorakhargosh/watchdog/pull/477>`__)\n- [mac] Fixed a bug when calling ``FSEventsEmitter.stop()`` twice (`#466 <https://github.com/gorakhargosh/watchdog/pull/466>`__)\n- [mac] Support for unscheduling deleted watch (`#541 <https://github.com/gorakhargosh/watchdog/issue/541>`__)\n- [mac] Fixed missing field initializers and unused parameters in\n  ``watchdog_fsevents.c``\n- [snapshot] Don't walk directories without read permissions (`#408 <https://github.com/gorakhargosh/watchdog/pull/408>`__)\n- [snapshot] Fixed a race condition crash when a directory is swapped for a file (`#513 <https://github.com/gorakhargosh/watchdog/pull/513>`__)\n- [snasphot] Fixed an ``AttributeError`` about forgotten ``path_for_inode`` attr (`#436 <https://github.com/gorakhargosh/watchdog/issues/436>`__)\n- [snasphot] Added the ``ignore_device=False`` parameter to the ctor (`597 <https://github.com/gorakhargosh/watchdog/pull/597>`__)\n- [watchmedo] Fixed the path separator used (`#478 <https://github.com/gorakhargosh/watchdog/pull/478>`__)\n- [watchmedo] Fixed the use of ``yaml.load()`` for ``yaml.safe_load()`` (`#453 <https://github.com/gorakhargosh/watchdog/issues/453>`__)\n- [watchmedo] Handle all available signals (`#549 <https://github.com/gorakhargosh/watchdog/issue/549>`__)\n- [watchmedo] Added the ``--debug-force-polling`` argument (`#404 <https://github.com/gorakhargosh/watchdog/pull/404>`__)\n- [windows] Fixed issues when the observed directory is deleted (`#570 <https://github.com/gorakhargosh/watchdog/issues/570>`__ and `#601 <https://github.com/gorakhargosh/watchdog/pull/601>`__)\n- [windows] ``WindowsApiEmitter`` made easier to subclass (`#344 <https://github.com/gorakhargosh/watchdog/pull/344>`__)\n- [windows] Use separate ctypes DLL instances\n- [windows] Generate sub created events only if ``recursive=True`` (`#454 <https://github.com/gorakhargosh/watchdog/pull/454>`__)\n- Thanks to our beloved contributors: @BoboTiG, @LKleinNux, @rrzaripov,\n  @wildmichael, @TauPan, @segevfiner, @petrblahos, @QuantumEnergyE,\n  @jeffwidman, @kapsh, @nickoala, @petrblahos, @julianolf, @tonybaloney,\n  @mbakiev, @pR0Ps, javaguirre, @skurfer, @exarkun, @joshuaskelly,\n  @danilobellini, @Ajordat\n\n\n0.9.0\n~~~~~\n\n2018-08-28 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.8.3...v0.9.0>`__\n\n- Deleting the observed directory now emits a ``DirDeletedEvent`` event\n- [bsd] Improved the platform detection (`#378 <https://github.com/gorakhargosh/watchdog/pull/378>`__)\n- [inotify] Fixed a crash when the root directory being watched by was deleted (`#374 <https://github.com/gorakhargosh/watchdog/pull/374>`__)\n- [inotify] Handle systems providing uClibc\n- [linux] Fixed a possible ``DirDeletedEvent`` duplication when\n  deleting a directory\n- [mac] Fixed unicode path handling ``fsevents2.py`` (`#298 <https://github.com/gorakhargosh/watchdog/pull/298>`__)\n- [watchmedo] Added the ``--debug-force-polling`` argument (`#336 <https://github.com/gorakhargosh/watchdog/pull/336>`__)\n- [windows] Fixed the ``FILE_LIST_DIRECTORY`` constant (`#376 <https://github.com/gorakhargosh/watchdog/pull/376>`__)\n- Thanks to our beloved contributors: @vulpeszerda, @hpk42, @tamland, @senden9,\n  @gorakhargosh, @nolsto, @mafrosis, @DonyorM, @anthrotype, @danilobellini,\n  @pierregr, @ShinNoNoir, @adrpar, @gforcada, @pR0Ps, @yegorich, @dhke\n\n\n0.8.3\n~~~~~\n\n2015-02-11 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.8.2...v0.8.3>`__\n\n- Fixed the use of the root logger (`#274 <https://github.com/gorakhargosh/watchdog/issues/274>`__)\n- [inotify] Refactored libc loading and improved error handling in\n  ``inotify_c.py``\n- [inotify] Fixed a possible unbound local error in ``inotify_c.py``\n- Thanks to our beloved contributors: @mmorearty, @tamland, @tony,\n  @gorakhargosh\n\n\n0.8.2\n~~~~~\n\n2014-10-29 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.8.1...v0.8.2>`__\n\n- Event emitters are no longer started on schedule if ``Observer`` is not\n  already running\n- [mac] Fixed unused arguments to pass clang compilation (`#265 <https://github.com/gorakhargosh/watchdog/pull/265>`__)\n- [snapshot] Fixed a possible race condition crash on directory deletion (`#281 <https://github.com/gorakhargosh/watchdog/pull/281>`__)\n- [windows] Fixed an error when watching the same folder again (`#270 <https://github.com/gorakhargosh/watchdog/pull/270>`__)\n- Thanks to our beloved contributors: @tamland, @apetrone, @Falldog,\n  @theospears\n\n\n0.8.1\n~~~~~\n\n2014-07-28 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.8.0...v0.8.1>`__\n\n- Fixed ``anon_inode`` descriptors leakage  (`#249 <https://github.com/gorakhargosh/watchdog/pull/249>`__)\n- [inotify] Fixed thread stop dead lock (`#250 <https://github.com/gorakhargosh/watchdog/issues/250>`__)\n- Thanks to our beloved contributors: @Witos, @adiroiban, @tamland\n\n\n0.8.0\n~~~~~\n\n2014-07-02 • `full history <https://github.com/gorakhargosh/watchdog/compare/v0.7.1...v0.8.0>`__\n\n- Fixed ``argh`` deprecation warnings (`#242 <https://github.com/gorakhargosh/watchdog/pull/242>`__)\n- [snapshot] Methods returning internal stats info were replaced by\n  ``mtime()``, ``inode()`` and ``path()`` methods\n- [snapshot] Deprecated the ``walker_callback`` argument\n- [watchmedo] Fixed ``auto-restart`` to terminate all children processes (`#225 <https://github.com/gorakhargosh/watchdog/pull/225>`__)\n- [watchmedo] Added the ``--no-parallel`` argument (`#227 <https://github.com/gorakhargosh/watchdog/issues/227>`__)\n- [windows] Fixed the value of ``INVALID_HANDLE_VALUE`` (`#123 <https://github.com/gorakhargosh/watchdog/issues/123>`__)\n- [windows] Fixed octal usages to work with Python 3 as well (`#223 <https://github.com/gorakhargosh/watchdog/issues/223>`__)\n- Thanks to our beloved contributors: @tamland, @Ormod, @berdario, @cro,\n  @BernieSumption, @pypingou, @gotcha, @tommorris, @frewsxcv\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# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\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 \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\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\t-rm -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/watchdog.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/watchdog.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/watchdog\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/watchdog\"\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\tmake -C $(BUILDDIR)/latex all-pdf\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\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"
  },
  {
    "path": "docs/eclipse_cdt_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<profiles version=\"1\">\n<profile kind=\"CodeFormatterProfile\" name=\"Yesudeep Mangalapilly\" version=\"1\">\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.lineSplit\" value=\"100\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer\" value=\"16\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.tabulation.size\" value=\"4\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_enumerator_list\" value=\"48\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_declarator_list\" value=\"16\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_empty_lines\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_method_declaration\" value=\"next_line\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_semicolon\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation\" value=\"82\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration\" value=\"83\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve\" value=\"1\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_block\" value=\"next_line_shifted\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_type_declaration\" value=\"next_line\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.continuation_indentation\" value=\"2\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_expression_list\" value=\"0\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_conditional_expression\" value=\"80\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer\" value=\"2\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_compact_if\" value=\"0\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_binary_operator\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration\" value=\"80\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_unary_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_statements_compare_to_body\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indent_statements_compare_to_block\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration\" value=\"82\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.tabulation.char\" value=\"space\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_block_in_case\" value=\"next_line_shifted\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.compact_else_if\" value=\"true\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_switch\" value=\"next_line_shifted\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line\" value=\"false\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.indentation.size\" value=\"8\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration\" value=\"next_line\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.brace_position_for_array_initializer\" value=\"next_line_shifted\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket\" value=\"do not insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters\" value=\"insert\"/>\n<setting id=\"org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments\" value=\"do not insert\"/>\n</profile>\n</profiles>\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\watchdog.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\watchdog.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/source/api.rst",
    "content": ".. include:: global.rst.inc\n\n.. api_reference:\n\n=============\nAPI Reference\n=============\n\n`watchdog.events`\n=================\n\n.. automodule:: watchdog.events\n\n\n`watchdog.observers.api`\n========================\n\n.. automodule:: watchdog.observers.api\n    :synopsis: Classes useful to observer implementers.\n\n    Immutables\n    ----------\n    .. autoclass:: ObservedWatch\n       :members:\n       :show-inheritance:\n\n\n    Collections\n    -----------\n    .. autoclass:: EventQueue\n       :members:\n       :show-inheritance:\n\n    Classes\n    -------\n    .. autoclass:: EventEmitter\n       :members:\n       :show-inheritance:\n\n    .. autoclass:: EventDispatcher\n       :members:\n       :show-inheritance:\n\n    .. autoclass:: BaseObserver\n       :members:\n       :show-inheritance:\n\n\n\n`watchdog.observers`\n====================\n\n.. automodule:: watchdog.observers\n\n\n`watchdog.observers.polling`\n============================\n\n.. automodule:: watchdog.observers.polling\n\n\n`watchdog.utils`\n================\n\n.. automodule:: watchdog.utils\n\n\n`watchdog.utils.dirsnapshot`\n============================\n\n.. automodule:: watchdog.utils.dirsnapshot\n\n`watchdog.tricks`\n=================\n\n.. automodule:: watchdog.tricks\n\n\n.. toctree::\n   :maxdepth: 2\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "# watchdog documentation build configuration file, created by\n# sphinx-quickstart on Tue Nov 30 00:43:58 2010.\n#\n# This file is execfile()d with the current directory set to its 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.\n\nimport sys\nimport os.path\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nTOP_DIR_PATH = os.path.abspath(\"../../\")\nSRC_DIR_PATH = os.path.join(TOP_DIR_PATH, \"src\")\nsys.path.insert(0, SRC_DIR_PATH)\n\nimport watchdog.version  # noqa: E402\n\nPROJECT_NAME = \"watchdog\"\nAUTHOR_NAME = \"Mickaël Schoentgen & contributors\"\nCOPYRIGHT = f\"2010-2025, {AUTHOR_NAME}\"\n\n\n# -- General configuration -----------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.ifconfig\",\n    \"sphinx.ext.viewcode\",\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 = PROJECT_NAME\ncopyright = COPYRIGHT\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = watchdog.version.VERSION_STRING\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"pyramid\"\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"%sdoc\" % PROJECT_NAME\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\n        \"index\",\n        \"%s.tex\" % PROJECT_NAME,\n        \"%s Documentation\" % PROJECT_NAME,\n        AUTHOR_NAME,\n        \"manual\",\n    ),\n]\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (\"index\", PROJECT_NAME, \"%s Documentation\" % PROJECT_NAME, [AUTHOR_NAME], 1)\n]\n\n\n# -- Options for Epub output ---------------------------------------------------\n\n# Bibliographic Dublin Core info.\nepub_title = PROJECT_NAME\nepub_author = AUTHOR_NAME\nepub_publisher = AUTHOR_NAME\nepub_copyright = COPYRIGHT\n"
  },
  {
    "path": "docs/source/examples/__init__.py",
    "content": ""
  },
  {
    "path": "docs/source/examples/logger.py",
    "content": "import sys\nimport time\n\nfrom watchdog.observers import Observer\nfrom watchdog.tricks import LoggerTrick\n\nevent_handler = LoggerTrick()\nobserver = Observer()\nobserver.schedule(event_handler, sys.argv[1], recursive=True)\nobserver.start()\ntry:\n    while True:\n        time.sleep(1)\nfinally:\n    observer.stop()\n    observer.join()\n"
  },
  {
    "path": "docs/source/examples/patterns.py",
    "content": "import logging\nimport sys\nimport time\n\nfrom watchdog.events import FileSystemEvent, PatternMatchingEventHandler\nfrom watchdog.observers import Observer\n\nlogging.basicConfig(level=logging.DEBUG)\n\n\nclass MyEventHandler(PatternMatchingEventHandler):\n    def on_any_event(self, event: FileSystemEvent) -> None:\n        logging.debug(event)\n\n\nevent_handler = MyEventHandler(\n    patterns=[\"**/*.py\", \"**/*.pyc\"], ignore_patterns=[\"version.py\"], ignore_directories=True\n)\nobserver = Observer()\nobserver.schedule(event_handler, sys.argv[1], recursive=True)\nobserver.start()\ntry:\n    while True:\n        time.sleep(1)\nfinally:\n    observer.stop()\n    observer.join()\n"
  },
  {
    "path": "docs/source/examples/simple.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport sys\nimport time\n\nfrom watchdog import events\nfrom watchdog.observers import Observer\n\nlogging.basicConfig(level=logging.DEBUG)\n\n\nclass MyEventHandler(events.FileSystemEventHandler):\n    def catch_all_handler(self, event: events.FileSystemEvent) -> None:\n        logging.debug(event)\n\n    def on_moved(self, event: events.DirMovedEvent | events.FileMovedEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_created(self, event: events.DirCreatedEvent | events.FileCreatedEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_deleted(self, event: events.DirDeletedEvent | events.FileDeletedEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_modified(self, event: events.DirModifiedEvent | events.FileModifiedEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_closed(self, event: events.FileClosedEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_closed_no_write(self, event: events.FileClosedNoWriteEvent) -> None:\n        self.catch_all_handler(event)\n\n    def on_opened(self, event: events.FileOpenedEvent) -> None:\n        self.catch_all_handler(event)\n\n\npath = sys.argv[1]\n\nevent_handler = MyEventHandler()\nobserver = Observer()\nobserver.schedule(event_handler, path, recursive=True)\nobserver.start()\ntry:\n    while True:\n        time.sleep(1)\nfinally:\n    observer.stop()\n    observer.join()\n"
  },
  {
    "path": "docs/source/examples/tricks.json",
    "content": "[\n    {\n        \"watchdog.tricks.LoggerTrick\": {\n            \"patterns\": [\n                \"**/*.py\",\n                \"**/*.js\"\n            ]\n        }\n    },\n    {\n        \"watchmedo_webtricks.GoogleClosureTrick\": {\n            \"scripts\": {\n                \"index-page\": [\n                    \"app/static/js/vendor/jquery.js\",\n                    \"app/static/js/base.js\",\n                    \"app/static/js/index-page.js\"],\n                \"about-page\": [\n                    \"app/static/js/vendor/jquery.js\",\n                    \"app/static/js/base.js\",\n                    \"app/static/js/about-page.js\"]\n            },\n            \"suffix\": \".min.js\",\n            \"source_directory\": \"app/static/js/\",\n            \"hash_names\": true,\n            \"patterns\": [\"**/*.js\"],\n            \"destination_directory\": \"app/public/js/\",\n            \"compilation_level\": \"advanced\",\n            \"mappings_module\": \"app/javascript_mappings.json\"\n        }\n    }\n]\n"
  },
  {
    "path": "docs/source/examples/tricks.yaml",
    "content": "tricks:\n- watchdog.tricks.LoggerTrick:\n    patterns: [\"*.py\", \"*.js\"]\n- watchmedo_webtricks.GoogleClosureTrick:\n    patterns: ['*.js']\n    hash_names: true\n    mappings_format: json                  # json|yaml|python\n    mappings_module: app/javascript_mappings\n    suffix: .min.js\n    compilation_level: advanced            # simple|advanced\n    source_directory: app/static/js/\n    destination_directory: app/public/js/\n    files:\n      index-page:\n      - app/static/js/vendor/jquery.js\n      - app/static/js/base.js\n      - app/static/js/index-page.js\n      about-page:\n      - app/static/js/vendor/jquery.js\n      - app/static/js/base.js\n      - app/static/js/about-page.js\n"
  },
  {
    "path": "docs/source/global.rst.inc",
    "content": ".. Global includes, substitutions, and common links.\n\n.. |author_name| replace:: Mickaël Schoentgen\n.. |author_email| replace:: contact@tiger-222.fr\n.. |copyright| replace:: Copyright 2010-2025 Mickaël Schoentgen & contributors.\n.. |project_name| replace:: ``watchdog``\n.. |project_version| replace:: 7.0.0\n\n.. _issue tracker: https://github.com/gorakhargosh/watchdog/issues\n.. _code repository: https://github.com/gorakhargosh/watchdog\n\n.. _kqueue: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2\n.. _FSEvents: https://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/FSEvents_ProgGuide/Introduction/Introduction.html\n.. _inotify: https://linux.die.net/man/7/inotify\n.. _macOS File System Monitoring Performance Guidelines: https://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html\n.. _ReadDirectoryChangesW: https://docs.microsoft.com/windows/win32/api/winbase/nf-winbase-readdirectorychangesw\n\n.. _file.monitor: https://github.com/pke/file.monitor\n.. _fsmonitor: https://github.com/shaurz/fsmonitor\n.. _git: https://git-scm.org/\n.. _github: https://github.com/\n.. _guard: https://github.com/guard/guard\n.. _inotify-tools: https://github.com/rvoicilas/inotify-tools\n.. _jnotify: http://jnotify.sourceforge.net/\n.. _pip: https://pypi.python.org/pypi/pip\n.. _pnotify: http://mark.heily.com/pnotify\n.. _pyfilesystem: https://github.com/PyFilesystem/pyfilesystem\n.. _pyinotify: https://github.com/seb-m/pyinotify\n.. _Python: https://python.org\n.. _PyYAML: https://www.pyyaml.org/\n.. _treewatcher: https://github.com/jbd/treewatcher\n.. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471\n.. _XCode: https://developer.apple.com/technologies/tools/xcode.html\n"
  },
  {
    "path": "docs/source/hacking.rst",
    "content": ".. include:: global.rst.inc\n\n.. _hacking:\n\nContributing\n============\nWelcome hacker! So you have got something you would like to see in\n|project_name|? Whee. This document will help you get started.\n\nImportant URLs\n--------------\n|project_name| uses git_ to track code history and hosts its `code repository`_\nat github_. The `issue tracker`_ is where you can file bug reports and request\nfeatures or enhancements to |project_name|.\n\nBefore you start\n----------------\nEnsure your system has the following programs and libraries installed before\nbeginning to hack:\n\n1. Python_\n2. git_\n3. XCode_ (on macOS)\n\nSetting up the Work Environment\n-------------------------------\n\nSteps to setting up a clean environment:\n\n1. Fork the `code repository`_ into your github_ account.\n\n2. Clone fork and create virtual environment:\n\n.. code:: bash\n\n    $ git clone https://github.com/gorakhargosh/watchdog.git\n    $ cd watchdog\n    $ python -m venv venv\n\n3. Linux\n\n.. code:: bash\n\n    $ . venv/bin/activate\n    (venv)$ python -m pip install -e '.'\n\n4. Windows\n\n.. code:: batch\n\n    > venv\\Scripts\\activate\n    (venv)> python -m pip install -e '.'\n\nThat's it with the setup. Now you're ready to hack on |project_name|.\n\nHappy hacking!\n"
  },
  {
    "path": "docs/source/index.rst",
    "content": ".. watchdog documentation master file, created by\n   sphinx-quickstart on Tue Nov 30 00:43:58 2010.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\n.. include:: global.rst.inc\n\n\nWatchdog\n========\n\nPython API library and shell utilities to monitor file system events.\n\nWorks on 3.9+.\n\nDirectory monitoring made easy with\n-----------------------------------\n\n* A cross-platform API.\n\n* A shell tool to run commands in response to directory changes.\n\nGet started quickly with a simple example in :ref:`quickstart`.\n\nEasy installation\n-----------------\nYou can use pip_ to install |project_name| quickly and easily::\n\n    $ python -m pip install -U watchdog\n\nNeed more help with installing? See :ref:`installation`.\n\n\nUser's Guide\n============\n\n.. toctree::\n   :maxdepth: 2\n\n   installation\n   quickstart\n   api\n   hacking\n\nContribute\n==========\n\nFound a bug in or want a feature added to |project_name|?\nYou can fork the official `code repository`_ or file an issue ticket\nat the `issue tracker`_. You may also want to refer to :ref:`hacking`\nfor information about contributing code or documentation to |project_name|.\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/source/installation.rst",
    "content": ".. include:: global.rst.inc\n\n.. _installation:\n\nInstallation\n============\n|project_name| requires 3.9+ to work. See a list of :ref:`installation-dependencies`.\n\nInstalling from PyPI using pip\n------------------------------\n\n.. parsed-literal::\n\n    $ python -m pip install -U |project_name|\n\n    # or to install the watchmedo utility:\n    $ python -m pip install -U '|project_name|\\[watchmedo]'\n\nInstalling from source tarballs\n-------------------------------\n\n.. parsed-literal::\n\n    $ wget -c https://pypi.python.org/packages/source/w/watchdog/watchdog-|project_version|.tar.gz\n    $ tar zxvf |project_name|-|project_version|.tar.gz\n    $ cd |project_name|-|project_version|\n    $ python -m pip install -e .\n\n    # or to install the watchmedo utility:\n    $ python -m pip install -e '.[watchmedo]'\n\nInstalling from the code repository\n-----------------------------------\n\n::\n\n    $ git clone --recursive git://github.com/gorakhargosh/watchdog.git\n    $ cd watchdog\n    $ python -m pip install -e .\n\n    # or to install the watchmedo utility:\n    $ python -m pip install -e '.[watchmedo]'\n\n.. _installation-dependencies:\n\nDependencies\n------------\n\n|project_name| depends on many libraries to do its job. The following is\na list of dependencies you need based on the operating system you are\nusing.\n\n+---------------------+-------------+-------------+--------+-------------+\n| Operating system    |   Windows   |  Linux 2.6  | macOS  |     BSD     |\n| Dependency (row)    |             |             | Darwin |             |\n+=====================+=============+=============+========+=============+\n| XCode_              |             |             |  Yes   |             |\n+---------------------+-------------+-------------+--------+-------------+\n\nThe following is a list of dependencies you need based on the operating system you are\nusing the ``watchmedo`` utility.\n\n+---------------------+-------------+-------------+--------+-------------+\n| Operating system    |   Windows   |  Linux 2.6  | macOS  |     BSD     |\n| Dependency (row)    |             |             | Darwin |             |\n+=====================+=============+=============+========+=============+\n| PyYAML_             |     Yes     |     Yes     |  Yes   |     Yes     |\n+---------------------+-------------+-------------+--------+-------------+\n\nSupported Platforms (and Caveats)\n---------------------------------\n|project_name| uses native APIs as much as possible falling back\nto polling the disk periodically to compare directory snapshots\nonly when it cannot use an API natively-provided by the underlying\noperating system. The following operating systems are currently\nsupported:\n\n.. WARNING:: Differences between behaviors of these native API\n             are noted below.\n\nLinux 2.6+\n    Linux kernel version 2.6 and later come with an API called inotify_\n    that programs can use to monitor file system events.\n\n    .. NOTE:: On most systems the maximum number of watches that can be\n              created per user is limited to ``8192``. |project_name| needs one\n              per directory to monitor. To change this limit, edit\n              ``/etc/sysctl.conf`` and add::\n\n                  fs.inotify.max_user_watches=16384\n\nmacOS\n    The Darwin kernel/OS X API maintains two ways to monitor directories\n    for file system events:\n\n    * kqueue_\n    * FSEvents_\n\n    |project_name| can use whichever one is available, preferring\n    FSEvents over ``kqueue(2)``. ``kqueue(2)`` uses open file descriptors for monitoring\n    and the current implementation uses\n    `macOS File System Monitoring Performance Guidelines`_ to open\n    these file descriptors only to monitor events, thus allowing\n    OS X to unmount volumes that are being watched without locking them.\n\n    .. NOTE:: More information about how |project_name| uses ``kqueue(2)`` is noted\n              in `BSD Unix variants`_. Much of this information applies to\n              macOS as well.\n\n_`BSD Unix variants`\n    BSD variants come with kqueue_ which programs can use to monitor\n    changes to open file descriptors. Because of the way ``kqueue(2)`` works,\n    |project_name| needs to open these files and directories in read-only\n    non-blocking mode and keep books about them.\n\n    |project_name| will automatically open file descriptors for all\n    new files/directories created and close those for which are deleted.\n\n    .. NOTE:: The maximum number of open file descriptor per process limit\n              on your operating system can hinder |project_name|'s ability to\n              monitor files.\n\n              You should ensure this limit is set to at least **1024**\n              (or a value suitable to your usage). The following command\n              appended to your ``~/.profile`` configuration file does\n              this for you::\n\n                  ulimit -n 1024\n\nWindows Vista and later\n    The Windows API provides the ReadDirectoryChangesW_. |project_name|\n    currently contains implementation for a synchronous approach requiring\n    additional API functionality only available in Windows Vista and later.\n\n    .. NOTE:: Since renaming is not the same operation as movement\n              on Windows, |project_name| tries hard to convert renames to\n              movement events. Also, because the ReadDirectoryChangesW_\n              API function returns rename/movement events for directories\n              even before the underlying I/O is complete, |project_name|\n              may not be able to completely scan the moved directory\n              in order to successfully queue movement events for\n              files and directories within it.\n\n    .. NOTE:: Since the Windows API does not provide information about whether\n              an object is a file or a directory, delete events for directories\n              may be reported as a file deleted event.\n\nOS Independent Polling\n    |project_name| also includes a fallback-implementation that polls\n    watched directories for changes by periodically comparing snapshots\n    of the directory tree.\n"
  },
  {
    "path": "docs/source/quickstart.rst",
    "content": ".. include:: global.rst.inc\n\n.. _quickstart:\n\nQuickstart\n==========\nBelow we present a simple example that monitors the current directory\nrecursively (which means, it will traverse any sub-directories)\nto detect changes. Here is what we will do with the API:\n\n1. Create an instance of the :class:`watchdog.observers.Observer` thread class.\n\n2. Implement a subclass of :class:`watchdog.events.FileSystemEventHandler`.\n\n3. Schedule monitoring a few paths with the observer instance\n   attaching the event handler.\n\n4. Start the observer thread and wait for it generate events\n   without blocking our main thread.\n\nBy default, an :class:`watchdog.observers.Observer` instance will not monitor\nsub-directories. By passing ``recursive=True`` in the call to\n:meth:`watchdog.observers.Observer.schedule` monitoring\nentire directory trees is ensured.\n\n\nA Simple Example\n----------------\nThe following example program will monitor the current directory recursively for\nfile system changes and simply print them to the console::\n\n    import time\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n    from watchdog.observers import Observer\n\n\n    class MyEventHandler(FileSystemEventHandler):\n        def on_any_event(self, event: FileSystemEvent) -> None:\n            print(event)\n\n\n    event_handler = MyEventHandler()\n    observer = Observer()\n    observer.schedule(event_handler, \".\", recursive=True)\n    observer.start()\n    try:\n        while True:\n            time.sleep(1)\n    finally:\n        observer.stop()\n        observer.join()\n\nTo stop the program, press Control-C.\n\nAlternatively, you can use the observer as a context manager for cleaner code::\n\n    import time\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n    from watchdog.observers import Observer\n\n\n    class MyEventHandler(FileSystemEventHandler):\n        def on_any_event(self, event: FileSystemEvent) -> None:\n            print(event)\n\n\n    event_handler = MyEventHandler()\n    observer = Observer()\n    observer.schedule(event_handler, \".\", recursive=True)\n\n    with observer:\n        while True:\n            time.sleep(1)\n\nThe context manager automatically handles starting and stopping the observer,\nensuring proper cleanup even if an exception occurs.\n\nTyping\n------\n\nIf you are using type annotations it is important to note that\n:class:`watchdog.observers.Observer` is not actually a class; it is a variable that\nhold the \"best\" observer class available on your platform.\n\nIn order to correctly type your own code your should use\n:class:`watchdog.observers.api.BaseObserver`. For example::\n\n    from watchdog.observers import Observer\n    from watchdog.observers.api import BaseObserver\n\n\n    def my_func(obs: BaseObserver) -> None:\n        # Do something with obs\n        pass\n\n    observer: BaseObserver = Observer()\n    my_func(observer)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    # Pin to an older setuptools version for PyPy only, due to pypa/distutils#283\n    \"setuptools; platform_python_implementation != 'PyPy'\",\n    \"setuptools<72.2; platform_python_implementation == 'PyPy'\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.cibuildwheel]\nenable = [\"cpython-freethreading\"]\nskip = \"*t*_i686\"\n\n[tool.coverage.report]\nexclude_also = [\n    \"if TYPE_CHECKING:\",\n    \"if __name__ == __main__:\",\n]\n\n[tool.mypy]\n# Ensure we know what we do\nwarn_redundant_casts = true\nwarn_unused_ignores = true\nwarn_unused_configs = true\n\n# Imports management\nignore_missing_imports = true\nfollow_imports = \"skip\"\n\n# Ensure full coverage\ndisallow_untyped_defs = true\ndisallow_incomplete_defs = true\ndisallow_untyped_calls = true\n\n# Restrict dynamic typing (a little)\n# e.g. `x: List[Any]` or x: List`\n# disallow_any_generics = true\n\nstrict_equality = true\n\n[tool.pytest.ini_options]\npythonpath = \"src\"\naddopts = \"\"\"\n    --showlocals\n    -vvv\n    --cov=watchdog\n    --cov-report=term-missing:skip-covered\n\"\"\"\n\n[tool.ruff]\nline-length = 120\nindent-width = 4\ntarget-version = \"py39\"\n\n[tool.ruff.lint]\nextend-select = [\"ALL\"]\nignore = [\n    \"ARG\",\n    \"ANN\",  # TODO\n    \"B023\",  # TODO\n    \"BLE001\",\n    \"C90\",\n    \"COM812\",\n    \"D\",\n    \"EM101\",\n    \"EM102\",\n    \"FIX\",\n    \"ISC001\",\n    \"PERF203\",\n    \"PL\",\n    \"PTH\",  # TODO?\n    \"S\",\n    \"TD\",\n]\nfixable = [\"ALL\"]\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\nskip-magic-trailing-comma = false\nline-ending = \"auto\"\ndocstring-code-format = true\n"
  },
  {
    "path": "requirements-tests.txt",
    "content": "eventlet==0.37.0; python_version < \"3.13\"\nflaky==3.8.1\npytest==8.3.3\npytest-cov==6.0.0\npytest-timeout==2.3.1\nruff==0.7.1\nsphinx==7.4.7; python_version <= \"3.9\"\nsphinx==8.1.3; python_version > \"3.9\"\nmypy==1.13.0\ntypes-PyYAML==6.0.12.20240917\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nproject_urls =\n    Documentation=https://python-watchdog.readthedocs.io/en/stable/\n    Source=https://github.com/gorakhargosh/watchdog/\n    Issues=https://github.com/gorakhargosh/watchdog/issues\n    Changelog=https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst\n\n[build_sphinx]\nsource-dir = docs/source\nbuild-dir  = docs/build\nall_files  = 1\n\n[upload_sphinx]\n# Requires sphinx-pypi-upload to work.\nupload-dir = docs/build/html\n"
  },
  {
    "path": "setup.py",
    "content": "import importlib.util\nimport sys\nimport os\nimport os.path\nfrom platform import machine\nfrom setuptools import setup, find_packages\nfrom setuptools.extension import Extension\nfrom setuptools.command.build_ext import build_ext\n\nSRC_DIR = \"src\"\nWATCHDOG_PKG_DIR = os.path.join(SRC_DIR, \"watchdog\")\n\n# Load the module version\nspec = importlib.util.spec_from_file_location(\n    \"version\", os.path.join(WATCHDOG_PKG_DIR, \"version.py\")\n)\nversion = importlib.util.module_from_spec(spec)\nspec.loader.exec_module(version)\n\n# Ignored Apple devices on which compiling watchdog_fsevents.c would fail.\n# The FORCE_MACOS_MACHINE envar, when set to 1, will force the compilation.\n_apple_devices = (\"appletv\", \"iphone\", \"ipod\", \"ipad\", \"watch\")\nis_macos = sys.platform == \"darwin\" and not machine().lower().startswith(_apple_devices)\n\next_modules = []\nif is_macos or os.getenv(\"FORCE_MACOS_MACHINE\", \"0\") == \"1\":\n    ext_modules = [\n        Extension(\n            name=\"_watchdog_fsevents\",\n            sources=[\n                \"src/watchdog_fsevents.c\",\n            ],\n            libraries=[\"m\"],\n            define_macros=[\n                (\"WATCHDOG_VERSION_STRING\", '\"' + version.VERSION_STRING + '\"'),\n                (\"WATCHDOG_VERSION_MAJOR\", version.VERSION_MAJOR),\n                (\"WATCHDOG_VERSION_MINOR\", version.VERSION_MINOR),\n                (\"WATCHDOG_VERSION_BUILD\", version.VERSION_BUILD),\n            ],\n            extra_link_args=[\n                \"-framework\",\n                \"CoreFoundation\",\n                \"-framework\",\n                \"CoreServices\",\n            ],\n            extra_compile_args=[\n                \"-std=c99\",\n                \"-pedantic\",\n                \"-Wall\",\n                \"-Wextra\",\n                \"-fPIC\",\n                # Issue #620\n                \"-Wno-nullability-completeness\",\n                # Issue #628\n                \"-Wno-nullability-extension\",\n                \"-Wno-newline-eof\",\n                # required w/Xcode 5.1+ and above because of '-mno-fused-madd'\n                \"-Wno-error=unused-command-line-argument\",\n            ],\n        ),\n    ]\n\nextras_require = {\n    \"watchmedo\": [\"PyYAML>=3.10\"],\n}\n\nwith open(\"README.rst\", encoding=\"utf-8\") as f:\n    readme = f.read()\n\nwith open(\"changelog.rst\", encoding=\"utf-8\") as f:\n    changelog = f.read()\n\nsetup(\n    name=\"watchdog\",\n    version=version.VERSION_STRING,\n    description=\"Filesystem events monitoring\",\n    long_description=readme + \"\\n\\n\" + changelog,\n    long_description_content_type=\"text/x-rst\",\n    author=\"Mickaël Schoentgen\",\n    author_email=\"contact@tiger-222.fr\",\n    license=\"Apache-2.0\",\n    url=\"https://github.com/gorakhargosh/watchdog\",\n    keywords=\" \".join(\n        [\n            \"python\",\n            \"filesystem\",\n            \"monitoring\",\n            \"monitor\",\n            \"FSEvents\",\n            \"kqueue\",\n            \"inotify\",\n            \"ReadDirectoryChangesW\",\n            \"polling\",\n            \"DirectorySnapshot\",\n        ]\n    ),\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Intended Audience :: Developers\",\n        \"Intended Audience :: System Administrators\",\n        \"License :: OSI Approved :: Apache Software License\",\n        \"Natural Language :: English\",\n        \"Operating System :: POSIX :: Linux\",\n        \"Operating System :: MacOS :: MacOS X\",\n        \"Operating System :: POSIX :: BSD\",\n        \"Operating System :: Microsoft :: Windows :: Windows Vista\",\n        \"Operating System :: Microsoft :: Windows :: Windows 7\",\n        \"Operating System :: Microsoft :: Windows :: Windows 8\",\n        \"Operating System :: Microsoft :: Windows :: Windows 8.1\",\n        \"Operating System :: Microsoft :: Windows :: Windows 10\",\n        \"Operating System :: Microsoft :: Windows :: Windows 11\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3 :: Only\",\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 :: Implementation :: CPython\",\n        \"Programming Language :: Python :: Implementation :: PyPy\",\n        \"Programming Language :: C\",\n        \"Topic :: Software Development :: Libraries\",\n        \"Topic :: System :: Monitoring\",\n        \"Topic :: System :: Filesystems\",\n        \"Topic :: Utilities\",\n    ],\n    package_dir={\"\": SRC_DIR},\n    packages=find_packages(SRC_DIR),\n    include_package_data=True,\n    extras_require=extras_require,\n    cmdclass={\n        \"build_ext\": build_ext,\n    },\n    ext_modules=ext_modules,\n    entry_points={\n        \"console_scripts\": [\n            \"watchmedo = watchdog.watchmedo:main [watchmedo]\",\n        ]\n    },\n    python_requires=\">=3.9\",\n    zip_safe=False,\n)\n"
  },
  {
    "path": "src/pythoncapi_compat.h",
    "content": "// Header file providing new C API functions to old Python versions.\n//\n// File distributed under the Zero Clause BSD (0BSD) license.\n// Copyright Contributors to the pythoncapi_compat project.\n//\n// Homepage:\n// https://github.com/python/pythoncapi_compat\n//\n// Latest version:\n// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h\n//\n// SPDX-License-Identifier: 0BSD\n\n#ifndef PYTHONCAPI_COMPAT\n#define PYTHONCAPI_COMPAT\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <Python.h>\n#include <stddef.h>               // offsetof()\n\n// Python 3.11.0b4 added PyFrame_Back() to Python.h\n#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION)\n#  include \"frameobject.h\"        // PyFrameObject, PyFrame_GetBack()\n#endif\n#if PY_VERSION_HEX < 0x030C00A3\n#  include <structmember.h>       // T_SHORT, READONLY\n#endif\n\n\n#ifndef _Py_CAST\n#  define _Py_CAST(type, expr) ((type)(expr))\n#endif\n\n// Static inline functions should use _Py_NULL rather than using directly NULL\n// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer,\n// _Py_NULL is defined as nullptr.\n#ifndef _Py_NULL\n#  if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \\\n          || (defined(__cplusplus) && __cplusplus >= 201103)\n#    define _Py_NULL nullptr\n#  else\n#    define _Py_NULL NULL\n#  endif\n#endif\n\n// Cast argument to PyObject* type.\n#ifndef _PyObject_CAST\n#  define _PyObject_CAST(op) _Py_CAST(PyObject*, op)\n#endif\n\n#ifndef Py_BUILD_ASSERT\n#  define Py_BUILD_ASSERT(cond) \\\n        do { \\\n            (void)sizeof(char [1 - 2 * !(cond)]); \\\n        } while(0)\n#endif\n\n\n// bpo-42262 added Py_NewRef() to Python 3.10.0a3\n#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef)\nstatic inline PyObject* _Py_NewRef(PyObject *obj)\n{\n    Py_INCREF(obj);\n    return obj;\n}\n#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj))\n#endif\n\n\n// bpo-42262 added Py_XNewRef() to Python 3.10.0a3\n#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef)\nstatic inline PyObject* _Py_XNewRef(PyObject *obj)\n{\n    Py_XINCREF(obj);\n    return obj;\n}\n#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj))\n#endif\n\n\n// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4\n#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT)\nstatic inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt)\n{\n    ob->ob_refcnt = refcnt;\n}\n#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt)\n#endif\n\n\n// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2.\n// It is excluded from the limited C API.\n#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API)\n#define Py_SETREF(dst, src)                                     \\\n    do {                                                        \\\n        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \\\n        PyObject *_tmp_dst = (*_tmp_dst_ptr);                   \\\n        *_tmp_dst_ptr = _PyObject_CAST(src);                    \\\n        Py_DECREF(_tmp_dst);                                    \\\n    } while (0)\n\n#define Py_XSETREF(dst, src)                                    \\\n    do {                                                        \\\n        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \\\n        PyObject *_tmp_dst = (*_tmp_dst_ptr);                   \\\n        *_tmp_dst_ptr = _PyObject_CAST(src);                    \\\n        Py_XDECREF(_tmp_dst);                                   \\\n    } while (0)\n#endif\n\n\n// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse()\n// to Python 3.10.0b1.\n#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is)\n#  define Py_Is(x, y) ((x) == (y))\n#endif\n#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone)\n#  define Py_IsNone(x) Py_Is(x, Py_None)\n#endif\n#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue)\n#  define Py_IsTrue(x) Py_Is(x, Py_True)\n#endif\n#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse)\n#  define Py_IsFalse(x) Py_Is(x, Py_False)\n#endif\n\n\n// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4\n#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE)\nstatic inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type)\n{\n    ob->ob_type = type;\n}\n#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type)\n#endif\n\n\n// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4\n#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE)\nstatic inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size)\n{\n    ob->ob_size = size;\n}\n#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size)\n#endif\n\n\n// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1\n#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION)\nstatic inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)\n{\n    assert(frame != _Py_NULL);\n    assert(frame->f_code != _Py_NULL);\n    return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code));\n}\n#endif\n\nstatic inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame)\n{\n    PyCodeObject *code = PyFrame_GetCode(frame);\n    Py_DECREF(code);\n    return code;\n}\n\n\n// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1\n#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)\nstatic inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame)\n{\n    assert(frame != _Py_NULL);\n    return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back));\n}\n#endif\n\n#if !defined(PYPY_VERSION)\nstatic inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame)\n{\n    PyFrameObject *back = PyFrame_GetBack(frame);\n    Py_XDECREF(back);\n    return back;\n}\n#endif\n\n\n// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7\n#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyFrame_GetLocals(PyFrameObject *frame)\n{\n#if PY_VERSION_HEX >= 0x030400B1\n    if (PyFrame_FastToLocalsWithError(frame) < 0) {\n        return NULL;\n    }\n#else\n    PyFrame_FastToLocals(frame);\n#endif\n    return Py_NewRef(frame->f_locals);\n}\n#endif\n\n\n// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7\n#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame)\n{\n    return Py_NewRef(frame->f_globals);\n}\n#endif\n\n\n// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7\n#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame)\n{\n    return Py_NewRef(frame->f_builtins);\n}\n#endif\n\n\n// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1\n#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)\nstatic inline int PyFrame_GetLasti(PyFrameObject *frame)\n{\n#if PY_VERSION_HEX >= 0x030A00A7\n    // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset,\n    // not a bytes offset anymore. Python uses 16-bit \"wordcode\" (2 bytes)\n    // instructions.\n    if (frame->f_lasti < 0) {\n        return -1;\n    }\n    return frame->f_lasti * 2;\n#else\n    return frame->f_lasti;\n#endif\n}\n#endif\n\n\n// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2\n#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name)\n{\n    PyObject *locals, *value;\n\n    locals = PyFrame_GetLocals(frame);\n    if (locals == NULL) {\n        return NULL;\n    }\n#if PY_VERSION_HEX >= 0x03000000\n    value = PyDict_GetItemWithError(locals, name);\n#else\n    value = _PyDict_GetItemWithError(locals, name);\n#endif\n    Py_DECREF(locals);\n\n    if (value == NULL) {\n        if (PyErr_Occurred()) {\n            return NULL;\n        }\n#if PY_VERSION_HEX >= 0x03000000\n        PyErr_Format(PyExc_NameError, \"variable %R does not exist\", name);\n#else\n        PyErr_SetString(PyExc_NameError, \"variable does not exist\");\n#endif\n        return NULL;\n    }\n    return Py_NewRef(value);\n}\n#endif\n\n\n// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2\n#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)\nstatic inline PyObject*\nPyFrame_GetVarString(PyFrameObject *frame, const char *name)\n{\n    PyObject *name_obj, *value;\n#if PY_VERSION_HEX >= 0x03000000\n    name_obj = PyUnicode_FromString(name);\n#else\n    name_obj = PyString_FromString(name);\n#endif\n    if (name_obj == NULL) {\n        return NULL;\n    }\n    value = PyFrame_GetVar(frame, name_obj);\n    Py_DECREF(name_obj);\n    return value;\n}\n#endif\n\n\n// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5\n#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000)\nstatic inline PyInterpreterState *\nPyThreadState_GetInterpreter(PyThreadState *tstate)\n{\n    assert(tstate != _Py_NULL);\n    return tstate->interp;\n}\n#endif\n\n\n// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1\n#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)\nstatic inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate)\n{\n    assert(tstate != _Py_NULL);\n    return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame));\n}\n#endif\n\n#if !defined(PYPY_VERSION)\nstatic inline PyFrameObject*\n_PyThreadState_GetFrameBorrow(PyThreadState *tstate)\n{\n    PyFrameObject *frame = PyThreadState_GetFrame(tstate);\n    Py_XDECREF(frame);\n    return frame;\n}\n#endif\n\n\n// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5\n#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION)\nstatic inline PyInterpreterState* PyInterpreterState_Get(void)\n{\n    PyThreadState *tstate;\n    PyInterpreterState *interp;\n\n    tstate = PyThreadState_GET();\n    if (tstate == _Py_NULL) {\n        Py_FatalError(\"GIL released (tstate is NULL)\");\n    }\n    interp = tstate->interp;\n    if (interp == _Py_NULL) {\n        Py_FatalError(\"no current interpreter\");\n    }\n    return interp;\n}\n#endif\n\n\n// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6\n#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)\nstatic inline uint64_t PyThreadState_GetID(PyThreadState *tstate)\n{\n    assert(tstate != _Py_NULL);\n    return tstate->id;\n}\n#endif\n\n// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2\n#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)\nstatic inline void PyThreadState_EnterTracing(PyThreadState *tstate)\n{\n    tstate->tracing++;\n#if PY_VERSION_HEX >= 0x030A00A1\n    tstate->cframe->use_tracing = 0;\n#else\n    tstate->use_tracing = 0;\n#endif\n}\n#endif\n\n// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2\n#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)\nstatic inline void PyThreadState_LeaveTracing(PyThreadState *tstate)\n{\n    int use_tracing = (tstate->c_tracefunc != _Py_NULL\n                       || tstate->c_profilefunc != _Py_NULL);\n    tstate->tracing--;\n#if PY_VERSION_HEX >= 0x030A00A1\n    tstate->cframe->use_tracing = use_tracing;\n#else\n    tstate->use_tracing = use_tracing;\n#endif\n}\n#endif\n\n\n// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1\n// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11\n#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1\nstatic inline PyObject* PyObject_CallNoArgs(PyObject *func)\n{\n    return PyObject_CallFunctionObjArgs(func, NULL);\n}\n#endif\n\n\n// bpo-39245 made PyObject_CallOneArg() public (previously called\n// _PyObject_CallOneArg) in Python 3.9.0a4\n// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11\n#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4\nstatic inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg)\n{\n    return PyObject_CallFunctionObjArgs(func, arg, NULL);\n}\n#endif\n\n\n// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3\n#if PY_VERSION_HEX < 0x030A00A3\nstatic inline int\nPyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)\n{\n    int res;\n\n    if (!value && !PyErr_Occurred()) {\n        // PyModule_AddObject() raises TypeError in this case\n        PyErr_SetString(PyExc_SystemError,\n                        \"PyModule_AddObjectRef() must be called \"\n                        \"with an exception raised if value is NULL\");\n        return -1;\n    }\n\n    Py_XINCREF(value);\n    res = PyModule_AddObject(module, name, value);\n    if (res < 0) {\n        Py_XDECREF(value);\n    }\n    return res;\n}\n#endif\n\n\n// bpo-40024 added PyModule_AddType() to Python 3.9.0a5\n#if PY_VERSION_HEX < 0x030900A5\nstatic inline int PyModule_AddType(PyObject *module, PyTypeObject *type)\n{\n    const char *name, *dot;\n\n    if (PyType_Ready(type) < 0) {\n        return -1;\n    }\n\n    // inline _PyType_Name()\n    name = type->tp_name;\n    assert(name != _Py_NULL);\n    dot = strrchr(name, '.');\n    if (dot != _Py_NULL) {\n        name = dot + 1;\n    }\n\n    return PyModule_AddObjectRef(module, name, _PyObject_CAST(type));\n}\n#endif\n\n\n// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6.\n// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2.\n#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)\nstatic inline int PyObject_GC_IsTracked(PyObject* obj)\n{\n    return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj));\n}\n#endif\n\n// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6.\n// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final.\n#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION)\nstatic inline int PyObject_GC_IsFinalized(PyObject *obj)\n{\n    PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1;\n    return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc));\n}\n#endif\n\n\n// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4\n#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE)\nstatic inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {\n    return Py_TYPE(ob) == type;\n}\n#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type)\n#endif\n\n\n// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7.\n// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1.\n// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal\n// C API: Python 3.11a2-3.11a6 versions are not supported.\n#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)\nstatic inline int PyFloat_Pack2(double x, char *p, int le)\n{ return _PyFloat_Pack2(x, (unsigned char*)p, le); }\n\nstatic inline double PyFloat_Unpack2(const char *p, int le)\n{ return _PyFloat_Unpack2((const unsigned char *)p, le); }\n#endif\n\n\n// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and\n// PyFloat_Unpack8() to Python 3.11a7.\n// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4()\n// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions\n// are not supported.\n#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)\nstatic inline int PyFloat_Pack4(double x, char *p, int le)\n{ return _PyFloat_Pack4(x, (unsigned char*)p, le); }\n\nstatic inline int PyFloat_Pack8(double x, char *p, int le)\n{ return _PyFloat_Pack8(x, (unsigned char*)p, le); }\n\nstatic inline double PyFloat_Unpack4(const char *p, int le)\n{ return _PyFloat_Unpack4((const unsigned char *)p, le); }\n\nstatic inline double PyFloat_Unpack8(const char *p, int le)\n{ return _PyFloat_Unpack8((const unsigned char *)p, le); }\n#endif\n\n\n// gh-92154 added PyCode_GetCode() to Python 3.11.0b1\n#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyCode_GetCode(PyCodeObject *code)\n{\n    return Py_NewRef(code->co_code);\n}\n#endif\n\n\n// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1\n#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyCode_GetVarnames(PyCodeObject *code)\n{\n    return Py_NewRef(code->co_varnames);\n}\n#endif\n\n// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1\n#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyCode_GetFreevars(PyCodeObject *code)\n{\n    return Py_NewRef(code->co_freevars);\n}\n#endif\n\n// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1\n#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)\nstatic inline PyObject* PyCode_GetCellvars(PyCodeObject *code)\n{\n    return Py_NewRef(code->co_cellvars);\n}\n#endif\n\n\n// Py_UNUSED() was added to Python 3.4.0b2.\n#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED)\n#  if defined(__GNUC__) || defined(__clang__)\n#    define Py_UNUSED(name) _unused_ ## name __attribute__((unused))\n#  else\n#    define Py_UNUSED(name) _unused_ ## name\n#  endif\n#endif\n\n\n// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A0\nstatic inline PyObject* PyImport_AddModuleRef(const char *name)\n{\n    return Py_XNewRef(PyImport_AddModule(name));\n}\n#endif\n\n\n// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D0000\nstatic inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)\n{\n    PyObject *obj;\n    if (ref != NULL && !PyWeakref_Check(ref)) {\n        *pobj = NULL;\n        PyErr_SetString(PyExc_TypeError, \"expected a weakref\");\n        return -1;\n    }\n    obj = PyWeakref_GetObject(ref);\n    if (obj == NULL) {\n        // SystemError if ref is NULL\n        *pobj = NULL;\n        return -1;\n    }\n    if (obj == Py_None) {\n        *pobj = NULL;\n        return 0;\n    }\n    *pobj = Py_NewRef(obj);\n    return 1;\n}\n#endif\n\n\n// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1\n#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET\n#  define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))\n#endif\n\n// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1\n#if PY_VERSION_HEX < 0x030800B1\nstatic inline Py_ssize_t PyVectorcall_NARGS(size_t n)\n{\n    return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;\n}\n#endif\n\n\n// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4\n#if PY_VERSION_HEX < 0x030900A4\nstatic inline PyObject*\nPyObject_Vectorcall(PyObject *callable, PyObject *const *args,\n                     size_t nargsf, PyObject *kwnames)\n{\n#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION)\n    // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1\n    return _PyObject_Vectorcall(callable, args, nargsf, kwnames);\n#else\n    PyObject *posargs = NULL, *kwargs = NULL;\n    PyObject *res;\n    Py_ssize_t nposargs, nkwargs, i;\n\n    if (nargsf != 0 && args == NULL) {\n        PyErr_BadInternalCall();\n        goto error;\n    }\n    if (kwnames != NULL && !PyTuple_Check(kwnames)) {\n        PyErr_BadInternalCall();\n        goto error;\n    }\n\n    nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf);\n    if (kwnames) {\n        nkwargs = PyTuple_GET_SIZE(kwnames);\n    }\n    else {\n        nkwargs = 0;\n    }\n\n    posargs = PyTuple_New(nposargs);\n    if (posargs == NULL) {\n        goto error;\n    }\n    if (nposargs) {\n        for (i=0; i < nposargs; i++) {\n            PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args));\n            args++;\n        }\n    }\n\n    if (nkwargs) {\n        kwargs = PyDict_New();\n        if (kwargs == NULL) {\n            goto error;\n        }\n\n        for (i = 0; i < nkwargs; i++) {\n            PyObject *key = PyTuple_GET_ITEM(kwnames, i);\n            PyObject *value = *args;\n            args++;\n            if (PyDict_SetItem(kwargs, key, value) < 0) {\n                goto error;\n            }\n        }\n    }\n    else {\n        kwargs = NULL;\n    }\n\n    res = PyObject_Call(callable, posargs, kwargs);\n    Py_DECREF(posargs);\n    Py_XDECREF(kwargs);\n    return res;\n\nerror:\n    Py_DECREF(posargs);\n    Py_XDECREF(kwargs);\n    return NULL;\n#endif\n}\n#endif\n\n\n// gh-106521 added PyObject_GetOptionalAttr() and\n// PyObject_GetOptionalAttrString() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)\n{\n    // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1\n#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION)\n    return _PyObject_LookupAttr(obj, attr_name, result);\n#else\n    *result = PyObject_GetAttr(obj, attr_name);\n    if (*result != NULL) {\n        return 1;\n    }\n    if (!PyErr_Occurred()) {\n        return 0;\n    }\n    if (PyErr_ExceptionMatches(PyExc_AttributeError)) {\n        PyErr_Clear();\n        return 0;\n    }\n    return -1;\n#endif\n}\n\nstatic inline int\nPyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result)\n{\n    PyObject *name_obj;\n    int rc;\n#if PY_VERSION_HEX >= 0x03000000\n    name_obj = PyUnicode_FromString(attr_name);\n#else\n    name_obj = PyString_FromString(attr_name);\n#endif\n    if (name_obj == NULL) {\n        *result = NULL;\n        return -1;\n    }\n    rc = PyObject_GetOptionalAttr(obj, name_obj, result);\n    Py_DECREF(name_obj);\n    return rc;\n}\n#endif\n\n\n// gh-106307 added PyObject_GetOptionalAttr() and\n// PyMapping_GetOptionalItemString() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)\n{\n    *result = PyObject_GetItem(obj, key);\n    if (*result) {\n        return 1;\n    }\n    if (!PyErr_ExceptionMatches(PyExc_KeyError)) {\n        return -1;\n    }\n    PyErr_Clear();\n    return 0;\n}\n\nstatic inline int\nPyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)\n{\n    PyObject *key_obj;\n    int rc;\n#if PY_VERSION_HEX >= 0x03000000\n    key_obj = PyUnicode_FromString(key);\n#else\n    key_obj = PyString_FromString(key);\n#endif\n    if (key_obj == NULL) {\n        *result = NULL;\n        return -1;\n    }\n    rc = PyMapping_GetOptionalItem(obj, key_obj, result);\n    Py_DECREF(key_obj);\n    return rc;\n}\n#endif\n\n// gh-108511 added PyMapping_HasKeyWithError() and\n// PyMapping_HasKeyStringWithError() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyMapping_HasKeyWithError(PyObject *obj, PyObject *key)\n{\n    PyObject *res;\n    int rc = PyMapping_GetOptionalItem(obj, key, &res);\n    Py_XDECREF(res);\n    return rc;\n}\n\nstatic inline int\nPyMapping_HasKeyStringWithError(PyObject *obj, const char *key)\n{\n    PyObject *res;\n    int rc = PyMapping_GetOptionalItemString(obj, key, &res);\n    Py_XDECREF(res);\n    return rc;\n}\n#endif\n\n\n// gh-108511 added PyObject_HasAttrWithError() and\n// PyObject_HasAttrStringWithError() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyObject_HasAttrWithError(PyObject *obj, PyObject *attr)\n{\n    PyObject *res;\n    int rc = PyObject_GetOptionalAttr(obj, attr, &res);\n    Py_XDECREF(res);\n    return rc;\n}\n\nstatic inline int\nPyObject_HasAttrStringWithError(PyObject *obj, const char *attr)\n{\n    PyObject *res;\n    int rc = PyObject_GetOptionalAttrString(obj, attr, &res);\n    Py_XDECREF(res);\n    return rc;\n}\n#endif\n\n\n// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()\n// to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result)\n{\n#if PY_VERSION_HEX >= 0x03000000\n    PyObject *item = PyDict_GetItemWithError(mp, key);\n#else\n    PyObject *item = _PyDict_GetItemWithError(mp, key);\n#endif\n    if (item != NULL) {\n        *result = Py_NewRef(item);\n        return 1;  // found\n    }\n    if (!PyErr_Occurred()) {\n        *result = NULL;\n        return 0;  // not found\n    }\n    *result = NULL;\n    return -1;\n}\n\nstatic inline int\nPyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result)\n{\n    int res;\n#if PY_VERSION_HEX >= 0x03000000\n    PyObject *key_obj = PyUnicode_FromString(key);\n#else\n    PyObject *key_obj = PyString_FromString(key);\n#endif\n    if (key_obj == NULL) {\n        *result = NULL;\n        return -1;\n    }\n    res = PyDict_GetItemRef(mp, key_obj, result);\n    Py_DECREF(key_obj);\n    return res;\n}\n#endif\n\n\n// gh-106307 added PyModule_Add() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyModule_Add(PyObject *mod, const char *name, PyObject *value)\n{\n    int res = PyModule_AddObjectRef(mod, name, value);\n    Py_XDECREF(value);\n    return res;\n}\n#endif\n\n\n// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1\n// bpo-1856 added _Py_Finalizing to Python 3.2.1b1.\n// _Py_IsFinalizing() was added to PyPy 7.3.0.\n#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \\\n        && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000)\nstatic inline int Py_IsFinalizing(void)\n{\n#if PY_VERSION_HEX >= 0x030700A1\n    // _Py_IsFinalizing() was added to Python 3.7.0a1.\n    return _Py_IsFinalizing();\n#else\n    return (_Py_Finalizing != NULL);\n#endif\n}\n#endif\n\n\n// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int PyDict_ContainsString(PyObject *op, const char *key)\n{\n    PyObject *key_obj = PyUnicode_FromString(key);\n    if (key_obj == NULL) {\n        return -1;\n    }\n    int res = PyDict_Contains(op, key_obj);\n    Py_DECREF(key_obj);\n    return res;\n}\n#endif\n\n\n// gh-108445 added PyLong_AsInt() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int PyLong_AsInt(PyObject *obj)\n{\n#ifdef PYPY_VERSION\n    long value = PyLong_AsLong(obj);\n    if (value == -1 && PyErr_Occurred()) {\n        return -1;\n    }\n    if (value < (long)INT_MIN || (long)INT_MAX < value) {\n        PyErr_SetString(PyExc_OverflowError,\n                        \"Python int too large to convert to C int\");\n        return -1;\n    }\n    return (int)value;\n#else\n    return _PyLong_AsInt(obj);\n#endif\n}\n#endif\n\n\n// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)\n{\n    PyObject **dict = _PyObject_GetDictPtr(obj);\n    if (dict == NULL || *dict == NULL) {\n        return -1;\n    }\n    Py_VISIT(*dict);\n    return 0;\n}\n\nstatic inline void\nPyObject_ClearManagedDict(PyObject *obj)\n{\n    PyObject **dict = _PyObject_GetDictPtr(obj);\n    if (dict == NULL || *dict == NULL) {\n        return;\n    }\n    Py_CLEAR(*dict);\n}\n#endif\n\n// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1\n// Python 3.5.2 added _PyThreadState_UncheckedGet().\n#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1\nstatic inline PyThreadState*\nPyThreadState_GetUnchecked(void)\n{\n    return _PyThreadState_UncheckedGet();\n}\n#endif\n\n// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize()\n// to Python 3.13.0a1\n#if PY_VERSION_HEX < 0x030D00A1\nstatic inline int\nPyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len)\n{\n    Py_ssize_t len;\n    const void *utf8;\n    PyObject *exc_type, *exc_value, *exc_tb;\n    int res;\n\n    // API cannot report errors so save/restore the exception\n    PyErr_Fetch(&exc_type, &exc_value, &exc_tb);\n\n    // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize()\n#if PY_VERSION_HEX >= 0x030300A1\n    if (PyUnicode_IS_ASCII(unicode)) {\n        utf8 = PyUnicode_DATA(unicode);\n        len = PyUnicode_GET_LENGTH(unicode);\n    }\n    else {\n        utf8 = PyUnicode_AsUTF8AndSize(unicode, &len);\n        if (utf8 == NULL) {\n            // Memory allocation failure. The API cannot report error,\n            // so ignore the exception and return 0.\n            res = 0;\n            goto done;\n        }\n    }\n\n    if (len != str_len) {\n        res = 0;\n        goto done;\n    }\n    res = (memcmp(utf8, str, (size_t)len) == 0);\n#else\n    PyObject *bytes = PyUnicode_AsUTF8String(unicode);\n    if (bytes == NULL) {\n        // Memory allocation failure. The API cannot report error,\n        // so ignore the exception and return 0.\n        res = 0;\n        goto done;\n    }\n\n#if PY_VERSION_HEX >= 0x03000000\n    len = PyBytes_GET_SIZE(bytes);\n    utf8 = PyBytes_AS_STRING(bytes);\n#else\n    len = PyString_GET_SIZE(bytes);\n    utf8 = PyString_AS_STRING(bytes);\n#endif\n    if (len != str_len) {\n        Py_DECREF(bytes);\n        res = 0;\n        goto done;\n    }\n\n    res = (memcmp(utf8, str, (size_t)len) == 0);\n    Py_DECREF(bytes);\n#endif\n\ndone:\n    PyErr_Restore(exc_type, exc_value, exc_tb);\n    return res;\n}\n\nstatic inline int\nPyUnicode_EqualToUTF8(PyObject *unicode, const char *str)\n{\n    return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str));\n}\n#endif\n\n\n// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2\n#if PY_VERSION_HEX < 0x030D00A2\nstatic inline int\nPyList_Extend(PyObject *list, PyObject *iterable)\n{\n    return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable);\n}\n\nstatic inline int\nPyList_Clear(PyObject *list)\n{\n    return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL);\n}\n#endif\n\n// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2\n#if PY_VERSION_HEX < 0x030D00A2\nstatic inline int\nPyDict_Pop(PyObject *dict, PyObject *key, PyObject **result)\n{\n    PyObject *value;\n\n    if (!PyDict_Check(dict)) {\n        PyErr_BadInternalCall();\n        if (result) {\n            *result = NULL;\n        }\n        return -1;\n    }\n\n    // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2.\n    // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*.\n    // Python 3.13.0a1 removed _PyDict_Pop().\n#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000\n    value = PyObject_CallMethod(dict, \"pop\", \"O\", key);\n#elif PY_VERSION_HEX < 0x030600b3\n    value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL);\n#else\n    value = _PyDict_Pop(dict, key, NULL);\n#endif\n    if (value == NULL) {\n        if (result) {\n            *result = NULL;\n        }\n        if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) {\n            return -1;\n        }\n        PyErr_Clear();\n        return 0;\n    }\n    if (result) {\n        *result = value;\n    }\n    else {\n        Py_DECREF(value);\n    }\n    return 1;\n}\n\nstatic inline int\nPyDict_PopString(PyObject *dict, const char *key, PyObject **result)\n{\n    PyObject *key_obj = PyUnicode_FromString(key);\n    if (key_obj == NULL) {\n        if (result != NULL) {\n            *result = NULL;\n        }\n        return -1;\n    }\n\n    int res = PyDict_Pop(dict, key_obj, result);\n    Py_DECREF(key_obj);\n    return res;\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030200A4\n// Python 3.2.0a4 added Py_hash_t type\ntypedef Py_ssize_t Py_hash_t;\n#endif\n\n\n// gh-111545 added Py_HashPointer() to Python 3.13.0a3\n#if PY_VERSION_HEX < 0x030D00A3\nstatic inline Py_hash_t Py_HashPointer(const void *ptr)\n{\n#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION)\n    return _Py_HashPointer(ptr);\n#else\n    return _Py_HashPointer(_Py_CAST(void*, ptr));\n#endif\n}\n#endif\n\n\n// Python 3.13a4 added a PyTime API.\n// Use the private API added to Python 3.5.\n#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX  >= 0x03050000\ntypedef _PyTime_t PyTime_t;\n#define PyTime_MIN _PyTime_MIN\n#define PyTime_MAX _PyTime_MAX\n\nstatic inline double PyTime_AsSecondsDouble(PyTime_t t)\n{ return _PyTime_AsSecondsDouble(t); }\n\nstatic inline int PyTime_Monotonic(PyTime_t *result)\n{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }\n\nstatic inline int PyTime_Time(PyTime_t *result)\n{ return _PyTime_GetSystemClockWithInfo(result, NULL); }\n\nstatic inline int PyTime_PerfCounter(PyTime_t *result)\n{\n#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)\n    return _PyTime_GetPerfCounterWithInfo(result, NULL);\n#elif PY_VERSION_HEX >= 0x03070000\n    // Call time.perf_counter_ns() and convert Python int object to PyTime_t.\n    // Cache time.perf_counter_ns() function for best performance.\n    static PyObject *func = NULL;\n    if (func == NULL) {\n        PyObject *mod = PyImport_ImportModule(\"time\");\n        if (mod == NULL) {\n            return -1;\n        }\n\n        func = PyObject_GetAttrString(mod, \"perf_counter_ns\");\n        Py_DECREF(mod);\n        if (func == NULL) {\n            return -1;\n        }\n    }\n\n    PyObject *res = PyObject_CallNoArgs(func);\n    if (res == NULL) {\n        return -1;\n    }\n    long long value = PyLong_AsLongLong(res);\n    Py_DECREF(res);\n\n    if (value == -1 && PyErr_Occurred()) {\n        return -1;\n    }\n\n    Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t));\n    *result = (PyTime_t)value;\n    return 0;\n#else\n    // Call time.perf_counter() and convert C double to PyTime_t.\n    // Cache time.perf_counter() function for best performance.\n    static PyObject *func = NULL;\n    if (func == NULL) {\n        PyObject *mod = PyImport_ImportModule(\"time\");\n        if (mod == NULL) {\n            return -1;\n        }\n\n        func = PyObject_GetAttrString(mod, \"perf_counter\");\n        Py_DECREF(mod);\n        if (func == NULL) {\n            return -1;\n        }\n    }\n\n    PyObject *res = PyObject_CallNoArgs(func);\n    if (res == NULL) {\n        return -1;\n    }\n    double d = PyFloat_AsDouble(res);\n    Py_DECREF(res);\n\n    if (d == -1.0 && PyErr_Occurred()) {\n        return -1;\n    }\n\n    // Avoid floor() to avoid having to link to libm\n    *result = (PyTime_t)(d * 1e9);\n    return 0;\n#endif\n}\n\n#endif\n\n// gh-111389 added hash constants to Python 3.13.0a5. These constants were\n// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8.\n#if (!defined(PyHASH_BITS) \\\n     && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \\\n         || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \\\n             && PYPY_VERSION_NUM >= 0x07030800)))\n#  define PyHASH_BITS _PyHASH_BITS\n#  define PyHASH_MODULUS _PyHASH_MODULUS\n#  define PyHASH_INF _PyHASH_INF\n#  define PyHASH_IMAG _PyHASH_IMAG\n#endif\n\n\n// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed()\n// to Python 3.13.0a6\n#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE)\n\n#define Py_CONSTANT_NONE 0\n#define Py_CONSTANT_FALSE 1\n#define Py_CONSTANT_TRUE 2\n#define Py_CONSTANT_ELLIPSIS 3\n#define Py_CONSTANT_NOT_IMPLEMENTED 4\n#define Py_CONSTANT_ZERO 5\n#define Py_CONSTANT_ONE 6\n#define Py_CONSTANT_EMPTY_STR 7\n#define Py_CONSTANT_EMPTY_BYTES 8\n#define Py_CONSTANT_EMPTY_TUPLE 9\n\nstatic inline PyObject* Py_GetConstant(unsigned int constant_id)\n{\n    static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL};\n\n    if (constants[Py_CONSTANT_NONE] == NULL) {\n        constants[Py_CONSTANT_NONE] = Py_None;\n        constants[Py_CONSTANT_FALSE] = Py_False;\n        constants[Py_CONSTANT_TRUE] = Py_True;\n        constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis;\n        constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented;\n\n        constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0);\n        if (constants[Py_CONSTANT_ZERO] == NULL) {\n            goto fatal_error;\n        }\n\n        constants[Py_CONSTANT_ONE] = PyLong_FromLong(1);\n        if (constants[Py_CONSTANT_ONE] == NULL) {\n            goto fatal_error;\n        }\n\n        constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize(\"\", 0);\n        if (constants[Py_CONSTANT_EMPTY_STR] == NULL) {\n            goto fatal_error;\n        }\n\n        constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize(\"\", 0);\n        if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) {\n            goto fatal_error;\n        }\n\n        constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0);\n        if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) {\n            goto fatal_error;\n        }\n        // goto dance to avoid compiler warnings about Py_FatalError()\n        goto init_done;\n\nfatal_error:\n        // This case should never happen\n        Py_FatalError(\"Py_GetConstant() failed to get constants\");\n    }\n\ninit_done:\n    if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) {\n        return Py_NewRef(constants[constant_id]);\n    }\n    else {\n        PyErr_BadInternalCall();\n        return NULL;\n    }\n}\n\nstatic inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id)\n{\n    PyObject *obj = Py_GetConstant(constant_id);\n    Py_XDECREF(obj);\n    return obj;\n}\n#endif\n\n\n// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4\n#if PY_VERSION_HEX < 0x030D00A4\nstatic inline PyObject *\nPyList_GetItemRef(PyObject *op, Py_ssize_t index)\n{\n    PyObject *item = PyList_GetItem(op, index);\n    Py_XINCREF(item);\n    return item;\n}\n#endif\n\n\n// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4\n#if PY_VERSION_HEX < 0x030D00A4\nstatic inline int\nPyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,\n                     PyObject **result)\n{\n    PyObject *value;\n    if (PyDict_GetItemRef(d, key, &value) < 0) {\n        // get error\n        if (result) {\n            *result = NULL;\n        }\n        return -1;\n    }\n    if (value != NULL) {\n        // present\n        if (result) {\n            *result = value;\n        }\n        else {\n            Py_DECREF(value);\n        }\n        return 1;\n    }\n\n    // missing: set the item\n    if (PyDict_SetItem(d, key, default_value) < 0) {\n        // set error\n        if (result) {\n            *result = NULL;\n        }\n        return -1;\n    }\n    if (result) {\n        *result = Py_NewRef(default_value);\n    }\n    return 0;\n}\n#endif\n\n#if PY_VERSION_HEX < 0x030D00B3\n#  define Py_BEGIN_CRITICAL_SECTION(op) {\n#  define Py_END_CRITICAL_SECTION() }\n#  define Py_BEGIN_CRITICAL_SECTION2(a, b) {\n#  define Py_END_CRITICAL_SECTION2() }\n#endif\n\n#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION)\ntypedef struct PyUnicodeWriter PyUnicodeWriter;\n\nstatic inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer)\n{\n    _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer);\n    PyMem_Free(writer);\n}\n\nstatic inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)\n{\n    if (length < 0) {\n        PyErr_SetString(PyExc_ValueError,\n                        \"length must be positive\");\n        return NULL;\n    }\n\n    const size_t size = sizeof(_PyUnicodeWriter);\n    PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size);\n    if (pub_writer == _Py_NULL) {\n        PyErr_NoMemory();\n        return _Py_NULL;\n    }\n    _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer;\n\n    _PyUnicodeWriter_Init(writer);\n    if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) {\n        PyUnicodeWriter_Discard(pub_writer);\n        return NULL;\n    }\n    writer->overallocate = 1;\n    return pub_writer;\n}\n\nstatic inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer)\n{\n    PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer);\n    assert(((_PyUnicodeWriter*)writer)->buffer == NULL);\n    PyMem_Free(writer);\n    return str;\n}\n\nstatic inline int\nPyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch)\n{\n    if (ch > 0x10ffff) {\n        PyErr_SetString(PyExc_ValueError,\n                        \"character must be in range(0x110000)\");\n        return -1;\n    }\n\n    return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch);\n}\n\nstatic inline int\nPyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj)\n{\n    PyObject *str = PyObject_Str(obj);\n    if (str == NULL) {\n        return -1;\n    }\n\n    int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);\n    Py_DECREF(str);\n    return res;\n}\n\nstatic inline int\nPyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)\n{\n    PyObject *str = PyObject_Repr(obj);\n    if (str == NULL) {\n        return -1;\n    }\n\n    int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);\n    Py_DECREF(str);\n    return res;\n}\n\nstatic inline int\nPyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer,\n                          const char *str, Py_ssize_t size)\n{\n    if (size < 0) {\n        size = (Py_ssize_t)strlen(str);\n    }\n\n    PyObject *str_obj = PyUnicode_FromStringAndSize(str, size);\n    if (str_obj == _Py_NULL) {\n        return -1;\n    }\n\n    int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);\n    Py_DECREF(str_obj);\n    return res;\n}\n\nstatic inline int\nPyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer,\n                           const char *str, Py_ssize_t size)\n{\n    if (size < 0) {\n        size = (Py_ssize_t)strlen(str);\n    }\n\n    return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer,\n                                             str, size);\n}\n\nstatic inline int\nPyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer,\n                              const wchar_t *str, Py_ssize_t size)\n{\n    if (size < 0) {\n        size = (Py_ssize_t)wcslen(str);\n    }\n\n    PyObject *str_obj = PyUnicode_FromWideChar(str, size);\n    if (str_obj == _Py_NULL) {\n        return -1;\n    }\n\n    int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj);\n    Py_DECREF(str_obj);\n    return res;\n}\n\nstatic inline int\nPyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str,\n                               Py_ssize_t start, Py_ssize_t end)\n{\n    if (!PyUnicode_Check(str)) {\n        PyErr_Format(PyExc_TypeError, \"expect str, not %s\",\n                     Py_TYPE(str)->tp_name);\n        return -1;\n    }\n    if (start < 0 || start > end) {\n        PyErr_Format(PyExc_ValueError, \"invalid start argument\");\n        return -1;\n    }\n    if (end > PyUnicode_GET_LENGTH(str)) {\n        PyErr_Format(PyExc_ValueError, \"invalid end argument\");\n        return -1;\n    }\n\n    return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str,\n                                           start, end);\n}\n\nstatic inline int\nPyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...)\n{\n    va_list vargs;\n    va_start(vargs, format);\n    PyObject *str = PyUnicode_FromFormatV(format, vargs);\n    va_end(vargs);\n    if (str == _Py_NULL) {\n        return -1;\n    }\n\n    int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str);\n    Py_DECREF(str);\n    return res;\n}\n#endif  // PY_VERSION_HEX < 0x030E0000\n\n// gh-116560 added PyLong_GetSign() to Python 3.14.0a0\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline int PyLong_GetSign(PyObject *obj, int *sign)\n{\n    if (!PyLong_Check(obj)) {\n        PyErr_Format(PyExc_TypeError, \"expect int, got %s\", Py_TYPE(obj)->tp_name);\n        return -1;\n    }\n\n    *sign = _PyLong_Sign(obj);\n    return 0;\n}\n#endif\n\n// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2\n#if PY_VERSION_HEX < 0x030E00A2\nstatic inline int PyLong_IsPositive(PyObject *obj)\n{\n    if (!PyLong_Check(obj)) {\n        PyErr_Format(PyExc_TypeError, \"expected int, got %s\", Py_TYPE(obj)->tp_name);\n        return -1;\n    }\n    return _PyLong_Sign(obj) == 1;\n}\n\nstatic inline int PyLong_IsNegative(PyObject *obj)\n{\n    if (!PyLong_Check(obj)) {\n        PyErr_Format(PyExc_TypeError, \"expected int, got %s\", Py_TYPE(obj)->tp_name);\n        return -1;\n    }\n    return _PyLong_Sign(obj) == -1;\n}\n\nstatic inline int PyLong_IsZero(PyObject *obj)\n{\n    if (!PyLong_Check(obj)) {\n        PyErr_Format(PyExc_TypeError, \"expected int, got %s\", Py_TYPE(obj)->tp_name);\n        return -1;\n    }\n    return _PyLong_Sign(obj) == 0;\n}\n#endif\n\n\n// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline int PyUnicode_Equal(PyObject *str1, PyObject *str2)\n{\n    if (!PyUnicode_Check(str1)) {\n        PyErr_Format(PyExc_TypeError, \"first argument must be str, not %s\",\n                     Py_TYPE(str1)->tp_name);\n        return -1;\n    }\n    if (!PyUnicode_Check(str2)) {\n        PyErr_Format(PyExc_TypeError, \"second argument must be str, not %s\",\n                     Py_TYPE(str2)->tp_name);\n        return -1;\n    }\n\n#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION)\n    PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2);\n\n    return _PyUnicode_Equal(str1, str2);\n#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION)\n    return _PyUnicode_EQ(str1, str2);\n#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION)\n    return _PyUnicode_EQ(str1, str2);\n#else\n    return (PyUnicode_Compare(str1, str2) == 0);\n#endif\n}\n#endif\n\n\n// gh-121645 added PyBytes_Join() to Python 3.14.0a0\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable)\n{\n    return _PyBytes_Join(sep, iterable);\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len)\n{\n#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)\n    PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len);\n\n    return _Py_HashBytes(ptr, len);\n#else\n    Py_hash_t hash;\n    PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len);\n    if (bytes == NULL) {\n        return -1;\n    }\n    hash = PyObject_Hash(bytes);\n    Py_DECREF(bytes);\n    return hash;\n#endif\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline int PyIter_NextItem(PyObject *iter, PyObject **item)\n{\n    iternextfunc tp_iternext;\n\n    assert(iter != NULL);\n    assert(item != NULL);\n\n    tp_iternext = Py_TYPE(iter)->tp_iternext;\n    if (tp_iternext == NULL) {\n        *item = NULL;\n        PyErr_Format(PyExc_TypeError, \"expected an iterator, got '%s'\",\n                     Py_TYPE(iter)->tp_name);\n        return -1;\n    }\n\n    if ((*item = tp_iternext(iter))) {\n        return 1;\n    }\n    if (!PyErr_Occurred()) {\n        return 0;\n    }\n    if (PyErr_ExceptionMatches(PyExc_StopIteration)) {\n        PyErr_Clear();\n        return 0;\n    }\n    return -1;\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030E00A0\nstatic inline PyObject* PyLong_FromInt32(int32_t value)\n{\n    Py_BUILD_ASSERT(sizeof(long) >= 4);\n    return PyLong_FromLong(value);\n}\n\nstatic inline PyObject* PyLong_FromInt64(int64_t value)\n{\n    Py_BUILD_ASSERT(sizeof(long long) >= 8);\n    return PyLong_FromLongLong(value);\n}\n\nstatic inline PyObject* PyLong_FromUInt32(uint32_t value)\n{\n    Py_BUILD_ASSERT(sizeof(unsigned long) >= 4);\n    return PyLong_FromUnsignedLong(value);\n}\n\nstatic inline PyObject* PyLong_FromUInt64(uint64_t value)\n{\n    Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8);\n    return PyLong_FromUnsignedLongLong(value);\n}\n\nstatic inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue)\n{\n    Py_BUILD_ASSERT(sizeof(int) == 4);\n    int value = PyLong_AsInt(obj);\n    if (value == -1 && PyErr_Occurred()) {\n        return -1;\n    }\n    *pvalue = (int32_t)value;\n    return 0;\n}\n\nstatic inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue)\n{\n    Py_BUILD_ASSERT(sizeof(long long) == 8);\n    long long value = PyLong_AsLongLong(obj);\n    if (value == -1 && PyErr_Occurred()) {\n        return -1;\n    }\n    *pvalue = (int64_t)value;\n    return 0;\n}\n\nstatic inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue)\n{\n    Py_BUILD_ASSERT(sizeof(long) >= 4);\n    unsigned long value = PyLong_AsUnsignedLong(obj);\n    if (value == (unsigned long)-1 && PyErr_Occurred()) {\n        return -1;\n    }\n#if SIZEOF_LONG > 4\n    if ((unsigned long)UINT32_MAX < value) {\n        PyErr_SetString(PyExc_OverflowError,\n                        \"Python int too large to convert to C uint32_t\");\n        return -1;\n    }\n#endif\n    *pvalue = (uint32_t)value;\n    return 0;\n}\n\nstatic inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue)\n{\n    Py_BUILD_ASSERT(sizeof(long long) == 8);\n    unsigned long long value = PyLong_AsUnsignedLongLong(obj);\n    if (value == (unsigned long long)-1 && PyErr_Occurred()) {\n        return -1;\n    }\n    *pvalue = (uint64_t)value;\n    return 0;\n}\n#endif\n\n\n// gh-102471 added import and export API for integers to 3.14.0a2.\n#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)\n// Helpers to access PyLongObject internals.\nstatic inline void\n_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)\n{\n#if PY_VERSION_HEX >= 0x030C0000\n    op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3);\n#elif PY_VERSION_HEX >= 0x030900A4\n    Py_SET_SIZE(op, sign * size);\n#else\n    Py_SIZE(op) = sign * size;\n#endif\n}\n\nstatic inline Py_ssize_t\n_PyLong_DigitCount(const PyLongObject *op)\n{\n#if PY_VERSION_HEX >= 0x030C0000\n    return (Py_ssize_t)(op->long_value.lv_tag >> 3);\n#else\n    return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op);\n#endif\n}\n\nstatic inline digit*\n_PyLong_GetDigits(const PyLongObject *op)\n{\n#if PY_VERSION_HEX >= 0x030C0000\n    return (digit*)(op->long_value.ob_digit);\n#else\n    return (digit*)(op->ob_digit);\n#endif\n}\n\ntypedef struct PyLongLayout {\n    uint8_t bits_per_digit;\n    uint8_t digit_size;\n    int8_t digits_order;\n    int8_t digit_endianness;\n} PyLongLayout;\n\ntypedef struct PyLongExport {\n    int64_t value;\n    uint8_t negative;\n    Py_ssize_t ndigits;\n    const void *digits;\n    Py_uintptr_t _reserved;\n} PyLongExport;\n\ntypedef struct PyLongWriter PyLongWriter;\n\nstatic inline const PyLongLayout*\nPyLong_GetNativeLayout(void)\n{\n    static const PyLongLayout PyLong_LAYOUT = {\n        PyLong_SHIFT,\n        sizeof(digit),\n        -1,  // least significant first\n        PY_LITTLE_ENDIAN ? -1 : 1,\n    };\n\n    return &PyLong_LAYOUT;\n}\n\nstatic inline int\nPyLong_Export(PyObject *obj, PyLongExport *export_long)\n{\n    if (!PyLong_Check(obj)) {\n        memset(export_long, 0, sizeof(*export_long));\n        PyErr_Format(PyExc_TypeError, \"expected int, got %s\",\n                     Py_TYPE(obj)->tp_name);\n        return -1;\n    }\n\n    // Fast-path: try to convert to a int64_t\n    PyLongObject *self = (PyLongObject*)obj;\n    int overflow;\n#if SIZEOF_LONG == 8\n    long value = PyLong_AsLongAndOverflow(obj, &overflow);\n#else\n    // Windows has 32-bit long, so use 64-bit long long instead\n    long long value = PyLong_AsLongLongAndOverflow(obj, &overflow);\n#endif\n    Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t));\n    // the function cannot fail since obj is a PyLongObject\n    assert(!(value == -1 && PyErr_Occurred()));\n\n    if (!overflow) {\n        export_long->value = value;\n        export_long->negative = 0;\n        export_long->ndigits = 0;\n        export_long->digits = 0;\n        export_long->_reserved = 0;\n    }\n    else {\n        export_long->value = 0;\n        export_long->negative = _PyLong_Sign(obj) < 0;\n        export_long->ndigits = _PyLong_DigitCount(self);\n        if (export_long->ndigits == 0) {\n            export_long->ndigits = 1;\n        }\n        export_long->digits = _PyLong_GetDigits(self);\n        export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj);\n    }\n    return 0;\n}\n\nstatic inline void\nPyLong_FreeExport(PyLongExport *export_long)\n{\n    PyObject *obj = (PyObject*)export_long->_reserved;\n\n    if (obj) {\n        export_long->_reserved = 0;\n        Py_DECREF(obj);\n    }\n}\n\nstatic inline PyLongWriter*\nPyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)\n{\n    if (ndigits <= 0) {\n        PyErr_SetString(PyExc_ValueError, \"ndigits must be positive\");\n        return NULL;\n    }\n    assert(digits != NULL);\n\n    PyLongObject *obj = _PyLong_New(ndigits);\n    if (obj == NULL) {\n        return NULL;\n    }\n    _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits);\n\n    *digits = _PyLong_GetDigits(obj);\n    return (PyLongWriter*)obj;\n}\n\nstatic inline void\nPyLongWriter_Discard(PyLongWriter *writer)\n{\n    PyLongObject *obj = (PyLongObject *)writer;\n\n    assert(Py_REFCNT(obj) == 1);\n    Py_DECREF(obj);\n}\n\nstatic inline PyObject*\nPyLongWriter_Finish(PyLongWriter *writer)\n{\n    PyObject *obj = (PyObject *)writer;\n    PyLongObject *self = (PyLongObject*)obj;\n    Py_ssize_t j = _PyLong_DigitCount(self);\n    Py_ssize_t i = j;\n    int sign = _PyLong_Sign(obj);\n\n    assert(Py_REFCNT(obj) == 1);\n\n    // Normalize and get singleton if possible\n    while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) {\n        --i;\n    }\n    if (i != j) {\n        if (i == 0) {\n            sign = 0;\n        }\n        _PyLong_SetSignAndDigitCount(self, sign, i);\n    }\n    if (i <= 1) {\n        long val = sign * (long)(_PyLong_GetDigits(self)[0]);\n        Py_DECREF(obj);\n        return PyLong_FromLong(val);\n    }\n\n    return obj;\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030C00A3\n#  define Py_T_SHORT      T_SHORT\n#  define Py_T_INT        T_INT\n#  define Py_T_LONG       T_LONG\n#  define Py_T_FLOAT      T_FLOAT\n#  define Py_T_DOUBLE     T_DOUBLE\n#  define Py_T_STRING     T_STRING\n#  define _Py_T_OBJECT    T_OBJECT\n#  define Py_T_CHAR       T_CHAR\n#  define Py_T_BYTE       T_BYTE\n#  define Py_T_UBYTE      T_UBYTE\n#  define Py_T_USHORT     T_USHORT\n#  define Py_T_UINT       T_UINT\n#  define Py_T_ULONG      T_ULONG\n#  define Py_T_STRING_INPLACE  T_STRING_INPLACE\n#  define Py_T_BOOL       T_BOOL\n#  define Py_T_OBJECT_EX  T_OBJECT_EX\n#  define Py_T_LONGLONG   T_LONGLONG\n#  define Py_T_ULONGLONG  T_ULONGLONG\n#  define Py_T_PYSSIZET   T_PYSSIZET\n\n#  if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)\n#    define _Py_T_NONE      T_NONE\n#  endif\n\n#  define Py_READONLY            READONLY\n#  define Py_AUDIT_READ          READ_RESTRICTED\n#  define _Py_WRITE_RESTRICTED   PY_WRITE_RESTRICTED\n#endif\n\n\n// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4\n#if PY_VERSION_HEX < 0x030E00A4\nstatic inline FILE* Py_fopen(PyObject *path, const char *mode)\n{\n#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION)\n    PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode);\n\n    return _Py_fopen_obj(path, mode);\n#else\n    FILE *f;\n    PyObject *bytes;\n#if PY_VERSION_HEX >= 0x03000000\n    if (!PyUnicode_FSConverter(path, &bytes)) {\n        return NULL;\n    }\n#else\n    if (!PyString_Check(path)) {\n        PyErr_SetString(PyExc_TypeError, \"except str\");\n        return NULL;\n    }\n    bytes = Py_NewRef(path);\n#endif\n    const char *path_bytes = PyBytes_AS_STRING(bytes);\n\n    f = fopen(path_bytes, mode);\n    Py_DECREF(bytes);\n\n    if (f == NULL) {\n        PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);\n        return NULL;\n    }\n    return f;\n#endif\n}\n\nstatic inline int Py_fclose(FILE *file)\n{\n    return fclose(file);\n}\n#endif\n\n\n#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION)\nstatic inline PyObject*\nPyConfig_Get(const char *name)\n{\n    typedef enum {\n        _PyConfig_MEMBER_INT,\n        _PyConfig_MEMBER_UINT,\n        _PyConfig_MEMBER_ULONG,\n        _PyConfig_MEMBER_BOOL,\n        _PyConfig_MEMBER_WSTR,\n        _PyConfig_MEMBER_WSTR_OPT,\n        _PyConfig_MEMBER_WSTR_LIST,\n    } PyConfigMemberType;\n\n    typedef struct {\n        const char *name;\n        size_t offset;\n        PyConfigMemberType type;\n        const char *sys_attr;\n    } PyConfigSpec;\n\n#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \\\n    {#MEMBER, offsetof(PyConfig, MEMBER), \\\n     _PyConfig_MEMBER_##TYPE, sys_attr}\n\n    static const PyConfigSpec config_spec[] = {\n        PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, \"argv\"),\n        PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, \"base_exec_prefix\"),\n        PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, \"_base_executable\"),\n        PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, \"base_prefix\"),\n        PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, \"exec_prefix\"),\n        PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, \"executable\"),\n        PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL),\n#if 0x030C0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, \"path\"),\n        PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, \"platlibdir\"),\n        PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, \"prefix\"),\n        PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, \"pycache_prefix\"),\n        PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL),\n#if 0x030B0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, \"_stdlib_dir\"),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, \"warnoptions\"),\n        PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, \"_xoptions\"),\n        PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL),\n#if 0x030B0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL),\n#if 0x030D0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL),\n#if 0x030B0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL),\n#endif\n#ifdef Py_GIL_DISABLED\n        PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL),\n#ifdef MS_WINDOWS\n        PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL),\n#if 0x030A0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, \"orig_argv\"),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL),\n#if 0x030C0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL),\n#if 0x030B0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL),\n#if 0x030B0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL),\n#endif\n        PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL),\n        PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL),\n#if 0x030A0000 <= PY_VERSION_HEX\n        PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL),\n#endif\n    };\n\n#undef PYTHONCAPI_COMPAT_SPEC\n\n    const PyConfigSpec *spec;\n    int found = 0;\n    for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) {\n        spec = &config_spec[i];\n        if (strcmp(spec->name, name) == 0) {\n            found = 1;\n            break;\n        }\n    }\n    if (found) {\n        if (spec->sys_attr != NULL) {\n            PyObject *value = PySys_GetObject(spec->sys_attr);\n            if (value == NULL) {\n                PyErr_Format(PyExc_RuntimeError, \"lost sys.%s\", spec->sys_attr);\n                return NULL;\n            }\n            return Py_NewRef(value);\n        }\n\n        PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void);\n\n        const PyConfig *config = _Py_GetConfig();\n        void *member = (char *)config + spec->offset;\n        switch (spec->type) {\n        case _PyConfig_MEMBER_INT:\n        case _PyConfig_MEMBER_UINT:\n        {\n            int value = *(int *)member;\n            return PyLong_FromLong(value);\n        }\n        case _PyConfig_MEMBER_BOOL:\n        {\n            int value = *(int *)member;\n            return PyBool_FromLong(value != 0);\n        }\n        case _PyConfig_MEMBER_ULONG:\n        {\n            unsigned long value = *(unsigned long *)member;\n            return PyLong_FromUnsignedLong(value);\n        }\n        case _PyConfig_MEMBER_WSTR:\n        case _PyConfig_MEMBER_WSTR_OPT:\n        {\n            wchar_t *wstr = *(wchar_t **)member;\n            if (wstr != NULL) {\n                return PyUnicode_FromWideChar(wstr, -1);\n            }\n            else {\n                return Py_NewRef(Py_None);\n            }\n        }\n        case _PyConfig_MEMBER_WSTR_LIST:\n        {\n            const PyWideStringList *list = (const PyWideStringList *)member;\n            PyObject *tuple = PyTuple_New(list->length);\n            if (tuple == NULL) {\n                return NULL;\n            }\n\n            for (Py_ssize_t i = 0; i < list->length; i++) {\n                PyObject *item = PyUnicode_FromWideChar(list->items[i], -1);\n                if (item == NULL) {\n                    Py_DECREF(tuple);\n                    return NULL;\n                }\n                PyTuple_SET_ITEM(tuple, i, item);\n            }\n            return tuple;\n        }\n        default:\n            Py_UNREACHABLE();\n        }\n    }\n\n    PyErr_Format(PyExc_ValueError, \"unknown config option name: %s\", name);\n    return NULL;\n}\n\nstatic inline int\nPyConfig_GetInt(const char *name, int *value)\n{\n    PyObject *obj = PyConfig_Get(name);\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (!PyLong_Check(obj)) {\n        Py_DECREF(obj);\n        PyErr_Format(PyExc_TypeError, \"config option %s is not an int\", name);\n        return -1;\n    }\n\n    int as_int = PyLong_AsInt(obj);\n    Py_DECREF(obj);\n    if (as_int == -1 && PyErr_Occurred()) {\n        PyErr_Format(PyExc_OverflowError,\n                     \"config option %s value does not fit into a C int\", name);\n        return -1;\n    }\n\n    *value = as_int;\n    return 0;\n}\n#endif  // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION)\n\n// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1.\n// Adapted from  _PyObject_IsUniquelyReferenced() implementation.\n#if PY_VERSION_HEX < 0x030E00B0\nstatic inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj)\n{\n#if !defined(Py_GIL_DISABLED)\n    return Py_REFCNT(obj) == 1;\n#else\n    // NOTE: the entire ob_ref_shared field must be zero, including flags, to\n    // ensure that other threads cannot concurrently create new references to\n    // this object.\n    return (_Py_IsOwnedByCurrentThread(obj) &&\n            _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 &&\n            _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0);\n#endif\n}\n#endif\n\n\n#if PY_VERSION_HEX < 0x030F0000\nstatic inline PyObject*\nPySys_GetAttrString(const char *name)\n{\n#if PY_VERSION_HEX >= 0x03000000\n    PyObject *value = Py_XNewRef(PySys_GetObject(name));\n#else\n    PyObject *value = Py_XNewRef(PySys_GetObject((char*)name));\n#endif\n    if (value != NULL) {\n        return value;\n    }\n    if (!PyErr_Occurred()) {\n        PyErr_Format(PyExc_RuntimeError, \"lost sys.%s\", name);\n    }\n    return NULL;\n}\n\nstatic inline PyObject*\nPySys_GetAttr(PyObject *name)\n{\n#if PY_VERSION_HEX >= 0x03000000\n    const char *name_str = PyUnicode_AsUTF8(name);\n#else\n    const char *name_str = PyString_AsString(name);\n#endif\n    if (name_str == NULL) {\n        return NULL;\n    }\n\n    return PySys_GetAttrString(name_str);\n}\n\nstatic inline int\nPySys_GetOptionalAttrString(const char *name, PyObject **value)\n{\n#if PY_VERSION_HEX >= 0x03000000\n    *value = Py_XNewRef(PySys_GetObject(name));\n#else\n    *value = Py_XNewRef(PySys_GetObject((char*)name));\n#endif\n    if (*value != NULL) {\n        return 1;\n    }\n    return 0;\n}\n\nstatic inline int\nPySys_GetOptionalAttr(PyObject *name, PyObject **value)\n{\n#if PY_VERSION_HEX >= 0x03000000\n    const char *name_str = PyUnicode_AsUTF8(name);\n#else\n    const char *name_str = PyString_AsString(name);\n#endif\n    if (name_str == NULL) {\n        *value = NULL;\n        return -1;\n    }\n\n    return PySys_GetOptionalAttrString(name_str, value);\n}\n#endif  // PY_VERSION_HEX < 0x030F00A1\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif  // PYTHONCAPI_COMPAT\n"
  },
  {
    "path": "src/watchdog/__init__.py",
    "content": ""
  },
  {
    "path": "src/watchdog/events.py",
    "content": "\"\"\":module: watchdog.events\n:synopsis: File system events and event handlers.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nEvent Classes\n-------------\n.. autoclass:: FileSystemEvent\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\n.. autoclass:: FileSystemMovedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileMovedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: DirMovedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileModifiedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: DirModifiedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileCreatedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileClosedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileClosedNoWriteEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileOpenedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: DirCreatedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: FileDeletedEvent\n   :members:\n   :show-inheritance:\n\n.. autoclass:: DirDeletedEvent\n   :members:\n   :show-inheritance:\n\n\nEvent Handler Classes\n---------------------\n.. autoclass:: FileSystemEventHandler\n   :members:\n   :show-inheritance:\n\n.. autoclass:: PatternMatchingEventHandler\n   :members:\n   :show-inheritance:\n\n.. autoclass:: RegexMatchingEventHandler\n   :members:\n   :show-inheritance:\n\n.. autoclass:: LoggingEventHandler\n   :members:\n   :show-inheritance:\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport os.path\nimport re\nfrom dataclasses import dataclass, field\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.utils.patterns import match_any_paths\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n\nEVENT_TYPE_MOVED = \"moved\"\nEVENT_TYPE_DELETED = \"deleted\"\nEVENT_TYPE_CREATED = \"created\"\nEVENT_TYPE_MODIFIED = \"modified\"\nEVENT_TYPE_CLOSED = \"closed\"\nEVENT_TYPE_CLOSED_NO_WRITE = \"closed_no_write\"\nEVENT_TYPE_OPENED = \"opened\"\n\n\n@dataclass(unsafe_hash=True)\nclass FileSystemEvent:\n    \"\"\"Immutable type that represents a file system event that is triggered\n    when a change occurs on the monitored file system.\n\n    All FileSystemEvent objects are required to be immutable and hence\n    can be used as keys in dictionaries or be added to sets.\n    \"\"\"\n\n    src_path: bytes | str\n    dest_path: bytes | str = \"\"\n    event_type: str = field(default=\"\", init=False)\n    is_directory: bool = field(default=False, init=False)\n\n    \"\"\"\n    True if event was synthesized; False otherwise.\n    These are events that weren't actually broadcast by the OS, but\n    are presumed to have happened based on other, actual events.\n    \"\"\"\n    is_synthetic: bool = field(default=False)\n\n\nclass FileSystemMovedEvent(FileSystemEvent):\n    \"\"\"File system event representing any kind of file system movement.\"\"\"\n\n    event_type = EVENT_TYPE_MOVED\n\n\n# File events.\n\n\nclass FileDeletedEvent(FileSystemEvent):\n    \"\"\"File system event representing file deletion on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_DELETED\n\n\nclass FileModifiedEvent(FileSystemEvent):\n    \"\"\"File system event representing file modification on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_MODIFIED\n\n\nclass FileCreatedEvent(FileSystemEvent):\n    \"\"\"File system event representing file creation on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_CREATED\n\n\nclass FileMovedEvent(FileSystemMovedEvent):\n    \"\"\"File system event representing file movement on the file system.\"\"\"\n\n\nclass FileClosedEvent(FileSystemEvent):\n    \"\"\"File system event representing file close on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_CLOSED\n\n\nclass FileClosedNoWriteEvent(FileSystemEvent):\n    \"\"\"File system event representing an unmodified file close on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_CLOSED_NO_WRITE\n\n\nclass FileOpenedEvent(FileSystemEvent):\n    \"\"\"File system event representing file close on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_OPENED\n\n\n# Directory events.\n\n\nclass DirDeletedEvent(FileSystemEvent):\n    \"\"\"File system event representing directory deletion on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_DELETED\n    is_directory = True\n\n\nclass DirModifiedEvent(FileSystemEvent):\n    \"\"\"File system event representing directory modification on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_MODIFIED\n    is_directory = True\n\n\nclass DirCreatedEvent(FileSystemEvent):\n    \"\"\"File system event representing directory creation on the file system.\"\"\"\n\n    event_type = EVENT_TYPE_CREATED\n    is_directory = True\n\n\nclass DirMovedEvent(FileSystemMovedEvent):\n    \"\"\"File system event representing directory movement on the file system.\"\"\"\n\n    is_directory = True\n\n\nclass FileSystemEventHandler:\n    \"\"\"Base file system event handler that you can override methods from.\"\"\"\n\n    def dispatch(self, event: FileSystemEvent) -> None:\n        \"\"\"Dispatches events to the appropriate methods.\n\n        :param event:\n            The event object representing the file system event.\n        :type event:\n            :class:`FileSystemEvent`\n        \"\"\"\n        self.on_any_event(event)\n        getattr(self, f\"on_{event.event_type}\")(event)\n\n    def on_any_event(self, event: FileSystemEvent) -> None:\n        \"\"\"Catch-all event handler.\n\n        :param event:\n            The event object representing the file system event.\n        :type event:\n            :class:`FileSystemEvent`\n        \"\"\"\n\n    def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:\n        \"\"\"Called when a file or a directory is moved or renamed.\n\n        :param event:\n            Event representing file/directory movement.\n        :type event:\n            :class:`DirMovedEvent` or :class:`FileMovedEvent`\n        \"\"\"\n\n    def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:\n        \"\"\"Called when a file or directory is created.\n\n        :param event:\n            Event representing file/directory creation.\n        :type event:\n            :class:`DirCreatedEvent` or :class:`FileCreatedEvent`\n        \"\"\"\n\n    def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:\n        \"\"\"Called when a file or directory is deleted.\n\n        :param event:\n            Event representing file/directory deletion.\n        :type event:\n            :class:`DirDeletedEvent` or :class:`FileDeletedEvent`\n        \"\"\"\n\n    def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:\n        \"\"\"Called when a file or directory is modified.\n\n        :param event:\n            Event representing file/directory modification.\n        :type event:\n            :class:`DirModifiedEvent` or :class:`FileModifiedEvent`\n        \"\"\"\n\n    def on_closed(self, event: FileClosedEvent) -> None:\n        \"\"\"Called when a file opened for writing is closed.\n\n        :param event:\n            Event representing file closing.\n        :type event:\n            :class:`FileClosedEvent`\n        \"\"\"\n\n    def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None:\n        \"\"\"Called when a file opened for reading is closed.\n\n        :param event:\n            Event representing file closing.\n        :type event:\n            :class:`FileClosedNoWriteEvent`\n        \"\"\"\n\n    def on_opened(self, event: FileOpenedEvent) -> None:\n        \"\"\"Called when a file is opened.\n\n        :param event:\n            Event representing file opening.\n        :type event:\n            :class:`FileOpenedEvent`\n        \"\"\"\n\n\nclass PatternMatchingEventHandler(FileSystemEventHandler):\n    \"\"\"Matches given patterns with file paths associated with occurring events.\n    Uses pathlib's `PurePath.match()` method. `patterns` and `ignore_patterns`\n    are expected to be a list of strings.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        patterns: list[str] | None = None,\n        ignore_patterns: list[str] | None = None,\n        ignore_directories: bool = False,\n        case_sensitive: bool = False,\n    ):\n        super().__init__()\n\n        self._patterns = patterns\n        self._ignore_patterns = ignore_patterns\n        self._ignore_directories = ignore_directories\n        self._case_sensitive = case_sensitive\n\n    @property\n    def patterns(self) -> list[str] | None:\n        \"\"\"(Read-only)\n        Patterns to allow matching event paths.\n        \"\"\"\n        return self._patterns\n\n    @property\n    def ignore_patterns(self) -> list[str] | None:\n        \"\"\"(Read-only)\n        Patterns to ignore matching event paths.\n        \"\"\"\n        return self._ignore_patterns\n\n    @property\n    def ignore_directories(self) -> bool:\n        \"\"\"(Read-only)\n        ``True`` if directories should be ignored; ``False`` otherwise.\n        \"\"\"\n        return self._ignore_directories\n\n    @property\n    def case_sensitive(self) -> bool:\n        \"\"\"(Read-only)\n        ``True`` if path names should be matched sensitive to case; ``False``\n        otherwise.\n        \"\"\"\n        return self._case_sensitive\n\n    def dispatch(self, event: FileSystemEvent) -> None:\n        \"\"\"Dispatches events to the appropriate methods.\n\n        :param event:\n            The event object representing the file system event.\n        :type event:\n            :class:`FileSystemEvent`\n        \"\"\"\n        if self.ignore_directories and event.is_directory:\n            return\n\n        paths = []\n        if hasattr(event, \"dest_path\"):\n            paths.append(os.fsdecode(event.dest_path))\n        if event.src_path:\n            paths.append(os.fsdecode(event.src_path))\n\n        if match_any_paths(\n            paths,\n            included_patterns=self.patterns,\n            excluded_patterns=self.ignore_patterns,\n            case_sensitive=self.case_sensitive,\n        ):\n            super().dispatch(event)\n\n\nclass RegexMatchingEventHandler(FileSystemEventHandler):\n    \"\"\"Matches given regexes with file paths associated with occurring events.\n    Uses the `re` module.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        regexes: list[str] | None = None,\n        ignore_regexes: list[str] | None = None,\n        ignore_directories: bool = False,\n        case_sensitive: bool = False,\n    ):\n        super().__init__()\n\n        if regexes is None:\n            regexes = [r\".*\"]\n        elif isinstance(regexes, str):\n            regexes = [regexes]\n        if ignore_regexes is None:\n            ignore_regexes = []\n        if case_sensitive:\n            self._regexes = [re.compile(r) for r in regexes]\n            self._ignore_regexes = [re.compile(r) for r in ignore_regexes]\n        else:\n            self._regexes = [re.compile(r, re.IGNORECASE) for r in regexes]\n            self._ignore_regexes = [re.compile(r, re.IGNORECASE) for r in ignore_regexes]\n        self._ignore_directories = ignore_directories\n        self._case_sensitive = case_sensitive\n\n    @property\n    def regexes(self) -> list[re.Pattern[str]]:\n        \"\"\"(Read-only)\n        Regexes to allow matching event paths.\n        \"\"\"\n        return self._regexes\n\n    @property\n    def ignore_regexes(self) -> list[re.Pattern[str]]:\n        \"\"\"(Read-only)\n        Regexes to ignore matching event paths.\n        \"\"\"\n        return self._ignore_regexes\n\n    @property\n    def ignore_directories(self) -> bool:\n        \"\"\"(Read-only)\n        ``True`` if directories should be ignored; ``False`` otherwise.\n        \"\"\"\n        return self._ignore_directories\n\n    @property\n    def case_sensitive(self) -> bool:\n        \"\"\"(Read-only)\n        ``True`` if path names should be matched sensitive to case; ``False``\n        otherwise.\n        \"\"\"\n        return self._case_sensitive\n\n    def dispatch(self, event: FileSystemEvent) -> None:\n        \"\"\"Dispatches events to the appropriate methods.\n\n        :param event:\n            The event object representing the file system event.\n        :type event:\n            :class:`FileSystemEvent`\n        \"\"\"\n        if self.ignore_directories and event.is_directory:\n            return\n\n        paths = []\n        if hasattr(event, \"dest_path\"):\n            paths.append(os.fsdecode(event.dest_path))\n        if event.src_path:\n            paths.append(os.fsdecode(event.src_path))\n\n        if any(r.match(p) for r in self.ignore_regexes for p in paths):\n            return\n\n        if any(r.match(p) for r in self.regexes for p in paths):\n            super().dispatch(event)\n\n\nclass LoggingEventHandler(FileSystemEventHandler):\n    \"\"\"Logs all the events captured.\"\"\"\n\n    def __init__(self, *, logger: logging.Logger | None = None) -> None:\n        super().__init__()\n        self.logger = logger or logging.root\n\n    def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:\n        super().on_moved(event)\n\n        what = \"directory\" if event.is_directory else \"file\"\n        self.logger.info(\"Moved %s: from %s to %s\", what, event.src_path, event.dest_path)\n\n    def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:\n        super().on_created(event)\n\n        what = \"directory\" if event.is_directory else \"file\"\n        self.logger.info(\"Created %s: %s\", what, event.src_path)\n\n    def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:\n        super().on_deleted(event)\n\n        what = \"directory\" if event.is_directory else \"file\"\n        self.logger.info(\"Deleted %s: %s\", what, event.src_path)\n\n    def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:\n        super().on_modified(event)\n\n        what = \"directory\" if event.is_directory else \"file\"\n        self.logger.info(\"Modified %s: %s\", what, event.src_path)\n\n    def on_closed(self, event: FileClosedEvent) -> None:\n        super().on_closed(event)\n\n        self.logger.info(\"Closed modified file: %s\", event.src_path)\n\n    def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None:\n        super().on_closed_no_write(event)\n\n        self.logger.info(\"Closed read file: %s\", event.src_path)\n\n    def on_opened(self, event: FileOpenedEvent) -> None:\n        super().on_opened(event)\n\n        self.logger.info(\"Opened file: %s\", event.src_path)\n\n\ndef generate_sub_moved_events(\n    src_dir_path: bytes | str,\n    dest_dir_path: bytes | str,\n) -> Generator[DirMovedEvent | FileMovedEvent]:\n    \"\"\"Generates an event list of :class:`DirMovedEvent` and\n    :class:`FileMovedEvent` objects for all the files and directories within\n    the given moved directory that were moved along with the directory.\n\n    :param src_dir_path:\n        The source path of the moved directory.\n    :param dest_dir_path:\n        The destination path of the moved directory.\n    :returns:\n        An iterable of file system events of type :class:`DirMovedEvent` and\n        :class:`FileMovedEvent`.\n    \"\"\"\n    for root, directories, filenames in os.walk(dest_dir_path):  # type: ignore[type-var]\n        for directory in directories:\n            full_path = os.path.join(root, directory)  # type: ignore[call-overload]\n            renamed_path = src_dir_path + full_path[len(dest_dir_path):] if src_dir_path else \"\"\n            yield DirMovedEvent(renamed_path, full_path, is_synthetic=True)\n        for filename in filenames:\n            full_path = os.path.join(root, filename)  # type: ignore[call-overload]\n            renamed_path = src_dir_path + full_path[len(dest_dir_path):] if src_dir_path else \"\"\n            yield FileMovedEvent(renamed_path, full_path, is_synthetic=True)\n\n\ndef generate_sub_created_events(src_dir_path: bytes | str) -> Generator[DirCreatedEvent | FileCreatedEvent]:\n    \"\"\"Generates an event list of :class:`DirCreatedEvent` and\n    :class:`FileCreatedEvent` objects for all the files and directories within\n    the given moved directory that were moved along with the directory.\n\n    :param src_dir_path:\n        The source path of the created directory.\n    :returns:\n        An iterable of file system events of type :class:`DirCreatedEvent` and\n        :class:`FileCreatedEvent`.\n    \"\"\"\n    for root, directories, filenames in os.walk(src_dir_path):  # type: ignore[type-var]\n        for directory in directories:\n            full_path = os.path.join(root, directory)  # type: ignore[call-overload]\n            yield DirCreatedEvent(full_path, is_synthetic=True)\n        for filename in filenames:\n            full_path = os.path.join(root, filename)  # type: ignore[call-overload]\n            yield FileCreatedEvent(full_path, is_synthetic=True)\n"
  },
  {
    "path": "src/watchdog/observers/__init__.py",
    "content": "\"\"\":module: watchdog.observers\n:synopsis: Observer that picks a native implementation if available.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nClasses\n=======\n.. autoclass:: Observer\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\nObserver thread that schedules watching directories and dispatches\ncalls to event handlers.\n\nYou can also import platform specific classes directly and use it instead\nof :class:`Observer`.  Here is a list of implemented observer classes.:\n\n============== ================================ ==============================\nClass          Platforms                        Note\n============== ================================ ==============================\n|Inotify|      Linux 2.6.13+                    ``inotify(7)`` based observer\n|FSEvents|     macOS                            FSEvents based observer\n|Kqueue|       macOS and BSD with kqueue(2)     ``kqueue(2)`` based observer\n|WinApi|       Microsoft Windows                Windows API-based observer\n|Polling|      Any                              fallback implementation\n============== ================================ ==============================\n\n.. |Inotify|     replace:: :class:`.inotify.InotifyObserver`\n.. |FSEvents|    replace:: :class:`.fsevents.FSEventsObserver`\n.. |Kqueue|      replace:: :class:`.kqueue.KqueueObserver`\n.. |WinApi|      replace:: :class:`.read_directory_changes.WindowsApiObserver`\n.. |Polling|     replace:: :class:`.polling.PollingObserver`\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport warnings\nfrom typing import TYPE_CHECKING, Protocol\n\nfrom watchdog.utils import UnsupportedLibcError, platform\n\nif TYPE_CHECKING:\n    from watchdog.observers.api import BaseObserver\n\n\nclass ObserverType(Protocol):\n    def __call__(self, *, timeout: float = ...) -> BaseObserver: ...\n\n\ndef _get_observer_cls() -> ObserverType:\n    if platform.is_linux():\n        with contextlib.suppress(UnsupportedLibcError):\n            from watchdog.observers.inotify import InotifyObserver\n\n            return InotifyObserver\n    elif platform.is_darwin():\n        try:\n            from watchdog.observers.fsevents import FSEventsObserver\n        except Exception:\n            try:\n                from watchdog.observers.kqueue import KqueueObserver\n            except Exception:\n                warnings.warn(\"Failed to import fsevents and kqueue. Fall back to polling.\", stacklevel=1)\n            else:\n                warnings.warn(\"Failed to import fsevents. Fall back to kqueue\", stacklevel=1)\n                return KqueueObserver\n        else:\n            return FSEventsObserver\n    elif platform.is_windows():\n        try:\n            from watchdog.observers.read_directory_changes import WindowsApiObserver\n        except Exception:\n            warnings.warn(\"Failed to import `read_directory_changes`. Fall back to polling.\", stacklevel=1)\n        else:\n            return WindowsApiObserver\n    elif platform.is_bsd():\n        from watchdog.observers.kqueue import KqueueObserver\n\n        return KqueueObserver\n\n    from watchdog.observers.polling import PollingObserver\n\n    return PollingObserver\n\n\nObserver = _get_observer_cls()\n\n__all__ = [\"Observer\"]\n"
  },
  {
    "path": "src/watchdog/observers/api.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport queue\nimport threading\nfrom collections import defaultdict\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.utils import BaseThread\nfrom watchdog.utils.bricks import SkipRepeatsQueue\n\nif TYPE_CHECKING:\n    import types\n\n    from typing_extensions import Self\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n\nDEFAULT_EMITTER_TIMEOUT = 1.0  # in seconds\nDEFAULT_OBSERVER_TIMEOUT = 1.0  # in seconds\n\n\nclass EventQueue(SkipRepeatsQueue):\n    \"\"\"Thread-safe event queue based on a special queue that skips adding\n    the same event (:class:`FileSystemEvent`) multiple times consecutively.\n    Thus avoiding dispatching multiple event handling\n    calls when multiple identical events are produced quicker than an observer\n    can consume them.\n    \"\"\"\n\n\nclass ObservedWatch:\n    \"\"\"An scheduled watch.\n\n    :param path:\n        Path string.\n    :param recursive:\n        ``True`` if watch is recursive; ``False`` otherwise.\n    :param event_filter:\n        Optional collection of :class:`watchdog.events.FileSystemEvent` to watch\n    \"\"\"\n\n    def __init__(\n        self,\n        path: str | Path,\n        *,\n        recursive: bool,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        follow_symlink: bool = False,\n    ):\n        self._path = str(path) if isinstance(path, Path) else path\n        self._is_recursive = recursive\n        self._follow_symlink = follow_symlink\n        self._event_filter = frozenset(event_filter) if event_filter is not None else None\n\n    @property\n    def path(self) -> str:\n        \"\"\"The path that this watch monitors.\"\"\"\n        return self._path\n\n    @property\n    def is_recursive(self) -> bool:\n        \"\"\"Determines whether subdirectories are watched for the path.\"\"\"\n        return self._is_recursive\n\n    @property\n    def follow_symlink(self) -> bool:\n        \"\"\"Determines whether symlink are followed.\"\"\"\n        return self._follow_symlink\n\n    @property\n    def event_filter(self) -> frozenset[type[FileSystemEvent]] | None:\n        \"\"\"Collection of event types watched for the path\"\"\"\n        return self._event_filter\n\n    @property\n    def key(self) -> tuple[str, bool, frozenset[type[FileSystemEvent]] | None]:\n        return self.path, self.is_recursive, self.event_filter\n\n    def __eq__(self, watch: object) -> bool:\n        if not isinstance(watch, ObservedWatch):\n            return NotImplemented\n        return self.key == watch.key\n\n    def __ne__(self, watch: object) -> bool:\n        if not isinstance(watch, ObservedWatch):\n            return NotImplemented\n        return self.key != watch.key\n\n    def __hash__(self) -> int:\n        return hash(self.key)\n\n    def __repr__(self) -> str:\n        if self.event_filter is not None:\n            event_filter_str = \"|\".join(sorted(_cls.__name__ for _cls in self.event_filter))\n            event_filter_str = f\", event_filter={event_filter_str}\"\n        else:\n            event_filter_str = \"\"\n        return f\"<{type(self).__name__}: path={self.path!r}, is_recursive={self.is_recursive}{event_filter_str}>\"\n\n\n# Observer classes\nclass EventEmitter(BaseThread):\n    \"\"\"Producer thread base class subclassed by event emitters\n    that generate events and populate a queue with them.\n\n    :param event_queue:\n        The event queue to populate with generated events.\n    :type event_queue:\n        :class:`watchdog.events.EventQueue`\n    :param watch:\n        The watch to observe and produce events for.\n    :type watch:\n        :class:`ObservedWatch`\n    :param timeout:\n        Timeout (in seconds) between successive attempts at reading events.\n    :type timeout:\n        ``float``\n    :param event_filter:\n        Collection of event types to emit, or None for no filtering (default).\n    :type event_filter:\n        Iterable[:class:`watchdog.events.FileSystemEvent`] | None\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n    ) -> None:\n        super().__init__()\n        self._event_queue = event_queue\n        self._watch = watch\n        self._timeout = timeout\n        self._event_filter = frozenset(event_filter) if event_filter is not None else None\n\n    @property\n    def timeout(self) -> float:\n        \"\"\"Blocking timeout for reading events.\"\"\"\n        return self._timeout\n\n    @property\n    def watch(self) -> ObservedWatch:\n        \"\"\"The watch associated with this emitter.\"\"\"\n        return self._watch\n\n    def queue_event(self, event: FileSystemEvent) -> None:\n        \"\"\"Queues a single event.\n\n        :param event:\n            Event to be queued.\n        :type event:\n            An instance of :class:`watchdog.events.FileSystemEvent`\n            or a subclass.\n        \"\"\"\n        if self._event_filter is None or any(isinstance(event, cls) for cls in self._event_filter):\n            self._event_queue.put((event, self.watch))\n\n    def queue_events(self, timeout: float) -> None:\n        \"\"\"Override this method to populate the event queue with events\n        per interval period.\n\n        :param timeout:\n            Timeout (in seconds) between successive attempts at\n            reading events.\n        :type timeout:\n            ``float``\n        \"\"\"\n\n    def run(self) -> None:\n        while self.should_keep_running():\n            self.queue_events(self.timeout)\n\n\nclass EventDispatcher(BaseThread):\n    \"\"\"Consumer thread base class subclassed by event observer threads\n    that dispatch events from an event queue to appropriate event handlers.\n\n    :param timeout:\n        Timeout value (in seconds) passed to emitters\n        constructions in the child class BaseObserver.\n    :type timeout:\n        ``float``\n    \"\"\"\n\n    stop_event = object()\n    \"\"\"Event inserted into the queue to signal a requested stop.\"\"\"\n\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__()\n        self._event_queue = EventQueue()\n        self._timeout = timeout\n\n    @property\n    def timeout(self) -> float:\n        \"\"\"Timeout value to construct emitters with.\"\"\"\n        return self._timeout\n\n    def stop(self) -> None:\n        BaseThread.stop(self)\n        with contextlib.suppress(queue.Full):\n            self.event_queue.put_nowait(EventDispatcher.stop_event)\n\n    @property\n    def event_queue(self) -> EventQueue:\n        \"\"\"The event queue which is populated with file system events\n        by emitters and from which events are dispatched by a dispatcher\n        thread.\n        \"\"\"\n        return self._event_queue\n\n    def dispatch_events(self, event_queue: EventQueue) -> None:\n        \"\"\"Override this method to consume events from an event queue, blocking\n        on the queue for the specified timeout before raising :class:`queue.Empty`.\n\n        :param event_queue:\n            Event queue to populate with one set of events.\n        :type event_queue:\n            :class:`EventQueue`\n        :raises:\n            :class:`queue.Empty`\n        \"\"\"\n\n    def run(self) -> None:\n        while self.should_keep_running():\n            try:\n                self.dispatch_events(self.event_queue)\n            except queue.Empty:\n                continue\n\n\nclass BaseObserver(EventDispatcher):\n    \"\"\"Base observer.\"\"\"\n\n    def __init__(self, emitter_class: type[EventEmitter], *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(timeout=timeout)\n        self._emitter_class = emitter_class\n        self._lock = threading.RLock()\n        self._watches: set[ObservedWatch] = set()\n        self._handlers: defaultdict[ObservedWatch, set[FileSystemEventHandler]] = defaultdict(set)\n        self._emitters: set[EventEmitter] = set()\n        self._emitter_for_watch: dict[ObservedWatch, EventEmitter] = {}\n\n    def _add_emitter(self, emitter: EventEmitter) -> None:\n        self._emitter_for_watch[emitter.watch] = emitter\n        self._emitters.add(emitter)\n\n    def _remove_emitter(self, emitter: EventEmitter) -> None:\n        del self._emitter_for_watch[emitter.watch]\n        self._emitters.remove(emitter)\n        emitter.stop()\n        with contextlib.suppress(RuntimeError):\n            emitter.join()\n\n    def _clear_emitters(self) -> None:\n        for emitter in self._emitters:\n            emitter.stop()\n        for emitter in self._emitters:\n            with contextlib.suppress(RuntimeError):\n                emitter.join()\n        self._emitters.clear()\n        self._emitter_for_watch.clear()\n\n    def _add_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:\n        self._handlers[watch].add(event_handler)\n\n    def _remove_handlers_for_watch(self, watch: ObservedWatch) -> None:\n        del self._handlers[watch]\n\n    @property\n    def emitters(self) -> set[EventEmitter]:\n        \"\"\"Returns event emitter created by this observer.\"\"\"\n        return self._emitters\n\n    def start(self) -> None:\n        for emitter in self._emitters.copy():\n            try:\n                emitter.start()\n            except Exception:\n                self._remove_emitter(emitter)\n                raise\n        super().start()\n\n    def __enter__(self) -> Self:\n        \"\"\"Context manager entry. Starts the observer if not already running.\"\"\"\n        if not self.is_alive():\n            self.start()\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: types.TracebackType | None,\n    ) -> None:\n        \"\"\"Context manager exit. Stops the observer and waits for it to finish.\"\"\"\n        if self.is_alive():\n            self.stop()\n            self.join()\n\n    def schedule(\n        self,\n        event_handler: FileSystemEventHandler,\n        path: str | Path,\n        *,\n        recursive: bool = False,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        follow_symlink: bool = False,\n    ) -> ObservedWatch:\n        \"\"\"Schedules watching a path and calls appropriate methods specified\n        in the given event handler in response to file system events.\n\n        :param event_handler:\n            An event handler instance that has appropriate event handling\n            methods which will be called by the observer in response to\n            file system events.\n        :type event_handler:\n            :class:`watchdog.events.FileSystemEventHandler` or a subclass\n        :param path:\n            Directory path that will be monitored.\n        :type path:\n            ``str`` or :class:`pathlib.Path`\n        :param recursive:\n            ``True`` if events will be emitted for sub-directories\n            traversed recursively; ``False`` otherwise.\n        :type recursive:\n            ``bool``\n        :param event_filter:\n            Collection of event types to emit, or None for no filtering (default).\n        :type event_filter:\n            Iterable[:class:`watchdog.events.FileSystemEvent`] | None\n        :return:\n            An :class:`ObservedWatch` object instance representing\n            a watch.\n        \"\"\"\n        with self._lock:\n            watch = ObservedWatch(path, recursive=recursive, event_filter=event_filter, follow_symlink=follow_symlink)\n            self._add_handler_for_watch(event_handler, watch)\n\n            # If we don't have an emitter for this watch already, create it.\n            if watch not in self._emitter_for_watch:\n                emitter = self._emitter_class(self.event_queue, watch, timeout=self.timeout, event_filter=event_filter)\n                if self.is_alive():\n                    emitter.start()\n                self._add_emitter(emitter)\n            self._watches.add(watch)\n        return watch\n\n    def add_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:\n        \"\"\"Adds a handler for the given watch.\n\n        :param event_handler:\n            An event handler instance that has appropriate event handling\n            methods which will be called by the observer in response to\n            file system events.\n        :type event_handler:\n            :class:`watchdog.events.FileSystemEventHandler` or a subclass\n        :param watch:\n            The watch to add a handler for.\n        :type watch:\n            An instance of :class:`ObservedWatch` or a subclass of\n            :class:`ObservedWatch`\n        \"\"\"\n        with self._lock:\n            self._add_handler_for_watch(event_handler, watch)\n\n    def remove_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None:\n        \"\"\"Removes a handler for the given watch.\n\n        :param event_handler:\n            An event handler instance that has appropriate event handling\n            methods which will be called by the observer in response to\n            file system events.\n        :type event_handler:\n            :class:`watchdog.events.FileSystemEventHandler` or a subclass\n        :param watch:\n            The watch to remove a handler for.\n        :type watch:\n            An instance of :class:`ObservedWatch` or a subclass of\n            :class:`ObservedWatch`\n        \"\"\"\n        with self._lock:\n            self._handlers[watch].remove(event_handler)\n\n    def unschedule(self, watch: ObservedWatch) -> None:\n        \"\"\"Unschedules a watch.\n\n        :param watch:\n            The watch to unschedule.\n        :type watch:\n            An instance of :class:`ObservedWatch` or a subclass of\n            :class:`ObservedWatch`\n        \"\"\"\n        with self._lock:\n            emitter = self._emitter_for_watch[watch]\n            del self._handlers[watch]\n            self._remove_emitter(emitter)\n            self._watches.remove(watch)\n\n    def unschedule_all(self) -> None:\n        \"\"\"Unschedules all watches and detaches all associated event handlers.\"\"\"\n        with self._lock:\n            self._handlers.clear()\n            self._clear_emitters()\n            self._watches.clear()\n\n    def on_thread_stop(self) -> None:\n        self.unschedule_all()\n\n    def dispatch_events(self, event_queue: EventQueue) -> None:\n        entry = event_queue.get(block=True)\n        if entry is EventDispatcher.stop_event:\n            event_queue.task_done()\n            return\n\n        event, watch = entry\n\n        with self._lock:\n            # To allow unschedule/stop and safe removal of event handlers\n            # within event handlers itself, check if the handler is still\n            # registered after every dispatch.\n            for handler in self._handlers[watch].copy():\n                if handler in self._handlers[watch]:\n                    handler.dispatch(event)\n        event_queue.task_done()\n"
  },
  {
    "path": "src/watchdog/observers/fsevents.py",
    "content": "\"\"\":module: watchdog.observers.fsevents\n:synopsis: FSEvents based emitter implementation.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:platforms: macOS\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport os\nimport threading\nimport time\nimport unicodedata\nfrom typing import TYPE_CHECKING\n\nimport _watchdog_fsevents as _fsevents\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    generate_sub_created_events,\n    generate_sub_moved_events,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\nfrom watchdog.utils.dirsnapshot import DirectorySnapshot\n\nif TYPE_CHECKING:\n    from pathlib import Path\n\n    from watchdog.events import FileSystemEvent, FileSystemEventHandler\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\n\nlogger = logging.getLogger(\"fsevents\")\n\n\nclass FSEventsEmitter(EventEmitter):\n    \"\"\"macOS FSEvents Emitter class.\n\n    :param event_queue:\n        The event queue to fill with events.\n    :param watch:\n        A watch object representing the directory to monitor.\n    :type watch:\n        :class:`watchdog.observers.api.ObservedWatch`\n    :param timeout:\n        Read events blocking timeout (in seconds).\n    :param event_filter:\n        Collection of event types to emit, or None for no filtering (default).\n    :param suppress_history:\n        The FSEvents API may emit historic events up to 30 sec before the watch was\n        started. When ``suppress_history`` is ``True``, those events will be suppressed\n        by creating a directory snapshot of the watched path before starting the stream\n        as a reference to suppress old events. Warning: This may result in significant\n        memory usage in case of a large number of items in the watched path.\n    :type timeout:\n        ``float``\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        suppress_history: bool = False,\n    ) -> None:\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n        self._fs_view: set[int] = set()\n        self.suppress_history = suppress_history\n        self._start_time = 0.0\n        self._starting_state: DirectorySnapshot | None = None\n        self._lock = threading.Lock()\n        self._absolute_watch_path = os.path.realpath(os.path.abspath(os.path.expanduser(self.watch.path)))\n\n    def on_thread_stop(self) -> None:\n        _fsevents.remove_watch(self.watch)\n        _fsevents.stop(self)\n\n    def queue_event(self, event: FileSystemEvent) -> None:\n        # fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop\n        # all the events here which do not have a src_path / dest_path that matches the watched path\n        if self._watch.is_recursive or not self._is_recursive_event(event):\n            logger.debug(\"queue_event %s\", event)\n            EventEmitter.queue_event(self, event)\n        else:\n            logger.debug(\"drop event %s\", event)\n\n    def _is_recursive_event(self, event: FileSystemEvent) -> bool:\n        src_path = event.src_path if event.is_directory else os.path.dirname(event.src_path)\n        if src_path == self._absolute_watch_path:\n            return False\n\n        if isinstance(event, (FileMovedEvent, DirMovedEvent)):\n            # when moving something into the watch path we must always take the dirname,\n            # otherwise we miss out on `DirMovedEvent`s\n            dest_path = os.path.dirname(event.dest_path)\n            if dest_path == self._absolute_watch_path:\n                return False\n\n        return True\n\n    def _queue_created_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:\n        cls = DirCreatedEvent if event.is_directory else FileCreatedEvent\n        self.queue_event(cls(src_path))\n        self.queue_event(DirModifiedEvent(dirname))\n\n    def _queue_deleted_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:\n        cls = DirDeletedEvent if event.is_directory else FileDeletedEvent\n        self.queue_event(cls(src_path))\n        self.queue_event(DirModifiedEvent(dirname))\n\n    def _queue_modified_event(self, event: FileSystemEvent, src_path: bytes | str, dirname: bytes | str) -> None:\n        cls = DirModifiedEvent if event.is_directory else FileModifiedEvent\n        self.queue_event(cls(src_path))\n\n    def _queue_renamed_event(\n        self,\n        src_event: FileSystemEvent,\n        src_path: bytes | str,\n        dst_path: bytes | str,\n        src_dirname: bytes | str,\n        dst_dirname: bytes | str,\n    ) -> None:\n        cls = DirMovedEvent if src_event.is_directory else FileMovedEvent\n        dst_path = self._encode_path(dst_path)\n        self.queue_event(cls(src_path, dst_path))\n        self.queue_event(DirModifiedEvent(src_dirname))\n        self.queue_event(DirModifiedEvent(dst_dirname))\n\n    def _is_historic_created_event(self, event: _fsevents.NativeEvent) -> bool:\n        # We only queue a created event if the item was created after we\n        # started the FSEventsStream.\n\n        in_history = event.inode in self._fs_view\n\n        if self._starting_state:\n            try:\n                old_inode = self._starting_state.inode(event.path)[0]\n                before_start = old_inode == event.inode\n            except KeyError:\n                before_start = False\n        else:\n            before_start = False\n\n        return in_history or before_start\n\n    @staticmethod\n    def _is_meta_mod(event: _fsevents.NativeEvent) -> bool:\n        \"\"\"Returns True if the event indicates a change in metadata.\"\"\"\n        return event.is_inode_meta_mod or event.is_xattr_mod or event.is_owner_change\n\n    def queue_events(self, timeout: float, events: list[_fsevents.NativeEvent]) -> None:  # type: ignore[override]\n        if logger.getEffectiveLevel() <= logging.DEBUG:\n            for event in events:\n                flags = \", \".join(attr for attr in dir(event) if getattr(event, attr) is True)\n                logger.debug(\"%s: %s\", event, flags)\n\n        if time.monotonic() - self._start_time > 60:\n            # Event history is no longer needed, let's free some memory.\n            self._starting_state = None\n\n        while events:\n            event = events.pop(0)\n\n            src_path = self._encode_path(event.path)\n            src_dirname = os.path.dirname(src_path)\n\n            try:\n                stat = os.stat(src_path)\n            except OSError:\n                stat = None\n\n            exists = stat and stat.st_ino == event.inode\n\n            # FSevents may coalesce multiple events for the same item + path into a\n            # single event. However, events are never coalesced for different items at\n            # the same path or for the same item at different paths. Therefore, the\n            # event chains \"removed -> created\" and \"created -> renamed -> removed\" will\n            # never emit a single native event and a deleted event *always* means that\n            # the item no longer existed at the end of the event chain.\n\n            # Some events will have a spurious `is_created` flag set, coalesced from an\n            # already emitted and processed CreatedEvent. To filter those, we keep track\n            # of all inodes which we know to be already created. This is safer than\n            # keeping track of paths since paths are more likely to be reused than\n            # inodes.\n\n            # Likewise, some events will have a spurious `is_modified`,\n            # `is_inode_meta_mod` or `is_xattr_mod` flag set. We currently do not\n            # suppress those but could do so if the item still exists by caching the\n            # stat result and verifying that it did change.\n\n            if event.is_created and event.is_removed:\n                # Events will only be coalesced for the same item / inode.\n                # The sequence deleted -> created therefore cannot occur.\n                # Any combination with renamed cannot occur either.\n\n                if not self._is_historic_created_event(event):\n                    self._queue_created_event(event, src_path, src_dirname)\n\n                self._fs_view.add(event.inode)\n\n                if event.is_modified or self._is_meta_mod(event):\n                    self._queue_modified_event(event, src_path, src_dirname)\n\n                self._queue_deleted_event(event, src_path, src_dirname)\n                self._fs_view.discard(event.inode)\n\n            else:\n                if event.is_created and not self._is_historic_created_event(event):\n                    self._queue_created_event(event, src_path, src_dirname)\n\n                self._fs_view.add(event.inode)\n\n                if event.is_modified or self._is_meta_mod(event):\n                    self._queue_modified_event(event, src_path, src_dirname)\n\n                if event.is_renamed:\n                    # Check if we have a corresponding destination event in the watched path.\n                    dst_event = next(\n                        iter(e for e in events if e.is_renamed and e.inode == event.inode),\n                        None,\n                    )\n\n                    if dst_event:\n                        # Item was moved within the watched folder.\n                        logger.debug(\"Destination event for rename is %s\", dst_event)\n\n                        dst_path = self._encode_path(dst_event.path)\n                        dst_dirname = os.path.dirname(dst_path)\n\n                        self._queue_renamed_event(event, src_path, dst_path, src_dirname, dst_dirname)\n                        self._fs_view.add(event.inode)\n\n                        for sub_moved_event in generate_sub_moved_events(src_path, dst_path):\n                            self.queue_event(sub_moved_event)\n\n                        # Process any coalesced flags for the dst_event.\n\n                        events.remove(dst_event)\n\n                        if dst_event.is_modified or self._is_meta_mod(dst_event):\n                            self._queue_modified_event(dst_event, dst_path, dst_dirname)\n\n                        if dst_event.is_removed:\n                            self._queue_deleted_event(dst_event, dst_path, dst_dirname)\n                            self._fs_view.discard(dst_event.inode)\n\n                    elif exists:\n                        # This is the destination event, item was moved into the watched\n                        # folder.\n                        self._queue_created_event(event, src_path, src_dirname)\n                        self._fs_view.add(event.inode)\n\n                        for sub_created_event in generate_sub_created_events(src_path):\n                            self.queue_event(sub_created_event)\n\n                    else:\n                        # This is the source event, item was moved out of the watched\n                        # folder.\n                        self._queue_deleted_event(event, src_path, src_dirname)\n                        self._fs_view.discard(event.inode)\n\n                        # Skip further coalesced processing.\n                        continue\n\n                if event.is_removed:\n                    # Won't occur together with renamed.\n                    self._queue_deleted_event(event, src_path, src_dirname)\n                    self._fs_view.discard(event.inode)\n\n            if event.is_root_changed:\n                # This will be set if root or any of its parents is renamed or deleted.\n                # TODO: find out new path and generate DirMovedEvent?\n                self.queue_event(DirDeletedEvent(self.watch.path))\n                logger.debug(\"Stopping because root path was changed\")\n                self.stop()\n\n                self._fs_view.clear()\n\n    def events_callback(self, paths: list[bytes], inodes: list[int], flags: list[int], ids: list[int]) -> None:\n        \"\"\"Callback passed to FSEventStreamCreate(), it will receive all\n        FS events and queue them.\n        \"\"\"\n        cls = _fsevents.NativeEvent\n        try:\n            events = [\n                cls(path, inode, event_flags, event_id)\n                for path, inode, event_flags, event_id in zip(paths, inodes, flags, ids)\n            ]\n            with self._lock:\n                self.queue_events(self.timeout, events)\n        except Exception:\n            logger.exception(\"Unhandled exception in fsevents callback\")\n\n    def run(self) -> None:\n        self.pathnames = [self.watch.path]\n        self._start_time = time.monotonic()\n        try:\n            _fsevents.add_watch(self, self.watch, self.events_callback, self.pathnames)\n            _fsevents.read_events(self)\n        except Exception:\n            logger.exception(\"Unhandled exception in FSEventsEmitter\")\n\n    def on_thread_start(self) -> None:\n        if self.suppress_history:\n            watch_path = os.fsdecode(self.watch.path) if isinstance(self.watch.path, bytes) else self.watch.path\n            self._starting_state = DirectorySnapshot(watch_path)\n\n    def _encode_path(self, path: bytes | str) -> bytes | str:\n        \"\"\"Encode path only if bytes were passed to this emitter.\"\"\"\n        return os.fsencode(path) if isinstance(self.watch.path, bytes) else path\n\n\nclass FSEventsObserver(BaseObserver):\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(FSEventsEmitter, timeout=timeout)\n\n    def schedule(\n        self,\n        event_handler: FileSystemEventHandler,\n        path: str | Path,\n        *,\n        recursive: bool = False,\n        follow_symlink: bool = False,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n    ) -> ObservedWatch:\n        # Fix for issue #26: Trace/BPT error when given a unicode path\n        # string. https://github.com/gorakhargosh/watchdog/issues#issue/26\n        if isinstance(path, str):\n            path = unicodedata.normalize(\"NFC\", path)\n\n        return super().schedule(\n            event_handler, path, recursive=recursive, follow_symlink=follow_symlink, event_filter=event_filter\n        )\n"
  },
  {
    "path": "src/watchdog/observers/fsevents2.py",
    "content": "\"\"\":module: watchdog.observers.fsevents2\n:synopsis: FSEvents based emitter implementation.\n:author: thomas.amland@gmail.com (Thomas Amland)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:platforms: macOS\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport os\nimport queue\nimport unicodedata\nimport warnings\nfrom threading import Thread\nfrom typing import TYPE_CHECKING\n\n# pyobjc\nimport AppKit\nfrom FSEvents import (\n    CFRunLoopGetCurrent,\n    CFRunLoopRun,\n    CFRunLoopStop,\n    FSEventStreamCreate,\n    FSEventStreamInvalidate,\n    FSEventStreamRelease,\n    FSEventStreamScheduleWithRunLoop,\n    FSEventStreamStart,\n    FSEventStreamStop,\n    kCFAllocatorDefault,\n    kCFRunLoopDefaultMode,\n    kFSEventStreamCreateFlagFileEvents,\n    kFSEventStreamCreateFlagNoDefer,\n    kFSEventStreamEventFlagItemChangeOwner,\n    kFSEventStreamEventFlagItemCreated,\n    kFSEventStreamEventFlagItemFinderInfoMod,\n    kFSEventStreamEventFlagItemInodeMetaMod,\n    kFSEventStreamEventFlagItemIsDir,\n    kFSEventStreamEventFlagItemIsSymlink,\n    kFSEventStreamEventFlagItemModified,\n    kFSEventStreamEventFlagItemRemoved,\n    kFSEventStreamEventFlagItemRenamed,\n    kFSEventStreamEventFlagItemXattrMod,\n    kFSEventStreamEventIdSinceNow,\n)\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    FileSystemEvent,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\nlogger = logging.getLogger(__name__)\n\nmessage = \"watchdog.observers.fsevents2 is deprecated and will be removed in a future release.\"\nwarnings.warn(message, category=DeprecationWarning, stacklevel=1)\nlogger.warning(message)\n\n\nclass FSEventsQueue(Thread):\n    \"\"\"Low level FSEvents client.\"\"\"\n\n    def __init__(self, path: bytes | str) -> None:\n        Thread.__init__(self)\n        self._queue: queue.Queue[list[NativeEvent] | None] = queue.Queue()\n        self._run_loop = None\n\n        if isinstance(path, bytes):\n            path = os.fsdecode(path)\n        self._path = unicodedata.normalize(\"NFC\", path)\n\n        context = None\n        latency = 1.0\n        self._stream_ref = FSEventStreamCreate(\n            kCFAllocatorDefault,\n            self._callback,\n            context,\n            [self._path],\n            kFSEventStreamEventIdSinceNow,\n            latency,\n            kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents,\n        )\n        if self._stream_ref is None:\n            error = \"FSEvents. Could not create stream.\"\n            raise OSError(error)\n\n    def run(self) -> None:\n        pool = AppKit.NSAutoreleasePool.alloc().init()\n        self._run_loop = CFRunLoopGetCurrent()\n        FSEventStreamScheduleWithRunLoop(self._stream_ref, self._run_loop, kCFRunLoopDefaultMode)\n        if not FSEventStreamStart(self._stream_ref):\n            FSEventStreamInvalidate(self._stream_ref)\n            FSEventStreamRelease(self._stream_ref)\n            error = \"FSEvents. Could not start stream.\"\n            raise OSError(error)\n\n        CFRunLoopRun()\n        FSEventStreamStop(self._stream_ref)\n        FSEventStreamInvalidate(self._stream_ref)\n        FSEventStreamRelease(self._stream_ref)\n        del pool\n        # Make sure waiting thread is notified\n        self._queue.put(None)\n\n    def stop(self) -> None:\n        if self._run_loop is not None:\n            CFRunLoopStop(self._run_loop)\n\n    def _callback(\n        self,\n        stream_ref: int,\n        client_callback_info: Callable,\n        num_events: int,\n        event_paths: list[bytes],\n        event_flags: list[int],\n        event_ids: list[int],\n    ) -> None:\n        events = [NativeEvent(path, flags, _id) for path, flags, _id in zip(event_paths, event_flags, event_ids)]\n        logger.debug(\"FSEvents callback. Got %d events:\", num_events)\n        for e in events:\n            logger.debug(e)\n        self._queue.put(events)\n\n    def read_events(self) -> list[NativeEvent] | None:\n        \"\"\"Returns a list or one or more events, or None if there are no more\n        events to be read.\n        \"\"\"\n        return self._queue.get() if self.is_alive() else None\n\n\nclass NativeEvent:\n    def __init__(self, path: bytes, flags: int, event_id: int) -> None:\n        self.path = path\n        self.flags = flags\n        self.event_id = event_id\n        self.is_created = bool(flags & kFSEventStreamEventFlagItemCreated)\n        self.is_removed = bool(flags & kFSEventStreamEventFlagItemRemoved)\n        self.is_renamed = bool(flags & kFSEventStreamEventFlagItemRenamed)\n        self.is_modified = bool(flags & kFSEventStreamEventFlagItemModified)\n        self.is_change_owner = bool(flags & kFSEventStreamEventFlagItemChangeOwner)\n        self.is_inode_meta_mod = bool(flags & kFSEventStreamEventFlagItemInodeMetaMod)\n        self.is_finder_info_mod = bool(flags & kFSEventStreamEventFlagItemFinderInfoMod)\n        self.is_xattr_mod = bool(flags & kFSEventStreamEventFlagItemXattrMod)\n        self.is_symlink = bool(flags & kFSEventStreamEventFlagItemIsSymlink)\n        self.is_directory = bool(flags & kFSEventStreamEventFlagItemIsDir)\n\n    @property\n    def _event_type(self) -> str:\n        if self.is_created:\n            return \"Created\"\n        if self.is_removed:\n            return \"Removed\"\n        if self.is_renamed:\n            return \"Renamed\"\n        if self.is_modified:\n            return \"Modified\"\n        if self.is_inode_meta_mod:\n            return \"InodeMetaMod\"\n        if self.is_xattr_mod:\n            return \"XattrMod\"\n        return \"Unknown\"\n\n    def __repr__(self) -> str:\n        return (\n            f\"<{type(self).__name__}: path={self.path!r}, type={self._event_type},\"\n            f\" is_dir={self.is_directory}, flags={hex(self.flags)}, id={self.event_id}>\"\n        )\n\n\nclass FSEventsEmitter(EventEmitter):\n    \"\"\"FSEvents based event emitter. Handles conversion of native events.\"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n    ):\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n        self._fsevents = FSEventsQueue(watch.path)\n        self._fsevents.start()\n\n    def on_thread_stop(self) -> None:\n        self._fsevents.stop()\n\n    def queue_events(self, timeout: float) -> None:\n        events = self._fsevents.read_events()\n        if events is None:\n            return\n        i = 0\n        while i < len(events):\n            event = events[i]\n\n            cls: type[FileSystemEvent]\n            # For some reason the create and remove flags are sometimes also\n            # set for rename and modify type events, so let those take\n            # precedence.\n            if event.is_renamed:\n                # Internal moves appears to always be consecutive in the same\n                # buffer and have IDs differ by exactly one (while others\n                # don't) making it possible to pair up the two events coming\n                # from a single move operation. (None of this is documented!)\n                # Otherwise, guess whether file was moved in or out.\n                # TODO: handle id wrapping\n                if i + 1 < len(events) and events[i + 1].is_renamed and events[i + 1].event_id == event.event_id + 1:\n                    cls = DirMovedEvent if event.is_directory else FileMovedEvent\n                    self.queue_event(cls(event.path, events[i + 1].path))\n                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))\n                    self.queue_event(DirModifiedEvent(os.path.dirname(events[i + 1].path)))\n                    i += 1\n                elif os.path.exists(event.path):\n                    cls = DirCreatedEvent if event.is_directory else FileCreatedEvent\n                    self.queue_event(cls(event.path))\n                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))\n                else:\n                    cls = DirDeletedEvent if event.is_directory else FileDeletedEvent\n                    self.queue_event(cls(event.path))\n                    self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))\n                # TODO: generate events for tree\n\n            elif event.is_modified or event.is_inode_meta_mod or event.is_xattr_mod:\n                cls = DirModifiedEvent if event.is_directory else FileModifiedEvent\n                self.queue_event(cls(event.path))\n\n            elif event.is_created:\n                cls = DirCreatedEvent if event.is_directory else FileCreatedEvent\n                self.queue_event(cls(event.path))\n                self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))\n\n            elif event.is_removed:\n                cls = DirDeletedEvent if event.is_directory else FileDeletedEvent\n                self.queue_event(cls(event.path))\n                self.queue_event(DirModifiedEvent(os.path.dirname(event.path)))\n            i += 1\n\n\nclass FSEventsObserver2(BaseObserver):\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(FSEventsEmitter, timeout=timeout)\n"
  },
  {
    "path": "src/watchdog/observers/inotify.py",
    "content": "\"\"\":module: watchdog.observers.inotify\n:synopsis: ``inotify(7)`` based emitter implementation.\n:author: Sebastien Martini <seb@dbzteam.org>\n:author: Luke McCarthy <luke@iogopro.co.uk>\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Tim Cuthbertson <tim+github@gfxmonk.net>\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:author: Joachim Coenen <joachimcoenen@icloud.com>\n:platforms: Linux 2.6.13+.\n\n.. ADMONITION:: About system requirements\n\n    Recommended minimum kernel version: 2.6.25.\n\n    Quote from the inotify(7) man page:\n\n        \"Inotify was merged into the 2.6.13 Linux kernel. The required library\n        interfaces were added to glibc in version 2.4. (IN_DONT_FOLLOW,\n        IN_MASK_ADD, and IN_ONLYDIR were only added in version 2.5.)\"\n\n    Therefore, you must ensure the system is running at least these versions\n    appropriate libraries and the kernel.\n\n.. ADMONITION:: About recursiveness, event order, and event coalescing\n\n    Quote from the inotify(7) man page:\n\n        If successive output inotify events produced on the inotify file\n        descriptor are identical (same wd, mask, cookie, and name) then they\n        are coalesced into a single event if the older event has not yet been\n        read (but see BUGS).\n\n        The events returned by reading from an inotify file descriptor form\n        an ordered queue. Thus, for example, it is guaranteed that when\n        renaming from one directory to another, events will be produced in\n        the correct order on the inotify file descriptor.\n\n        ...\n\n        Inotify monitoring of directories is not recursive: to monitor\n        subdirectories under a directory, additional watches must be created.\n\n    This emitter implementation therefore automatically adds watches for\n    sub-directories if running in recursive mode.\n\n.. ADMONITION:: Challenges with the inotify API:\n    inotify has some limitations:\n\n    - A watch on a file/folder is not informed when the file/folder itself or any containing (outer) folders  is moved.\n    - When a file is moved from a watched directory to a different directory, there will only be an IN_MOVE_FROM event\n      for the watch on that directory.\n    - When a file is moved from an unwatched directory to a watched directory, there will only be an IN_MOVE_TO event\n      for the watch on that directory.\n\n    If we were to keep track of the path of watches in InotifyFD, an\n    InotifyWatchGroup would get different events depending on whether there are\n    other InotifyWatchGroups for exactly the right set of paths or not. The same\n    goes for coalescing move events.\n\n    Therefore, both things are handled by the InotifyWatchGroups themselves.\n\nSome extremely useful articles and documentation:\n\n.. _inotify FAQ: http://inotify.aiken.cz/?section=inotify&page=faq&lang=en\n.. _intro to inotify: http://www.linuxjournal.com/article/8478\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport logging\nimport os\nimport threading\nfrom dataclasses import dataclass, field\nfrom typing import TYPE_CHECKING, Protocol, cast\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileClosedEvent,\n    FileClosedNoWriteEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    FileOpenedEvent,\n    FileSystemEvent,\n    generate_sub_created_events,\n    generate_sub_moved_events,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\nfrom watchdog.observers.inotify_c import (\n    WATCHDOG_ALL_EVENTS,\n    CallbackId,\n    InotifyConstants,\n    InotifyEvent,\n    InotifyFD,\n    Mask,\n    WatchCallback,\n    WatchDescriptor,\n)\nfrom watchdog.observers.inotify_move_event_grouper import (\n    GroupedInotifyEvent,\n    InotifyMoveEventGrouper,\n    PathedInotifyEvent,\n)\n\nif TYPE_CHECKING:\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\nlogger = logging.getLogger(__name__)\n\n\nclass FileSystemEventCtor(Protocol):\n    def __call__(self, src_path: bytes | str, dest_path: bytes | str = \"\") -> FileSystemEvent: ...\n\n\n@dataclass\nclass InotifyWatchGroup(WatchCallback):\n    \"\"\"Linux inotify(7) API wrapper class.\n\n    Bundles everything needed to watch a file or (possibly recursive) directory.\n    Manages the watches needed and coalesces IN_MOVE_FROM and IN_MOVE_TO events.\n\n    In order to preserve consistency the behavior of one InotifyWatchGroup must\n    be independent of the existence of any other InotifyWatchGroup.\n    Therefore, an InotifyWatchGroup is itself responsible for:\n\n    - keeping track of the actual path a watch watches (including tracking moves, if possible)\n    - coalescing move events.\n\n    :param path:\n        The directory path for which we want an inotify object.\n    :type path:\n        :class:`bytes`\n    :param is_recursive:\n        ``True`` if subdirectories should be monitored; ``False`` otherwise.\n    \"\"\"\n\n    _inotify_fd: InotifyFD\n    \"\"\"The inotify instance to use\"\"\"\n    path: bytes\n    \"\"\"Whether we are watching directories recursively.\"\"\"\n    event_mask: Mask = field(default=Mask(0))\n    \"\"\"The path associated with the inotify instance.\"\"\"\n    is_recursive: bool = False\n    \"\"\"The event mask for this inotify instance.\"\"\"\n    follow_symlink: bool = False\n\n    _move_event_grouper: InotifyMoveEventGrouper = field(default_factory=InotifyMoveEventGrouper, init=False)\n    _lock: threading.Lock = field(default_factory=threading.Lock, init=False)\n    _id: CallbackId = field(init=False)\n\n    _is_active: bool = field(default=False, init=False)\n\n    _active_callbacks_by_watch: dict[WatchDescriptor, bytes] = field(default_factory=dict, init=False)\n    _active_callbacks_by_path: dict[bytes, WatchDescriptor] = field(default_factory=dict, init=False)\n\n    def __post_init__(self) -> None:\n        self.event_mask = InotifyWatchGroup.build_event_mask(self.event_mask, follow_symlink=self.follow_symlink)\n        self._id = CallbackId(id(self))\n        self._activate()\n\n    @staticmethod\n    def build_event_mask(event_mask: Mask, *, follow_symlink: bool) -> Mask:\n        if follow_symlink:\n            event_mask = Mask(event_mask & ~InotifyConstants.IN_DONT_FOLLOW)\n        else:\n            event_mask = Mask(event_mask | InotifyConstants.IN_DONT_FOLLOW)\n        return event_mask\n\n    @property\n    def is_active(self) -> bool:\n        \"\"\"Returns True if there are any callbacks active\"\"\"\n        return self._is_active\n\n    def _source_for_move(self, cookie: int) -> bytes | None:\n        \"\"\"The source path corresponding to the given MOVED_TO event.\n\n        If the source path is outside the monitored directories, None\n        is returned instead.\n        \"\"\"\n        src_event = self._move_event_grouper.get_queued_moved_from_event(cookie)\n        if src_event is not None:\n            return src_event.path\n        return None\n\n    @property\n    def _callback(self) -> WatchCallback:\n        return self\n\n    def read_event(self) -> GroupedInotifyEvent | None:\n        \"\"\"Returns a single event or a tuple of from/to events in case of a\n        paired move event. If this buffer has been closed, raise the Closed\n        exception.\n        \"\"\"\n        return self._move_event_grouper.read_event()\n\n    def on_watch_deleted(self, wd: WatchDescriptor) -> None:\n        \"\"\"Called when a watch that this callback is registered at is removed.\n        This is the case when the watched object is deleted.\"\"\"\n        with self._lock:\n            if not self.is_active:\n                return\n            self._remove_watch_internally(wd)\n\n    def on_event(self, event: InotifyEvent) -> None:\n        \"\"\"called for every event for each watch this callback is registered at.\"\"\"\n        with self._lock:\n            if not self.is_active:\n                return\n            src_path = self._build_event_source_path(event)\n            if src_path is None:\n                return\n\n            # todo look into desired behavior for IN_MOVE_SELF events\n            #  (keep watching?, stop watching?, are they even possible?)\n            if event.is_moved_from:\n                # TODO: When a directory from a watched directory\n                #  is moved into another part of the filesystem, this\n                #  will not generate DELETE events for the directory tree.\n                #  We need to coalesce IN_MOVED_FROM events and those\n                #  IN_MOVED_FROM events which don't pair up with\n                #  IN_MOVED_TO events should be marked IN_DELETE (maybe?)\n                #  instead relative to this directory. And the respective\n                #  callbacks for the directory and sub directory must be removed.\n                #\n                #  also: hold back all other events for this directory and its\n                #  subdirectories, until we know whether it is still watched\n                pass\n            elif event.is_moved_to:\n                move_src_path = self._source_for_move(event.cookie)\n                move_dst_path = src_path\n                if move_src_path is not None:\n                    self._move_watches(move_src_path, move_dst_path)\n                # TODO: When a directory from another part of the\n                #  filesystem is moved into a watched directory, this\n                #  will not generate events for the directory tree.\n                #  We need to coalesce IN_MOVED_TO events and those\n                #  IN_MOVED_TO events which don't pair up with\n                #  IN_MOVED_FROM events should be marked IN_CREATE\n                #  instead relative to this directory.\n            elif event.is_create and event.is_directory and self.is_recursive:\n                self._add_all_callbacks(src_path)\n\n            self._move_event_grouper.put_event(PathedInotifyEvent(event, src_path))\n\n            if event.is_create and event.is_directory and self.is_recursive:\n                for sub_event in self._recursive_simulate(src_path):\n                    sub_src_path = self._build_event_source_path(sub_event)\n                    if sub_src_path is not None:\n                        self._move_event_grouper.put_event(PathedInotifyEvent(sub_event, sub_src_path))\n\n    def _recursive_simulate(self, src_path: bytes) -> list[InotifyEvent]:\n        # HACK: We need to traverse the directory path recursively and simulate\n        # events for newly  created subdirectories/files.\n        # This will handle: mkdir -p foobar/blah/bar; touch foobar/afile\n        events = []\n        for root, dirnames, filenames in os.walk(src_path, followlinks=self.follow_symlink):\n            for dirname in dirnames:\n                full_path = os.path.join(root, dirname)\n                wd_dir = self._active_callbacks_by_path[os.path.dirname(full_path)]\n                mask = Mask(InotifyConstants.IN_CREATE | InotifyConstants.IN_ISDIR)\n                events.append(InotifyEvent(wd_dir, mask, 0, dirname))\n\n            for filename in filenames:\n                full_path = os.path.join(root, filename)\n                wd_parent_dir = self._active_callbacks_by_path[os.path.dirname(full_path)]\n                mask = InotifyConstants.IN_CREATE\n                events.append(InotifyEvent(wd_parent_dir, mask, 0, filename))\n        return events\n\n    def deactivate(self) -> None:\n        \"\"\"Removes all associated watches.\"\"\"\n        with self._lock:\n            self._is_active = False\n            self._remove_callbacks(list(self._active_callbacks_by_watch))\n            self._move_event_grouper.close()\n\n    def _activate(self) -> None:\n        \"\"\"Adds a watch (optionally recursively) for the given directory path\n        to monitor events specified by the mask.\n        \"\"\"\n        with self._lock:\n            if self.is_active:  # maybe wwe can remove this check...\n                return\n\n            self._add_all_callbacks(self.path)\n            self._is_active = True\n\n    # Non-synchronized methods:\n\n    def _build_event_source_path(self, event: InotifyEvent) -> bytes | None:\n        watched_path = self._active_callbacks_by_watch.get(event.wd)\n        if watched_path is None:\n            # investigate: can we *actually* get events for a WatchDescriptor that has already been removed?\n            return None\n        return os.path.join(watched_path, event.name) if event.name else watched_path  # avoid trailing slash\n\n    def _move_watches(self, move_src_path: bytes, move_dst_path: bytes) -> None:\n        \"\"\"moves all watches that are inside the directory move_src_path to move_dst_path\"\"\"\n        moved_watch = self._active_callbacks_by_path.pop(move_src_path, None)\n        if moved_watch is not None:\n            self._active_callbacks_by_watch[moved_watch] = move_dst_path\n            self._active_callbacks_by_path[move_dst_path] = moved_watch\n\n            # move all watches within this directory\n            move_src_prefix = move_src_path + os.path.sep.encode()\n            for path, wd in self._active_callbacks_by_path.copy().items():\n                if path.startswith(move_src_prefix):\n                    del self._active_callbacks_by_path[path]\n                    path = path.replace(move_src_path, move_dst_path, 1)\n                    self._active_callbacks_by_watch[wd] = path\n                    self._active_callbacks_by_path[path] = wd\n\n    def _add_all_callbacks(self, path: bytes) -> None:\n        \"\"\"Adds a watch (optionally recursively) for the given directory path\n        to monitor events specified by the mask.\n\n        :param path:\n            Path to monitor\n        \"\"\"\n        is_dir = os.path.isdir(path)\n        self._add_callback(path)\n        if is_dir and self.is_recursive:\n            for root, dirnames, _ in os.walk(path, followlinks=self.follow_symlink):\n                for dirname in dirnames:\n                    full_path = os.path.join(root, dirname)\n                    if not self.follow_symlink and os.path.islink(full_path):\n                        continue\n                    self._add_callback(full_path)\n\n    def _add_callback(self, path: bytes) -> None:\n        \"\"\"Adds a callback for the given path to monitor events specified by the\n        mask.\n\n        :param path:\n            Path to monitor\n        \"\"\"\n        with contextlib.suppress(OSError):\n            wd = self._inotify_fd.add_callback(path, self.event_mask, self._callback, self._id)\n            self._active_callbacks_by_path[path] = wd\n            self._active_callbacks_by_watch[wd] = path\n\n    def _remove_callbacks(self, wds: list[WatchDescriptor]) -> None:\n        \"\"\"removes callbacks for the given paths.\n\n        :param wds:\n            a list of WatchDescriptors\n        \"\"\"\n        self._inotify_fd.remove_callbacks([(wd, self._id) for wd in wds])\n        for wd in wds:\n            self._remove_watch_internally(wd)\n\n    def _remove_watch_internally(self, wd: WatchDescriptor) -> None:\n        \"\"\"Removes a watch descriptor from internal dicts.\"\"\"\n        path = self._active_callbacks_by_watch.pop(wd)\n        wd2 = self._active_callbacks_by_path.pop(path)\n        if wd2 != wd:\n            # Oops. The path already belongs to a different wd. Put it back.\n            # This can happen, when events are sent slightly out of order.\n            self._active_callbacks_by_path[path] = wd2\n\n\ndef _select_event_type(\n    dir_event: type[FileSystemEvent],\n    file_event: type[FileSystemEvent],\n    *,\n    is_directory: bool,\n) -> FileSystemEventCtor:\n    \"\"\"selects the correct FileSystemEvent Type based on `is_directory` and returns it.\"\"\"\n    return cast(FileSystemEventCtor, dir_event if is_directory else file_event)\n\n\nclass InotifyEmitter(EventEmitter):\n    \"\"\"inotify(7)-based event emitter.\n\n    :param event_queue:\n        The event queue to fill with events.\n    :param watch:\n        A watch object representing the directory to monitor.\n    :type watch:\n        :class:`watchdog.observers.api.ObservedWatch`\n    :param timeout:\n        Read events blocking timeout (in seconds).\n    :type timeout:\n        ``float``\n    :param event_filter:\n        Collection of event types to emit, or None for no filtering (default).\n    :type event_filter:\n        Iterable[:class:`watchdog.events.FileSystemEvent`] | None\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        inotify_fd: InotifyFD | None = None,\n    ) -> None:\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n        self._lock: threading.Lock = threading.Lock()\n        self._inotify_fd: InotifyFD = inotify_fd if inotify_fd is not None else InotifyFD.get_instance()\n        self._inotify: InotifyWatchGroup | None = None\n\n    def on_thread_start(self) -> None:\n        path = os.fsencode(self.watch.path)\n        event_mask = self.get_event_mask_from_filter()\n        self._inotify = InotifyWatchGroup(\n            self._inotify_fd,\n            path,\n            is_recursive=self.watch.is_recursive,\n            event_mask=event_mask,\n            follow_symlink=self.watch.follow_symlink,\n        )\n\n    def on_thread_stop(self) -> None:\n        if self._inotify is not None:\n            self._inotify.deactivate()\n            self._inotify = None\n\n    def queue_events(self, timeout: float, *, full_events: bool = False) -> None:\n        # If \"full_events\" is true, then the method will report unmatched move events as separate events\n        # This behavior is by default only called by a InotifyFullEmitter\n        if self._inotify is None:\n            logger.error(\"InotifyEmitter.queue_events() called when the thread is inactive\")\n            return\n        with self._lock:\n            if self._inotify is None:\n                logger.error(\"InotifyEmitter.queue_events() called when the thread is inactive\")\n                return\n            event = self._inotify.read_event()\n            if event is None:\n                return\n            self.build_and_queue_event(event)\n\n    def build_and_queue_event(self, event: GroupedInotifyEvent, *, full_events: bool = False) -> None:\n        \"\"\"called for every event for each watch this callback is registered at.\"\"\"\n        cls: FileSystemEventCtor\n        if not isinstance(event, PathedInotifyEvent):\n            # we got a move event tuple\n            move_from, move_to = event\n            src_path = self._decode_path(move_from.path)\n            dest_path = self._decode_path(move_to.path)\n            cls = _select_event_type(DirMovedEvent, FileMovedEvent, is_directory=move_from.ev.is_directory)\n            self.queue_event(cls(src_path, dest_path))\n            self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n            self.queue_event(DirModifiedEvent(os.path.dirname(dest_path)))\n            if move_from.ev.is_directory and self.watch.is_recursive:\n                for sub_moved_event in generate_sub_moved_events(src_path, dest_path):\n                    self.queue_event(sub_moved_event)\n        else:\n            src_path = self._decode_path(event.path)\n            if event.ev.is_moved_to:\n                if full_events:\n                    cls = _select_event_type(DirMovedEvent, FileMovedEvent, is_directory=event.ev.is_directory)\n                    self.queue_event(cls(\"\", src_path))\n                else:\n                    cls = _select_event_type(DirCreatedEvent, FileCreatedEvent, is_directory=event.ev.is_directory)\n                    self.queue_event(cls(src_path))\n                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n                if event.ev.is_directory and self.watch.is_recursive:\n                    for sub_created_event in generate_sub_created_events(src_path):\n                        self.queue_event(sub_created_event)\n            elif event.ev.is_attrib or event.ev.is_modify:\n                cls = _select_event_type(DirModifiedEvent, FileModifiedEvent, is_directory=event.ev.is_directory)\n                self.queue_event(cls(src_path))\n            elif event.ev.is_delete or (event.ev.is_moved_from and not full_events):\n                cls = _select_event_type(DirDeletedEvent, FileDeletedEvent, is_directory=event.ev.is_directory)\n                self.queue_event(cls(src_path))\n                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n            elif event.ev.is_moved_from and full_events:\n                cls = _select_event_type(DirMovedEvent, FileMovedEvent, is_directory=event.ev.is_directory)\n                self.queue_event(cls(src_path, \"\"))\n                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n            elif event.ev.is_create:\n                cls = _select_event_type(DirCreatedEvent, FileCreatedEvent, is_directory=event.ev.is_directory)\n                self.queue_event(cls(src_path))\n                self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n            elif event.ev.is_delete_self and src_path == self.watch.path:\n                cls = _select_event_type(DirDeletedEvent, FileDeletedEvent, is_directory=event.ev.is_directory)\n                self.queue_event(cls(src_path))\n                self.stop()\n            elif not event.ev.is_directory:\n                if event.ev.is_open:\n                    self.queue_event(FileOpenedEvent(src_path))\n                elif event.ev.is_close_write:\n                    self.queue_event(FileClosedEvent(src_path))\n                    self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))\n                elif event.ev.is_close_nowrite:\n                    self.queue_event(FileClosedNoWriteEvent(src_path))\n\n    def _decode_path(self, path: bytes) -> bytes | str:\n        \"\"\"Decode path only if unicode string was passed to this emitter.\"\"\"\n        return path if isinstance(self.watch.path, bytes) else os.fsdecode(path)\n\n    def get_event_mask_from_filter(self) -> Mask:\n        \"\"\"Optimization: Only include events we are filtering in inotify call.\"\"\"\n        if self._event_filter is None:\n            return WATCHDOG_ALL_EVENTS\n\n        # Always listen to delete self\n        event_mask = InotifyConstants.IN_DELETE_SELF\n\n        for cls in self._event_filter:\n            if cls in {DirMovedEvent, FileMovedEvent}:\n                event_mask = Mask(event_mask | InotifyConstants.IN_MOVE)\n            elif cls in {DirCreatedEvent, FileCreatedEvent}:\n                event_mask = Mask(event_mask | InotifyConstants.IN_MOVE | InotifyConstants.IN_CREATE)\n            elif cls is DirModifiedEvent:\n                event_mask = Mask(\n                    event_mask\n                    | (\n                        InotifyConstants.IN_MOVE\n                        | InotifyConstants.IN_ATTRIB\n                        | InotifyConstants.IN_MODIFY\n                        | InotifyConstants.IN_CREATE\n                        | InotifyConstants.IN_CLOSE_WRITE\n                    )\n                )\n            elif cls is FileModifiedEvent:\n                event_mask = Mask(event_mask | InotifyConstants.IN_ATTRIB | InotifyConstants.IN_MODIFY)\n            elif cls in {DirDeletedEvent, FileDeletedEvent}:\n                event_mask = Mask(event_mask | InotifyConstants.IN_DELETE)\n            elif cls is FileClosedEvent:\n                event_mask = Mask(event_mask | InotifyConstants.IN_CLOSE_WRITE)\n            elif cls is FileClosedNoWriteEvent:\n                event_mask = Mask(event_mask | InotifyConstants.IN_CLOSE_NOWRITE)\n            elif cls is FileOpenedEvent:\n                event_mask = Mask(event_mask | InotifyConstants.IN_OPEN)\n\n        return event_mask\n\n\nclass InotifyFullEmitter(InotifyEmitter):\n    \"\"\"inotify(7)-based event emitter. By default, this class produces move events even if they are not matched\n    Such move events will have a ``None`` value for the unmatched part.\n    \"\"\"\n\n    def build_and_queue_event(self, event: GroupedInotifyEvent, *, full_events: bool = True) -> None:\n        super().build_and_queue_event(event, full_events=full_events)\n\n\nclass InotifyObserver(BaseObserver):\n    \"\"\"Observer thread that schedules watching directories and dispatches\n    calls to event handlers.\n    \"\"\"\n\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT, generate_full_events: bool = False) -> None:\n        cls = InotifyFullEmitter if generate_full_events else InotifyEmitter\n        super().__init__(cls, timeout=timeout)\n"
  },
  {
    "path": "src/watchdog/observers/inotify_c.py",
    "content": "from __future__ import annotations\n\nimport ctypes\nimport ctypes.util\nimport errno\nimport logging\nimport os\nimport select\nimport struct\nimport threading\nimport warnings\nfrom ctypes import c_char_p, c_int, c_uint32\nfrom dataclasses import dataclass, field\nfrom functools import reduce\nfrom typing import TYPE_CHECKING, Callable, ClassVar, NewType, Protocol, cast\n\nfrom watchdog.utils import BaseThread, UnsupportedLibcError\n\nif TYPE_CHECKING:\n    from collections.abc import Generator, Sequence\n\nlogger = logging.getLogger(__name__)\n\nlibc = ctypes.CDLL(None)\n\nif not hasattr(libc, \"inotify_init\") or not hasattr(libc, \"inotify_add_watch\") or not hasattr(libc, \"inotify_rm_watch\"):\n    error = f\"Unsupported libc version found: {libc._name}\"  # noqa:SLF001\n    raise UnsupportedLibcError(error)\n\n\nWatchDescriptor = NewType(\"WatchDescriptor\", int)\nMask = NewType(\"Mask\", int)\n\ninotify_add_watch = cast(\n    Callable[[int, bytes, int], WatchDescriptor],\n    ctypes.CFUNCTYPE(c_int, c_int, c_char_p, c_uint32, use_errno=True)((\"inotify_add_watch\", libc)),\n)\n\ninotify_rm_watch = cast(\n    Callable[[int, WatchDescriptor], int],\n    ctypes.CFUNCTYPE(c_int, c_int, c_uint32, use_errno=True)((\"inotify_rm_watch\", libc)),\n)\n\ninotify_init = cast(Callable[[], int], ctypes.CFUNCTYPE(c_int, use_errno=True)((\"inotify_init\", libc)))\n\n\nclass InotifyConstants:\n    # User-space events\n    IN_ACCESS: ClassVar[Mask] = Mask(0x00000001)  # File was accessed.\n    IN_MODIFY: ClassVar[Mask] = Mask(0x00000002)  # File was modified.\n    IN_ATTRIB: ClassVar[Mask] = Mask(0x00000004)  # Meta-data changed.\n    IN_CLOSE_WRITE: ClassVar[Mask] = Mask(0x00000008)  # Writable file was closed.\n    IN_CLOSE_NOWRITE: ClassVar[Mask] = Mask(0x00000010)  # Unwritable file closed.\n    IN_OPEN: ClassVar[Mask] = Mask(0x00000020)  # File was opened.\n    IN_MOVED_FROM: ClassVar[Mask] = Mask(0x00000040)  # File was moved from X.\n    IN_MOVED_TO: ClassVar[Mask] = Mask(0x00000080)  # File was moved to Y.\n    IN_CREATE: ClassVar[Mask] = Mask(0x00000100)  # Subfile was created.\n    IN_DELETE: ClassVar[Mask] = Mask(0x00000200)  # Subfile was deleted.\n    IN_DELETE_SELF: ClassVar[Mask] = Mask(0x00000400)  # Self was deleted.\n    IN_MOVE_SELF: ClassVar[Mask] = Mask(0x00000800)  # Self was moved.\n\n    # Helper user-space events.\n    IN_MOVE: ClassVar[Mask] = Mask(IN_MOVED_FROM | IN_MOVED_TO)  # Moves.\n\n    # Events sent by the kernel to a watch.\n    IN_UNMOUNT: ClassVar[Mask] = Mask(0x00002000)  # Backing file system was unmounted.\n    IN_Q_OVERFLOW: ClassVar[Mask] = Mask(0x00004000)  # Event queued overflowed.\n    IN_IGNORED: ClassVar[Mask] = Mask(0x00008000)  # File was ignored.\n\n    # Special flags.\n    IN_ONLYDIR: ClassVar[Mask] = Mask(0x01000000)  # Only watch the path if it's a directory.\n    IN_DONT_FOLLOW: ClassVar[Mask] = Mask(0x02000000)  # Do not follow a symbolic link.\n    IN_EXCL_UNLINK: ClassVar[Mask] = Mask(0x04000000)  # Exclude events on unlinked objects\n    IN_MASK_ADD: ClassVar[Mask] = Mask(0x20000000)  # Add to the mask of an existing watch.\n    IN_ISDIR: ClassVar[Mask] = Mask(0x40000000)  # Event occurred against directory.\n    IN_ONESHOT: ClassVar[Mask] = Mask(0x80000000)  # Only send event once.\n\n    # All user-space events.\n    IN_ALL_EVENTS: ClassVar[Mask] = reduce(\n        lambda x, y: Mask(x | y),\n        [\n            IN_ACCESS,\n            IN_MODIFY,\n            IN_ATTRIB,\n            IN_CLOSE_WRITE,\n            IN_CLOSE_NOWRITE,\n            IN_OPEN,\n            IN_MOVED_FROM,\n            IN_MOVED_TO,\n            IN_DELETE,\n            IN_CREATE,\n            IN_DELETE_SELF,\n            IN_MOVE_SELF,\n        ],\n    )\n\n    # Flags for ``inotify_init1``\n    IN_CLOEXEC: ClassVar[Mask] = Mask(0x02000000)\n    IN_NONBLOCK: ClassVar[Mask] = Mask(0x00004000)\n\n\nINOTIFY_ALL_CONSTANTS: dict[str, Mask] = {\n    name: getattr(InotifyConstants, name)\n    for name in dir(InotifyConstants)\n    if name.startswith(\"IN_\") and name not in {\"IN_ALL_EVENTS\", \"IN_MOVE\"}\n}\n\n\n# Watchdog's API cares only about these events.\nWATCHDOG_ALL_EVENTS: Mask = reduce(\n    lambda x, y: Mask(x | y),\n    [\n        InotifyConstants.IN_MODIFY,\n        InotifyConstants.IN_ATTRIB,\n        InotifyConstants.IN_MOVED_FROM,\n        InotifyConstants.IN_MOVED_TO,\n        InotifyConstants.IN_CREATE,\n        InotifyConstants.IN_DELETE,\n        InotifyConstants.IN_DELETE_SELF,\n        InotifyConstants.IN_DONT_FOLLOW,\n        InotifyConstants.IN_CLOSE_WRITE,\n        InotifyConstants.IN_CLOSE_NOWRITE,\n        InotifyConstants.IN_OPEN,\n    ],\n)\n\n\ndef _get_mask_string(mask: int) -> str:\n    return \"|\".join(name for name, c_val in INOTIFY_ALL_CONSTANTS.items() if mask & c_val)\n\n\nclass InotifyEventStruct(ctypes.Structure):\n    \"\"\"Structure representation of the inotify_event structure\n    (used in buffer size calculations)::\n\n        struct inotify_event {\n            __s32 wd;            /* watch descriptor */\n            __u32 mask;          /* watch mask */\n            __u32 cookie;        /* cookie to synchronize two events */\n            __u32 len;           /* length (including nulls) of name */\n            char  name[0];       /* stub for possible name */\n        };\n    \"\"\"\n\n    _fields_ = (\n        (\"wd\", c_int),\n        (\"mask\", c_uint32),\n        (\"cookie\", c_uint32),\n        (\"len\", c_uint32),\n        (\"name\", c_char_p),\n    )\n\n\nEVENT_SIZE = ctypes.sizeof(InotifyEventStruct)\nDEFAULT_NUM_EVENTS = 2048\nDEFAULT_EVENT_BUFFER_SIZE = DEFAULT_NUM_EVENTS * (EVENT_SIZE + 16)\n\n\nCallbackId = NewType(\"CallbackId\", int)\n\n\nclass WatchCallback(Protocol):\n    def on_event(self, event: InotifyEvent) -> None:\n        \"\"\"called for every event for each watch this callback is registered at.\"\"\"\n        ...\n\n    def on_watch_deleted(self, wd: WatchDescriptor) -> None:\n        \"\"\"Called when a watch that this callback is registered at is removed.\n        This is the case when the watched object is deleted.\"\"\"\n        ...\n\n\n@dataclass\nclass Watch:\n    \"\"\"Represents an inotify watch\"\"\"\n\n    wd: WatchDescriptor\n    \"\"\"the inotify watch descriptor\"\"\"\n    mask: Mask\n    \"\"\"the mask used\"\"\"\n    _initial_creation_path: bytes\n    \"\"\"the original(!) path being watched.\n    .. NOTE:: Do **NOT** use when creating or interpreting events, finding watches\n    or similar. This is purely meant to help debugging.\n\n    If a watched file/folder gets moved and we create a new watch for the\n    file/folder at the new path, inotify will give us the same watch descriptor,\n    which is still remembered undr the old path.\n    \"\"\"\n    callbacks: dict[CallbackId, WatchCallback] = field(default_factory=dict, init=False, compare=False)\n    \"\"\"callbacks to be called when an event for this watch is fired. dict[<some form of id>, Callback]\"\"\"\n\n    @property\n    def is_used(self) -> bool:\n        return bool(self.callbacks)\n\n    def short_str(self) -> str:\n        contents = \", \".join(\n            [\n                f\"wd={self.wd}\",\n                f\"mask={_get_mask_string(self.mask)}\",\n                f\"_initial_creation_path={self._initial_creation_path!r}\",\n            ]\n        )\n        return f\"<{type(self).__name__}: {contents}>\"\n\n\nclass InotifyFD(BaseThread):\n    \"\"\"Linux inotify(7) API wrapper class.\n    Allows adding and removing callbacks to specific inotify watches, keeps\n    track of them, and automatically calls the appropriate callbacks for each\n    event.\n\n    Watches are created and removed as needed.\n    \"\"\"\n\n    # InotifyFD is a singleton for now.\n    _instance: ClassVar[InotifyFD | None] = None\n    _global_lock: ClassVar[threading.Lock] = threading.Lock()\n\n    def __init__(self) -> None:\n        super().__init__()\n        if hasattr(self, \"is_initialized\"):\n            return  # do not initialize the singleton twice.\n        self.is_initialized = True\n\n        # The file descriptor associated with the inotify instance.\n        self._inotify_fd: int = self._create_inotify_fd()\n\n        self._lock = threading.Lock()\n        self._closed = False\n        self._is_reading = True\n        self._kill_r, self._kill_w = os.pipe()\n\n        # used by _check_inotify_fd to tell if we can read _inotify_fd without blocking\n        if hasattr(select, \"poll\"):\n            self._poller: select.poll | None = select.poll()\n            self._poller.register(self._inotify_fd, select.POLLIN)\n            self._poller.register(self._kill_r, select.POLLIN)\n        else:\n            self._poller = None\n\n        # Stores the callbacks for a given watch descriptor.\n        self._watch_for_wd: dict[WatchDescriptor, Watch] = {}\n\n    @classmethod\n    def _create_inotify_fd(cls) -> int:\n        inotify_fd = inotify_init()\n        if inotify_fd == -1:\n            InotifyFD._raise_error()\n        return inotify_fd\n\n    @classmethod\n    def get_instance(cls) -> InotifyFD:\n        \"\"\"Use this class method to get a running InotifyFD instance.\"\"\"\n        with cls._global_lock:\n            # enforce that InotifyFD is a singleton.\n            if cls._instance is None:\n                cls._instance = InotifyFD()\n                cls._instance.start()\n            return cls._instance\n\n    def add_callback(self, path: bytes, mask: Mask, callback: WatchCallback, id_: CallbackId) -> WatchDescriptor:\n        \"\"\"Adds a callback for the given path to monitor events specified by the\n        mask. If a watch already exists for the given path, it is reused.\n        If a callback with the given id_ already exists for this watch, it is overwritten and a warning is generated.\n\n        :param path:\n            Path to begin monitoring.\n        :param mask:\n            Event bit mask.\n        :param callback:\n            Function to be called when an event for this watch is fired\n        :param id_:\n            Some form of id usd to identify the callback (for example to remove it later on...).\n            The id must be unique only within a given watch.\n        \"\"\"\n        with self._lock:\n            return self._add_callback(path, mask, callback, id_)\n\n    def remove_callbacks(self, callbacks: list[tuple[WatchDescriptor, CallbackId]]) -> None:\n        \"\"\"Removes callbacks from WatchDescriptors. If a callback was the last\n        callback on a watch, the watch is removed. Otherwise, just the callback\n        is removed from the watch.\n\n        If no watch for a given WatchDescriptor exists or no callback with the\n        given id_ for the watch exists, a warning is generated.\n\n        Implementation Note:\n        This does not use the path to identify a watch, because the _actual_\n        path of a watch can change if the watched file/folder is moved.\n\n        :param callbacks:\n            a list of (WatchDescriptor, callback id)-tuples for each of which\n            the callback will be removed from the WatchDescriptor.\n        \"\"\"\n        with self._lock:\n            for wd, id_ in callbacks:\n                self._remove_callback(wd, id_)\n\n    def on_thread_stop(self) -> None:\n        self.close()\n\n    def run(self) -> None:\n        \"\"\"Read events from `inotify` and handle them.\"\"\"\n        while self.should_keep_running():\n            inotify_events = self.read_events()\n            for event in inotify_events:\n                self.handle_event(event)\n\n    def close(self) -> None:\n        \"\"\"Closes the inotify instance and removes all associated watches.\"\"\"\n        delete_callbacks = []\n        with self._lock:\n            if not self._closed:\n                self._closed = True\n                for wd in self._watch_for_wd.copy():\n                    inotify_rm_watch(self._inotify_fd, wd)\n                    delete_callbacks.append((wd, self._remove_watch(wd)))\n                self._watch_for_wd.clear()\n\n                if self._is_reading:\n                    # inotify_rm_watch() should write data to _inotify_fd and wake\n                    # the thread, but writing to the kill channel will guarantee this\n                    os.write(self._kill_w, b\"!\")\n                else:\n                    self._close_resources()\n\n        # execute callbacks outside of lock, as they might attempt to register / unregister watches:\n        for wd, callbacks in delete_callbacks:\n            for callback in callbacks:\n                callback.on_watch_deleted(wd)\n\n    def handle_event(self, event: InotifyEvent) -> None:\n        with self._lock:\n            if event.is_ignored:\n                # Clean up book-keeping for deleted watches.\n                delete_callbacks: Sequence[WatchCallback] = self._remove_watch(event.wd)\n                callbacks: Sequence[WatchCallback] = ()\n            else:\n                delete_callbacks = ()\n                watch = self._watch_for_wd.get(event.wd)\n\n                # watch might have been removed already. Also copy, because\n                # watch.callbacks might change during later iteration\n                callbacks = list(watch.callbacks.values()) if watch is not None else ()\n\n        # execute callbacks outside of lock, as they might need to register / unregister watches:\n        for callback in callbacks:\n            callback.on_event(event)\n        for callback in delete_callbacks:\n            callback.on_watch_deleted(event.wd)\n\n    def read_events(self, *, event_buffer_size: int = DEFAULT_EVENT_BUFFER_SIZE) -> list[InotifyEvent]:\n        \"\"\"\n        Reads events from inotify and yields them.\n        All appropriate exiting watches are automatically moved when a move event occurs.\n        \"\"\"\n        event_buffer = self._read_event_buffer(event_buffer_size)\n        return [\n            InotifyEvent(wd, mask, cookie, name)\n            for wd, mask, cookie, name in InotifyFD._parse_event_buffer(event_buffer)\n            if wd != -1\n        ]\n\n    # Non-synchronized methods.\n\n    def _check_inotify_fd(self) -> bool:\n        \"\"\"return true if we can read _inotify_fd without blocking\"\"\"\n        if self._poller is not None:\n            return any(fd == self._inotify_fd for fd, _ in self._poller.poll())\n\n        result = select.select([self._inotify_fd, self._kill_r], [], [])\n        return self._inotify_fd in result[0]\n\n    def _read_event_buffer(self, event_buffer_size: int) -> bytes:\n        \"\"\"\n        Reads from inotify and returns what was read.\n        If inotify got closed or if an errno.EBADF occurred during reading, None is returned.\n        \"\"\"\n        event_buffer = b\"\"\n        while True:\n            try:\n                with self._lock:\n                    if self._closed:\n                        return b\"\"\n\n                    self._is_reading = True\n\n                if self._check_inotify_fd():\n                    event_buffer = os.read(self._inotify_fd, event_buffer_size)\n\n                with self._lock:\n                    self._is_reading = False\n\n                    if self._closed:\n                        self._close_resources()\n                        return b\"\"\n            except OSError as e:\n                if e.errno == errno.EINTR:\n                    continue\n\n                if e.errno == errno.EBADF:\n                    return b\"\"\n\n                raise\n            break\n        return event_buffer\n\n    def _close_resources(self) -> None:\n        os.close(self._inotify_fd)\n        os.close(self._kill_r)\n        os.close(self._kill_w)\n\n    def _add_callback(self, path: bytes, mask: Mask, callback: WatchCallback, id_: CallbackId) -> WatchDescriptor:\n        \"\"\"Adds a callback for the given path to monitor events specified by the\n        mask. If a watch already exists for the given path, it is reused.\n        If a callback with the given id_ already exists for this watch, it is overwritten and a warning is generated.\n\n        :param path:\n            Path to begin monitoring.\n        :param mask:\n            Event bit mask.\n        :param callback:\n            Function to be called when an event for this watch is fired\n        :param id_:\n            Some form of id usd to identify the callback (for example to remove it later on...).\n            The id must be unique only within a given watch.\n        \"\"\"\n        watch = self._get_or_create_watch(path, mask)\n\n        if id_ in watch.callbacks:\n            msg = f\"Callback with id '{id_}' already exists for watch {watch.short_str}. It will be Overwritten.\"\n            warnings.warn(msg, RuntimeWarning, stacklevel=3)\n        watch.callbacks[id_] = callback\n        return watch.wd\n\n    def _get_or_create_watch(self, path: bytes, mask: Mask) -> Watch:\n        \"\"\"Creates a watch for the given path to monitor events specified by the\n        mask.\n\n        :param path:\n            Path to monitor\n        :param mask:\n            Event bit mask.\n        \"\"\"\n        # returns an existing watch descriptor, if one already exists for path:\n        wd = inotify_add_watch(self._inotify_fd, path, mask)\n        if wd == -1:\n            InotifyFD._raise_error()\n        watch = self._watch_for_wd.get(wd)\n        if watch is None:\n            watch = Watch(wd, mask, path)\n            self._watch_for_wd[wd] = watch\n        return watch\n\n    def _remove_callback(self, wd: WatchDescriptor, id_: CallbackId) -> None:\n        \"\"\"Removes a callback for the given WatchDescriptor. If it was the last callback on\n        the watch, the watch is removed. Otherwise, just the callback is removed\n        from the watch.\n        If no watch for the given WatchDescriptor exists or no callback with the given id_ for the watch exists, a\n        warning is generated.\n\n        :param wd:\n            WatchDescriptor for which the callback will be removed.\n        :param id_:\n            Some form of id usd to identify the callback.\n        \"\"\"\n        watch = self._watch_for_wd.get(wd)\n        if watch is None:\n            msg = \"Trying to remove callback from a watch that does not exist. WatchDescriptor: %s, callback id: '%s'.\"\n            logger.debug(msg, wd, id_)\n            return\n\n        if watch.callbacks.pop(id_, None) is None:\n            msg = f\"Callback with id '{id_}' does not exist for watch {watch.short_str} and therefore cannot be removed\"\n            warnings.warn(msg, RuntimeWarning, stacklevel=3)\n\n        if not watch.is_used:\n            delete_callbacks = self._remove_watch(watch.wd)\n            if inotify_rm_watch(self._inotify_fd, watch.wd) == -1:\n                InotifyFD._raise_error(ignore_invalid_argument=True)  # ignore if a watch doesn't exist anymore\n            assert not delete_callbacks, f\"delete_callbacks should be empty, but was: {delete_callbacks}\"\n\n    def _remove_watch(self, wd: WatchDescriptor) -> Sequence[WatchCallback]:\n        \"\"\"Notifies all necessary objects of deleted watches and cleans up book-keeping.\n        This does NOT call inotify_rm_watch.\"\"\"\n        watch = self._watch_for_wd.pop(wd, None)\n        return list(watch.callbacks.values()) if watch is not None else []\n\n    @staticmethod\n    def _raise_error(*, ignore_invalid_argument: bool = False) -> None:\n        \"\"\"Raises errors for inotify failures.\"\"\"\n        err = ctypes.get_errno()\n\n        if err == errno.ENOSPC:\n            raise OSError(errno.ENOSPC, \"inotify watch limit reached\")\n\n        if err == errno.EMFILE:\n            raise OSError(errno.EMFILE, \"inotify instance limit reached\")\n\n        if ignore_invalid_argument and err == errno.EINVAL:\n            return  # ignore\n\n        if err != errno.EACCES:\n            raise OSError(err, os.strerror(err))\n\n    @staticmethod\n    def _parse_event_buffer(event_buffer: bytes) -> Generator[tuple[WatchDescriptor, Mask, int, bytes]]:\n        \"\"\"Parses an event buffer of ``inotify_event`` structs returned by\n        inotify::\n\n            struct inotify_event {\n                __s32 wd;            /* watch descriptor */\n                __u32 mask;          /* watch mask */\n                __u32 cookie;        /* cookie to synchronize two events */\n                __u32 len;           /* length (including nulls) of name */\n                char  name[0];       /* stub for possible name */\n            };\n\n        The ``cookie`` member of this struct is used to pair two related\n        events, for example, it pairs an IN_MOVED_FROM event with an\n        IN_MOVED_TO event.\n        \"\"\"\n        i = 0\n        while i + 16 <= len(event_buffer):\n            wd, mask, cookie, length = struct.unpack_from(\"iIII\", event_buffer, i)\n            name = event_buffer[i + 16 : i + 16 + length].rstrip(b\"\\0\")\n            i += 16 + length\n            yield wd, mask, cookie, name\n\n\n# creates global InotifyFD instance NOW, (only necessary for unit tests) todo find better solution\nInotifyFD.get_instance()\n\n\n@dataclass(unsafe_hash=True, frozen=True)\nclass InotifyEvent:\n    \"\"\"Inotify event struct wrapper.\"\"\"\n\n    wd: WatchDescriptor\n    \"\"\"Watch descriptor\"\"\"\n    mask: Mask\n    \"\"\"Event mask\"\"\"\n    cookie: int\n    \"\"\"Event cookie\"\"\"\n    name: bytes\n    \"\"\"Base name of the event source path. might be empty\"\"\"\n    # src_path: bytes; We cannot set the src_path.\n    # See 'Challenges With inotify' section in the description of inotify.py\n\n    @property\n    def is_modify(self) -> bool:\n        return self.mask & InotifyConstants.IN_MODIFY > 0\n\n    @property\n    def is_close_write(self) -> bool:\n        return self.mask & InotifyConstants.IN_CLOSE_WRITE > 0\n\n    @property\n    def is_close_nowrite(self) -> bool:\n        return self.mask & InotifyConstants.IN_CLOSE_NOWRITE > 0\n\n    @property\n    def is_open(self) -> bool:\n        return self.mask & InotifyConstants.IN_OPEN > 0\n\n    @property\n    def is_access(self) -> bool:\n        return self.mask & InotifyConstants.IN_ACCESS > 0\n\n    @property\n    def is_delete(self) -> bool:\n        return self.mask & InotifyConstants.IN_DELETE > 0\n\n    @property\n    def is_delete_self(self) -> bool:\n        return self.mask & InotifyConstants.IN_DELETE_SELF > 0\n\n    @property\n    def is_create(self) -> bool:\n        return self.mask & InotifyConstants.IN_CREATE > 0\n\n    @property\n    def is_moved_from(self) -> bool:\n        return self.mask & InotifyConstants.IN_MOVED_FROM > 0\n\n    @property\n    def is_moved_to(self) -> bool:\n        return self.mask & InotifyConstants.IN_MOVED_TO > 0\n\n    @property\n    def is_move(self) -> bool:\n        return self.mask & InotifyConstants.IN_MOVE > 0\n\n    @property\n    def is_move_self(self) -> bool:\n        return self.mask & InotifyConstants.IN_MOVE_SELF > 0\n\n    @property\n    def is_attrib(self) -> bool:\n        return self.mask & InotifyConstants.IN_ATTRIB > 0\n\n    @property\n    def is_ignored(self) -> bool:\n        return self.mask & InotifyConstants.IN_IGNORED > 0\n\n    @property\n    def is_directory(self) -> bool:\n        # It looks like the kernel does not provide this information for\n        # IN_DELETE_SELF and IN_MOVE_SELF. In this case, assume it's a dir.\n        # See also: https://github.com/seb-m/pyinotify/blob/2c7e8f8/python2/pyinotify.py#L897\n        return self.is_delete_self or self.is_move_self or self.mask & InotifyConstants.IN_ISDIR > 0\n\n    def __repr__(self) -> str:\n        contents = \", \".join(\n            [\n                f\"wd={self.wd}\",\n                f\"mask={_get_mask_string(self.mask)}\",\n                f\"cookie={self.cookie}\",\n                f\"name={os.fsdecode(self.name)!r}\",\n            ]\n        )\n        return f\"<{type(self).__name__}: {contents}>\"\n"
  },
  {
    "path": "src/watchdog/observers/inotify_move_event_grouper.py",
    "content": "\"\"\":module: watchdog.observers.inotify_buffer\n:synopsis: queue-like class for ``Inotify`` to group move events.\n:author: thomas.amland@gmail.com (Thomas Amland)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:author: Joachim Coenen <joachimcoenen@icloud.com>\n:platforms: linux 2.6.13+\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING, NamedTuple, Union, cast\n\nfrom watchdog.utils.delayed_queue import DelayedQueue\n\nif TYPE_CHECKING:\n    from typing import TypeAlias\n\n    from watchdog.observers.inotify_c import InotifyEvent\n\nlogger = logging.getLogger(__name__)\n\n\nclass PathedInotifyEvent(NamedTuple):\n    \"\"\"An InotifyEvent and its full source path\"\"\"\n\n    ev: InotifyEvent\n    path: bytes\n\n\nGroupedInotifyEvent: TypeAlias = Union[PathedInotifyEvent, tuple[PathedInotifyEvent, PathedInotifyEvent]]\n\n\nclass InotifyMoveEventGrouper:\n    \"\"\"A queue-like class for `Inotify` that holds IN_MOVE_FROM events for\n    `delay` seconds. During this time, IN_MOVED_FROM and IN_MOVED_TO events are\n    paired.\n    \"\"\"\n\n    delay = 0.5\n\n    def __init__(self) -> None:\n        self._queue: DelayedQueue[GroupedInotifyEvent] = DelayedQueue(self.delay)\n\n    def read_event(self) -> GroupedInotifyEvent | None:\n        \"\"\"Returns a single event or a tuple of from/to events in case of a\n        paired move event. If this buffer has been closed, raise the Closed\n        exception.\n        \"\"\"\n        return self._queue.get()\n\n    def put_event(self, event: PathedInotifyEvent) -> None:\n        \"\"\"Add an event to the `queue`. When adding an IN_MOVE_TO event, remove\n        the previous added matching IN_MOVE_FROM event and add them back to the\n        queue as a tuple.\n        \"\"\"\n        logger.debug(\"in-event %s\", event)\n        # Only add delay for unmatched move_from events\n        should_delay = event.ev.is_moved_from\n\n        grouped_event = self._group_moved_to_event(event) if event.ev.is_moved_to else event\n\n        self._queue.put(grouped_event, delay=should_delay)\n\n    def _group_moved_to_event(self, to_event: PathedInotifyEvent) -> GroupedInotifyEvent:\n        \"\"\"Group any matching move events by checking if a matching move_from is\n        in delay queue already and removing it\"\"\"\n        cookie = to_event.ev.cookie\n\n        def matching_from_event(event: GroupedInotifyEvent) -> bool:\n            return isinstance(event, PathedInotifyEvent) and event.ev.is_moved_from and event.ev.cookie == cookie\n\n        # Check if move_from is in delayqueue already\n        from_event = cast(PathedInotifyEvent, self._queue.remove(matching_from_event))\n        if from_event is None:\n            logger.debug(\"could not find matching move_from event\")\n\n        return (from_event, to_event) if from_event is not None else to_event\n\n    def get_queued_moved_from_event(self, cookie: int) -> PathedInotifyEvent | None:\n        \"\"\"Finds a queued IN_MOVED_FROM event with the give cookie, but does not\n        remove it.\"\"\"\n\n        def matching_from_event(event: GroupedInotifyEvent) -> bool:\n            return isinstance(event, PathedInotifyEvent) and event.ev.is_moved_from and event.ev.cookie == cookie\n\n        return cast(PathedInotifyEvent, self._queue.find(matching_from_event))\n\n    def close(self) -> None:\n        \"\"\"closes the queue\"\"\"\n        self._queue.close()\n"
  },
  {
    "path": "src/watchdog/observers/kqueue.py",
    "content": "\"\"\":module: watchdog.observers.kqueue\n:synopsis: ``kqueue(2)`` based emitter implementation.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:platforms: macOS and BSD with kqueue(2).\n\n.. WARNING:: kqueue is a very heavyweight way to monitor file systems.\n             Each kqueue-detected directory modification triggers\n             a full directory scan. Traversing the entire directory tree\n             and opening file descriptors for all files will create\n             performance problems. We need to find a way to re-scan\n             only those directories which report changes and do a diff\n             between two sub-DirectorySnapshots perhaps.\n\n.. ADMONITION:: About OS X performance guidelines\n\n    Quote from the `macOS File System Performance Guidelines`_:\n\n        \"When you only want to track changes on a file or directory, be sure to\n        open it using the ``O_EVTONLY`` flag. This flag prevents the file or\n        directory from being marked as open or in use. This is important\n        if you are tracking files on a removable volume and the user tries to\n        unmount the volume. With this flag in place, the system knows it can\n        dismiss the volume. If you had opened the files or directories without\n        this flag, the volume would be marked as busy and would not be\n        unmounted.\"\n\n    ``O_EVTONLY`` is defined as ``0x8000`` in the OS X header files.\n    More information here: http://www.mlsite.net/blog/?p=2312\n\nClasses\n-------\n.. autoclass:: KqueueEmitter\n   :members:\n   :show-inheritance:\n\nCollections and Utility Classes\n-------------------------------\n.. autoclass:: KeventDescriptor\n   :members:\n   :show-inheritance:\n\n.. autoclass:: KeventDescriptorSet\n   :members:\n   :show-inheritance:\n\n.. _macOS File System Performance Guidelines:\n    http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html#//apple_ref/doc/uid/20001993-CJBJFIDD\n\n\"\"\"\n\n\n# The `select` module varies between platforms.\n# mypy may complain about missing module attributes depending on which platform it's running on.\n# The comment below disables mypy's attribute check.\n# mypy: disable-error-code=\"attr-defined, name-defined\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport errno\nimport os\nimport os.path\nimport select\nimport threading\nfrom stat import S_ISDIR\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.events import (\n    EVENT_TYPE_CREATED,\n    EVENT_TYPE_DELETED,\n    EVENT_TYPE_MOVED,\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    generate_sub_moved_events,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\nfrom watchdog.utils import platform\nfrom watchdog.utils.dirsnapshot import DirectorySnapshot\n\nif TYPE_CHECKING:\n    from collections.abc import Generator\n    from typing import Callable\n\n    from watchdog.events import FileSystemEvent\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\n# Maximum number of events to process.\nMAX_EVENTS = 4096\n\n# O_EVTONLY value from the header files for OS X only.\nO_EVTONLY = 0x8000\n\n# Pre-calculated values for the kevent filter, flags, and fflags attributes.\nWATCHDOG_OS_OPEN_FLAGS = O_EVTONLY if platform.is_darwin() else os.O_RDONLY | os.O_NONBLOCK\nWATCHDOG_KQ_FILTER = select.KQ_FILTER_VNODE\nWATCHDOG_KQ_EV_FLAGS = select.KQ_EV_ADD | select.KQ_EV_ENABLE | select.KQ_EV_CLEAR\nWATCHDOG_KQ_FFLAGS = (\n    select.KQ_NOTE_DELETE\n    | select.KQ_NOTE_WRITE\n    | select.KQ_NOTE_EXTEND\n    | select.KQ_NOTE_ATTRIB\n    | select.KQ_NOTE_LINK\n    | select.KQ_NOTE_RENAME\n    | select.KQ_NOTE_REVOKE\n)\n\n\ndef absolute_path(path: bytes | str) -> bytes | str:\n    return os.path.abspath(os.path.normpath(path))\n\n\n# Flag tests.\n\n\ndef is_deleted(kev: select.kevent) -> bool:\n    \"\"\"Determines whether the given kevent represents deletion.\"\"\"\n    return kev.fflags & select.KQ_NOTE_DELETE > 0\n\n\ndef is_modified(kev: select.kevent) -> bool:\n    \"\"\"Determines whether the given kevent represents modification.\"\"\"\n    fflags = kev.fflags\n    return (fflags & select.KQ_NOTE_EXTEND > 0) or (fflags & select.KQ_NOTE_WRITE > 0)\n\n\ndef is_attrib_modified(kev: select.kevent) -> bool:\n    \"\"\"Determines whether the given kevent represents attribute modification.\"\"\"\n    return kev.fflags & select.KQ_NOTE_ATTRIB > 0\n\n\ndef is_renamed(kev: select.kevent) -> bool:\n    \"\"\"Determines whether the given kevent represents movement.\"\"\"\n    return kev.fflags & select.KQ_NOTE_RENAME > 0\n\n\nclass KeventDescriptorSet:\n    \"\"\"Thread-safe kevent descriptor collection.\"\"\"\n\n    def __init__(self) -> None:\n        self._descriptors: set[KeventDescriptor] = set()\n        self._descriptor_for_path: dict[bytes | str, KeventDescriptor] = {}\n        self._descriptor_for_fd: dict[int, KeventDescriptor] = {}\n        self._kevents: list[select.kevent] = []\n        self._lock = threading.Lock()\n\n    @property\n    def kevents(self) -> list[select.kevent]:\n        \"\"\"List of kevents monitored.\"\"\"\n        with self._lock:\n            return self._kevents\n\n    @property\n    def paths(self) -> list[bytes | str]:\n        \"\"\"List of paths for which kevents have been created.\"\"\"\n        with self._lock:\n            return list(self._descriptor_for_path.keys())\n\n    def get_for_fd(self, fd: int) -> KeventDescriptor:\n        \"\"\"Given a file descriptor, returns the kevent descriptor object\n        for it.\n\n        :param fd:\n            OS file descriptor.\n        :type fd:\n            ``int``\n        :returns:\n            A :class:`KeventDescriptor` object.\n        \"\"\"\n        with self._lock:\n            return self._descriptor_for_fd[fd]\n\n    def get(self, path: bytes | str) -> KeventDescriptor:\n        \"\"\"Obtains a :class:`KeventDescriptor` object for the specified path.\n\n        :param path:\n            Path for which the descriptor will be obtained.\n        \"\"\"\n        with self._lock:\n            path = absolute_path(path)\n            return self._get(path)\n\n    def __contains__(self, path: bytes | str) -> bool:\n        \"\"\"Determines whether a :class:`KeventDescriptor has been registered\n        for the specified path.\n\n        :param path:\n            Path for which the descriptor will be obtained.\n        \"\"\"\n        with self._lock:\n            path = absolute_path(path)\n            return self._has_path(path)\n\n    def add(self, path: bytes | str, *, is_directory: bool) -> None:\n        \"\"\"Adds a :class:`KeventDescriptor` to the collection for the given\n        path.\n\n        :param path:\n            The path for which a :class:`KeventDescriptor` object will be\n            added.\n        :param is_directory:\n            ``True`` if the path refers to a directory; ``False`` otherwise.\n        :type is_directory:\n            ``bool``\n        \"\"\"\n        with self._lock:\n            path = absolute_path(path)\n            if not self._has_path(path):\n                self._add_descriptor(KeventDescriptor(path, is_directory=is_directory))\n\n    def remove(self, path: bytes | str) -> None:\n        \"\"\"Removes the :class:`KeventDescriptor` object for the given path\n        if it already exists.\n\n        :param path:\n            Path for which the :class:`KeventDescriptor` object will be\n            removed.\n        \"\"\"\n        with self._lock:\n            path = absolute_path(path)\n            if self._has_path(path):\n                self._remove_descriptor(self._get(path))\n\n    def clear(self) -> None:\n        \"\"\"Clears the collection and closes all open descriptors.\"\"\"\n        with self._lock:\n            for descriptor in self._descriptors:\n                descriptor.close()\n            self._descriptors.clear()\n            self._descriptor_for_fd.clear()\n            self._descriptor_for_path.clear()\n            self._kevents = []\n\n    # Thread-unsafe methods. Locking is provided at a higher level.\n    def _get(self, path: bytes | str) -> KeventDescriptor:\n        \"\"\"Returns a kevent descriptor for a given path.\"\"\"\n        return self._descriptor_for_path[path]\n\n    def _has_path(self, path: bytes | str) -> bool:\n        \"\"\"Determines whether a :class:`KeventDescriptor` for the specified\n        path exists already in the collection.\n        \"\"\"\n        return path in self._descriptor_for_path\n\n    def _add_descriptor(self, descriptor: KeventDescriptor) -> None:\n        \"\"\"Adds a descriptor to the collection.\n\n        :param descriptor:\n            An instance of :class:`KeventDescriptor` to be added.\n        \"\"\"\n        self._descriptors.add(descriptor)\n        self._kevents.append(descriptor.kevent)\n        self._descriptor_for_path[descriptor.path] = descriptor\n        self._descriptor_for_fd[descriptor.fd] = descriptor\n\n    def _remove_descriptor(self, descriptor: KeventDescriptor) -> None:\n        \"\"\"Removes a descriptor from the collection.\n\n        :param descriptor:\n            An instance of :class:`KeventDescriptor` to be removed.\n        \"\"\"\n        self._descriptors.remove(descriptor)\n        del self._descriptor_for_fd[descriptor.fd]\n        del self._descriptor_for_path[descriptor.path]\n        self._kevents.remove(descriptor.kevent)\n        descriptor.close()\n\n\nclass KeventDescriptor:\n    \"\"\"A kevent descriptor convenience data structure to keep together:\n\n        * kevent\n        * directory status\n        * path\n        * file descriptor\n\n    :param path:\n        Path string for which a kevent descriptor will be created.\n    :param is_directory:\n        ``True`` if the path refers to a directory; ``False`` otherwise.\n    :type is_directory:\n        ``bool``\n    \"\"\"\n\n    def __init__(self, path: bytes | str, *, is_directory: bool) -> None:\n        self._path = absolute_path(path)\n        self._is_directory = is_directory\n        self._fd = os.open(path, WATCHDOG_OS_OPEN_FLAGS)\n        self._kev = select.kevent(\n            self._fd,\n            filter=WATCHDOG_KQ_FILTER,\n            flags=WATCHDOG_KQ_EV_FLAGS,\n            fflags=WATCHDOG_KQ_FFLAGS,\n        )\n\n    @property\n    def fd(self) -> int:\n        \"\"\"OS file descriptor for the kevent descriptor.\"\"\"\n        return self._fd\n\n    @property\n    def path(self) -> bytes | str:\n        \"\"\"The path associated with the kevent descriptor.\"\"\"\n        return self._path\n\n    @property\n    def kevent(self) -> select.kevent:\n        \"\"\"The kevent object associated with the kevent descriptor.\"\"\"\n        return self._kev\n\n    @property\n    def is_directory(self) -> bool:\n        \"\"\"Determines whether the kevent descriptor refers to a directory.\n\n        :returns:\n            ``True`` or ``False``\n        \"\"\"\n        return self._is_directory\n\n    def close(self) -> None:\n        \"\"\"Closes the file descriptor associated with a kevent descriptor.\"\"\"\n        with contextlib.suppress(OSError):\n            os.close(self.fd)\n\n    @property\n    def key(self) -> tuple[bytes | str, bool]:\n        return (self.path, self.is_directory)\n\n    def __eq__(self, descriptor: object) -> bool:\n        if not isinstance(descriptor, KeventDescriptor):\n            return NotImplemented\n        return self.key == descriptor.key\n\n    def __ne__(self, descriptor: object) -> bool:\n        if not isinstance(descriptor, KeventDescriptor):\n            return NotImplemented\n        return self.key != descriptor.key\n\n    def __hash__(self) -> int:\n        return hash(self.key)\n\n    def __repr__(self) -> str:\n        return f\"<{type(self).__name__}: path={self.path!r}, is_directory={self.is_directory}>\"\n\n\nclass KqueueEmitter(EventEmitter):\n    \"\"\"kqueue(2)-based event emitter.\n\n    .. ADMONITION:: About ``kqueue(2)`` behavior and this implementation\n\n              ``kqueue(2)`` monitors file system events only for\n              open descriptors, which means, this emitter does a lot of\n              book-keeping behind the scenes to keep track of open\n              descriptors for every entry in the monitored directory tree.\n\n              This also means the number of maximum open file descriptors\n              on your system must be increased **manually**.\n              Usually, issuing a call to ``ulimit`` should suffice::\n\n                  ulimit -n 1024\n\n              Ensure that you pick a number that is larger than the\n              number of files you expect to be monitored.\n\n              ``kqueue(2)`` does not provide enough information about the\n              following things:\n\n              * The destination path of a file or directory that is renamed.\n              * Creation of a file or directory within a directory; in this\n                case, ``kqueue(2)`` only indicates a modified event on the\n                parent directory.\n\n              Therefore, this emitter takes a snapshot of the directory\n              tree when ``kqueue(2)`` detects a change on the file system\n              to be able to determine the above information.\n\n    :param event_queue:\n        The event queue to fill with events.\n    :param watch:\n        A watch object representing the directory to monitor.\n    :type watch:\n        :class:`watchdog.observers.api.ObservedWatch`\n    :param timeout:\n        Read events blocking timeout (in seconds).\n    :type timeout:\n        ``float``\n    :param event_filter:\n        Collection of event types to emit, or None for no filtering (default).\n    :type event_filter:\n        Iterable[:class:`watchdog.events.FileSystemEvent`] | None\n    :param stat: stat function. See ``os.stat`` for details.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        stat: Callable[[str], os.stat_result] = os.stat,\n    ) -> None:\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n\n        self._kq = select.kqueue()\n        self._lock = threading.RLock()\n\n        # A collection of KeventDescriptor.\n        self._descriptors = KeventDescriptorSet()\n\n        def custom_stat(path: str, cls: KqueueEmitter = self) -> os.stat_result:\n            stat_info = stat(path)\n            cls._register_kevent(path, is_directory=S_ISDIR(stat_info.st_mode))\n            return stat_info\n\n        self._snapshot = DirectorySnapshot(watch.path, recursive=watch.is_recursive, stat=custom_stat)\n\n    def _register_kevent(self, path: bytes | str, *, is_directory: bool) -> None:\n        \"\"\"Registers a kevent descriptor for the given path.\n\n        :param path:\n            Path for which a kevent descriptor will be created.\n        :param is_directory:\n            ``True`` if the path refers to a directory; ``False`` otherwise.\n        :type is_directory:\n            ``bool``\n        \"\"\"\n        try:\n            self._descriptors.add(path, is_directory=is_directory)\n        except OSError as e:\n            if e.errno == errno.ENOENT:\n                # Probably dealing with a temporary file that was created\n                # and then quickly deleted before we could open\n                # a descriptor for it. Therefore, simply queue a sequence\n                # of created and deleted events for the path.\n\n                # TODO: We could simply ignore these files.\n                # Locked files cause the python process to die with\n                # a bus error when we handle temporary files.\n                # eg. .git/index.lock when running tig operations.\n                # I don't fully understand this at the moment.\n                pass\n            elif e.errno == errno.EOPNOTSUPP:\n                # Probably dealing with the socket or special file\n                # mounted through a file system that does not support\n                # access to it (e.g. NFS). On BSD systems look at\n                # EOPNOTSUPP in man 2 open.\n                pass\n            else:\n                # All other errors are propagated.\n                raise\n\n    def _unregister_kevent(self, path: bytes | str) -> None:\n        \"\"\"Convenience function to close the kevent descriptor for a\n        specified kqueue-monitored path.\n\n        :param path:\n            Path for which the kevent descriptor will be closed.\n        \"\"\"\n        self._descriptors.remove(path)\n\n    def queue_event(self, event: FileSystemEvent) -> None:\n        \"\"\"Handles queueing a single event object.\n\n        :param event:\n            An instance of :class:`watchdog.events.FileSystemEvent`\n            or a subclass.\n        \"\"\"\n        # Handles all the book keeping for queued events.\n        # We do not need to fire moved/deleted events for all subitems in\n        # a directory tree here, because this function is called by kqueue\n        # for all those events anyway.\n        EventEmitter.queue_event(self, event)\n        if event.event_type == EVENT_TYPE_CREATED:\n            self._register_kevent(event.src_path, is_directory=event.is_directory)\n        elif event.event_type == EVENT_TYPE_MOVED:\n            self._unregister_kevent(event.src_path)\n            self._register_kevent(event.dest_path, is_directory=event.is_directory)\n        elif event.event_type == EVENT_TYPE_DELETED:\n            self._unregister_kevent(event.src_path)\n\n    def _gen_kqueue_events(\n        self, kev: select.kevent, ref_snapshot: DirectorySnapshot, new_snapshot: DirectorySnapshot\n    ) -> Generator[FileSystemEvent]:\n        \"\"\"Generate events from the kevent list returned from the call to\n        :meth:`select.kqueue.control`.\n\n        .. NOTE:: kqueue only tells us about deletions, file modifications,\n                  attribute modifications. The other events, namely,\n                  file creation, directory modification, file rename,\n                  directory rename, directory creation, etc. are\n                  determined by comparing directory snapshots.\n        \"\"\"\n        descriptor = self._descriptors.get_for_fd(kev.ident)\n        src_path = descriptor.path\n\n        if is_renamed(kev):\n            # Kqueue does not specify the destination names for renames\n            # to, so we have to process these using the a snapshot\n            # of the directory.\n            yield from self._gen_renamed_events(\n                src_path,\n                ref_snapshot,\n                new_snapshot,\n                is_directory=descriptor.is_directory,\n            )\n        elif is_attrib_modified(kev):\n            if descriptor.is_directory:\n                yield DirModifiedEvent(src_path)\n            else:\n                yield FileModifiedEvent(src_path)\n        elif is_modified(kev):\n            if descriptor.is_directory:\n                if self.watch.is_recursive or self.watch.path == src_path:\n                    # When a directory is modified, it may be due to\n                    # sub-file/directory renames or new file/directory\n                    # creation. We determine all this by comparing\n                    # snapshots later.\n                    yield DirModifiedEvent(src_path)\n            else:\n                yield FileModifiedEvent(src_path)\n        elif is_deleted(kev):\n            if descriptor.is_directory:\n                yield DirDeletedEvent(src_path)\n            else:\n                yield FileDeletedEvent(src_path)\n\n    def _parent_dir_modified(self, src_path: bytes | str) -> DirModifiedEvent:\n        \"\"\"Helper to generate a DirModifiedEvent on the parent of src_path.\"\"\"\n        return DirModifiedEvent(os.path.dirname(src_path))\n\n    def _gen_renamed_events(\n        self,\n        src_path: bytes | str,\n        ref_snapshot: DirectorySnapshot,\n        new_snapshot: DirectorySnapshot,\n        *,\n        is_directory: bool,\n    ) -> Generator[FileSystemEvent]:\n        \"\"\"Compares information from two directory snapshots (one taken before\n        the rename operation and another taken right after) to determine the\n        destination path of the file system object renamed, and yields\n        the appropriate events to be queued.\n        \"\"\"\n        try:\n            f_inode = ref_snapshot.inode(src_path)\n        except KeyError:\n            # Probably caught a temporary file/directory that was renamed\n            # and deleted. Fires a sequence of created and deleted events\n            # for the path.\n            if is_directory:\n                yield DirCreatedEvent(src_path)\n                yield DirDeletedEvent(src_path)\n            else:\n                yield FileCreatedEvent(src_path)\n                yield FileDeletedEvent(src_path)\n                # We don't process any further and bail out assuming\n            # the event represents deletion/creation instead of movement.\n            return\n\n        dest_path = new_snapshot.path(f_inode)\n        if dest_path is not None:\n            dest_path = absolute_path(dest_path)\n            if is_directory:\n                yield DirMovedEvent(src_path, dest_path)\n            else:\n                yield FileMovedEvent(src_path, dest_path)\n            yield self._parent_dir_modified(src_path)\n            yield self._parent_dir_modified(dest_path)\n            if is_directory and self.watch.is_recursive:\n                # TODO: Do we need to fire moved events for the items\n                # inside the directory tree? Does kqueue does this\n                # all by itself? Check this and then enable this code\n                # only if it doesn't already.\n                # A: It doesn't. So I've enabled this block.\n                yield from generate_sub_moved_events(src_path, dest_path)\n        else:\n            # If the new snapshot does not have an inode for the\n            # old path, we haven't found the new name. Therefore,\n            # we mark it as deleted and remove unregister the path.\n            if is_directory:\n                yield DirDeletedEvent(src_path)\n            else:\n                yield FileDeletedEvent(src_path)\n            yield self._parent_dir_modified(src_path)\n\n    def _read_events(self, timeout: float) -> list[select.kevent]:\n        \"\"\"Reads events from a call to the blocking\n        :meth:`select.kqueue.control()` method.\n\n        :param timeout:\n            Blocking timeout for reading events.\n        :type timeout:\n            ``float`` (seconds)\n        \"\"\"\n        return self._kq.control(self._descriptors.kevents, MAX_EVENTS, timeout)\n\n    def queue_events(self, timeout: float) -> None:\n        \"\"\"Queues events by reading them from a call to the blocking\n        :meth:`select.kqueue.control()` method.\n\n        :param timeout:\n            Blocking timeout for reading events.\n        :type timeout:\n            ``float`` (seconds)\n        \"\"\"\n        with self._lock:\n            try:\n                event_list = self._read_events(timeout)\n                # TODO: investigate why order appears to be reversed\n                event_list.reverse()\n\n                # Take a fresh snapshot of the directory and update the\n                # saved snapshot.\n                new_snapshot = DirectorySnapshot(self.watch.path, recursive=self.watch.is_recursive)\n                ref_snapshot = self._snapshot\n                self._snapshot = new_snapshot\n                diff_events = new_snapshot - ref_snapshot\n\n                # Process events\n                for directory_created in diff_events.dirs_created:\n                    self.queue_event(DirCreatedEvent(directory_created))\n                for file_created in diff_events.files_created:\n                    self.queue_event(FileCreatedEvent(file_created))\n                for file_modified in diff_events.files_modified:\n                    self.queue_event(FileModifiedEvent(file_modified))\n\n                for kev in event_list:\n                    for event in self._gen_kqueue_events(kev, ref_snapshot, new_snapshot):\n                        self.queue_event(event)\n\n            except OSError as e:\n                if e.errno != errno.EBADF:\n                    raise\n\n    def on_thread_stop(self) -> None:\n        # Clean up.\n        with self._lock:\n            self._descriptors.clear()\n            self._kq.close()\n\n\nclass KqueueObserver(BaseObserver):\n    \"\"\"Observer thread that schedules watching directories and dispatches\n    calls to event handlers.\n    \"\"\"\n\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(KqueueEmitter, timeout=timeout)\n"
  },
  {
    "path": "src/watchdog/observers/polling.py",
    "content": "\"\"\":module: watchdog.observers.polling\n:synopsis: Polling emitter implementation.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nClasses\n-------\n.. autoclass:: PollingObserver\n   :members:\n   :show-inheritance:\n\n.. autoclass:: PollingObserverVFS\n   :members:\n   :show-inheritance:\n   :special-members:\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport threading\nfrom functools import partial\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\nfrom watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff, EmptyDirectorySnapshot\n\nif TYPE_CHECKING:\n    from collections.abc import Iterator\n    from typing import Callable\n\n    from watchdog.events import FileSystemEvent\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\n\nclass PollingEmitter(EventEmitter):\n    \"\"\"Platform-independent emitter that polls a directory to detect file\n    system changes.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n        stat: Callable[[str], os.stat_result] = os.stat,\n        listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,\n    ) -> None:\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n        self._snapshot: DirectorySnapshot = EmptyDirectorySnapshot()\n        self._lock = threading.Lock()\n        self._take_snapshot: Callable[[], DirectorySnapshot] = lambda: DirectorySnapshot(\n            self.watch.path,\n            recursive=self.watch.is_recursive,\n            stat=stat,\n            listdir=listdir,\n        )\n\n    def on_thread_start(self) -> None:\n        self._snapshot = self._take_snapshot()\n\n    def queue_events(self, timeout: float) -> None:\n        # We don't want to hit the disk continuously.\n        # timeout behaves like an interval for polling emitters.\n        if self.stopped_event.wait(timeout):\n            return\n\n        with self._lock:\n            if not self.should_keep_running():\n                return\n\n            # Get event diff between fresh snapshot and previous snapshot.\n            # Update snapshot.\n            try:\n                new_snapshot = self._take_snapshot()\n            except OSError:\n                self.queue_event(DirDeletedEvent(self.watch.path))\n                self.stop()\n                return\n\n            events = DirectorySnapshotDiff(self._snapshot, new_snapshot)\n            self._snapshot = new_snapshot\n\n            # Files.\n            for src_path in events.files_deleted:\n                self.queue_event(FileDeletedEvent(src_path))\n            for src_path in events.files_modified:\n                self.queue_event(FileModifiedEvent(src_path))\n            for src_path in events.files_created:\n                self.queue_event(FileCreatedEvent(src_path))\n            for src_path, dest_path in events.files_moved:\n                self.queue_event(FileMovedEvent(src_path, dest_path))\n\n            # Directories.\n            for src_path in events.dirs_deleted:\n                self.queue_event(DirDeletedEvent(src_path))\n            for src_path in events.dirs_modified:\n                self.queue_event(DirModifiedEvent(src_path))\n            for src_path in events.dirs_created:\n                self.queue_event(DirCreatedEvent(src_path))\n            for src_path, dest_path in events.dirs_moved:\n                self.queue_event(DirMovedEvent(src_path, dest_path))\n\n\nclass PollingObserver(BaseObserver):\n    \"\"\"Platform-independent observer that polls a directory to detect file\n    system changes.\n    \"\"\"\n\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(PollingEmitter, timeout=timeout)\n\n\nclass PollingObserverVFS(BaseObserver):\n    \"\"\"File system independent observer that polls a directory to detect changes.\"\"\"\n\n    def __init__(\n        self,\n        stat: Callable[[str], os.stat_result],\n        listdir: Callable[[str | None], Iterator[os.DirEntry]],\n        *,\n        polling_interval: int = 1,\n    ) -> None:\n        \"\"\":param stat: stat function. See ``os.stat`` for details.\n        :param listdir: listdir function. See ``os.scandir`` for details.\n        :type polling_interval: int\n        :param polling_interval: interval in seconds between polling the file system.\n        \"\"\"\n        emitter_cls = partial(PollingEmitter, stat=stat, listdir=listdir)\n        super().__init__(emitter_cls, timeout=polling_interval)  # type: ignore[arg-type]\n"
  },
  {
    "path": "src/watchdog/observers/read_directory_changes.py",
    "content": "from __future__ import annotations\n\nimport os.path\nimport platform\nimport threading\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    generate_sub_created_events,\n    generate_sub_moved_events,\n)\nfrom watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter\nfrom watchdog.observers.winapi import DirectoryChangeReader\n\nif TYPE_CHECKING:\n    from watchdog.events import FileSystemEvent\n    from watchdog.observers.api import EventQueue, ObservedWatch\n\n\nclass WindowsApiEmitter(EventEmitter):\n    \"\"\"Windows API-based emitter that uses ReadDirectoryChangesW\n    to detect file system changes for a watch.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_queue: EventQueue,\n        watch: ObservedWatch,\n        *,\n        timeout: float = DEFAULT_EMITTER_TIMEOUT,\n        event_filter: list[type[FileSystemEvent]] | None = None,\n    ) -> None:\n        super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter)\n        self._lock = threading.RLock()\n        self._reader: DirectoryChangeReader | None = None\n\n    def on_thread_start(self) -> None:\n        with self._lock:\n            assert self._reader is None\n            self._reader = DirectoryChangeReader(self.watch.path, recursive=self.watch.is_recursive)\n        self._reader.start()\n\n    if platform.python_implementation() == \"PyPy\":\n\n        def start(self) -> None:\n            \"\"\"PyPy needs some time before receiving events, see #792.\"\"\"\n            from time import sleep\n\n            super().start()\n            sleep(0.01)\n\n    def on_thread_stop(self) -> None:\n        with self._lock:\n            reader = self._reader\n            self._reader = None\n        if reader is not None:\n            reader.stop()\n\n    def queue_events(self, timeout: float) -> None:\n        reader = self._reader\n        if reader is None:\n            return  # reader has been stopped\n        last_renamed_src_path = \"\"\n        should_stop = False\n        for winapi_event in reader.get_events(timeout):\n            src_path = os.path.join(self.watch.path, winapi_event.src_path)\n\n            if winapi_event.is_renamed_old:\n                last_renamed_src_path = src_path\n            elif winapi_event.is_renamed_new:\n                dest_path = src_path\n                src_path = last_renamed_src_path\n                if os.path.isdir(dest_path):\n                    self.queue_event(DirMovedEvent(src_path, dest_path))\n                    if self.watch.is_recursive:\n                        for sub_moved_event in generate_sub_moved_events(src_path, dest_path):\n                            self.queue_event(sub_moved_event)\n                else:\n                    self.queue_event(FileMovedEvent(src_path, dest_path))\n            elif winapi_event.is_modified:\n                self.queue_event((DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent)(src_path))\n            elif winapi_event.is_added:\n                isdir = os.path.isdir(src_path)\n                self.queue_event((DirCreatedEvent if isdir else FileCreatedEvent)(src_path))\n                if isdir and self.watch.is_recursive:\n                    for sub_created_event in generate_sub_created_events(src_path):\n                        self.queue_event(sub_created_event)\n            elif winapi_event.is_removed:\n                self.queue_event(FileDeletedEvent(src_path))\n            elif winapi_event.is_removed_self:\n                self.queue_event(DirDeletedEvent(self.watch.path))\n                should_stop = True\n        if should_stop:\n            # watched directory was deleted, stop observer threads\n            self.stop()\n\n\nclass WindowsApiObserver(BaseObserver):\n    \"\"\"Observer thread that schedules watching directories and dispatches\n    calls to event handlers.\n    \"\"\"\n\n    def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None:\n        super().__init__(WindowsApiEmitter, timeout=timeout)\n"
  },
  {
    "path": "src/watchdog/observers/winapi.py",
    "content": "\"\"\":module: watchdog.observers.winapi\n:synopsis: Windows API-Python interface (removes dependency on ``pywin32``).\n:author: theller@ctypes.org (Thomas Heller)\n:author: will@willmcgugan.com (Will McGugan)\n:author: ryan@rfk.id.au (Ryan Kelly)\n:author: yesudeep@gmail.com (Yesudeep Mangalapilly)\n:author: thomas.amland@gmail.com (Thomas Amland)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:platforms: windows\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport ctypes\nimport queue\nimport threading\nfrom ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR\nfrom dataclasses import dataclass\nfrom functools import reduce\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any\n\n# Invalid handle value.\nINVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value\n\n# File notification constants.\nFILE_NOTIFY_CHANGE_FILE_NAME = 0x01\nFILE_NOTIFY_CHANGE_DIR_NAME = 0x02\nFILE_NOTIFY_CHANGE_ATTRIBUTES = 0x04\nFILE_NOTIFY_CHANGE_SIZE = 0x08\nFILE_NOTIFY_CHANGE_LAST_WRITE = 0x010\nFILE_NOTIFY_CHANGE_LAST_ACCESS = 0x020\nFILE_NOTIFY_CHANGE_CREATION = 0x040\nFILE_NOTIFY_CHANGE_SECURITY = 0x0100\n\nFILE_FLAG_BACKUP_SEMANTICS = 0x02000000\nFILE_FLAG_OVERLAPPED = 0x40000000\nFILE_LIST_DIRECTORY = 1\nFILE_SHARE_READ = 0x01\nFILE_SHARE_WRITE = 0x02\nFILE_SHARE_DELETE = 0x04\nOPEN_EXISTING = 3\n\nVOLUME_NAME_NT = 0x02\n\n# File action constants.\nFILE_ACTION_CREATED = 1\nFILE_ACTION_DELETED = 2\nFILE_ACTION_MODIFIED = 3\nFILE_ACTION_RENAMED_OLD_NAME = 4\nFILE_ACTION_RENAMED_NEW_NAME = 5\nFILE_ACTION_DELETED_SELF = 0xFFFE\nFILE_ACTION_OVERFLOW = 0xFFFF\n\n# Aliases\nFILE_ACTION_ADDED = FILE_ACTION_CREATED\nFILE_ACTION_REMOVED = FILE_ACTION_DELETED\nFILE_ACTION_REMOVED_SELF = FILE_ACTION_DELETED_SELF\n\nTHREAD_TERMINATE = 0x0001\n\n# IO waiting constants.\nWAIT_ABANDONED = 0x00000080\nWAIT_IO_COMPLETION = 0x000000C0\nWAIT_OBJECT_0 = 0x00000000\nWAIT_TIMEOUT = 0x00000102\n\n# Error codes\nERROR_OPERATION_ABORTED = 995\nERROR_ELEMENT_NOT_FOUND = 1168\n\n\nclass OVERLAPPED(ctypes.Structure):\n    _fields_ = (\n        (\"Internal\", LPVOID),\n        (\"InternalHigh\", LPVOID),\n        (\"Offset\", DWORD),\n        (\"OffsetHigh\", DWORD),\n        (\"Pointer\", LPVOID),\n        (\"hEvent\", HANDLE),\n    )\n\n\ndef _errcheck_bool(value: Any | None, func: Any, args: Any) -> Any:\n    if not value:\n        raise ctypes.WinError()  # type: ignore[attr-defined]\n    return args\n\n\ndef _errcheck_handle(value: Any | None, func: Any, args: Any) -> Any:\n    if not value:\n        raise ctypes.WinError()  # type: ignore[attr-defined]\n    if value == INVALID_HANDLE_VALUE:\n        raise ctypes.WinError()  # type: ignore[attr-defined]\n    return args\n\n\ndef _errcheck_dword(value: Any | None, func: Any, args: Any) -> Any:\n    if value == 0xFFFFFFFF:\n        raise ctypes.WinError()  # type: ignore[attr-defined]\n    return args\n\n\nkernel32 = ctypes.WinDLL(\"kernel32\")  # type: ignore[attr-defined]\n\nReadDirectoryChangesW = kernel32.ReadDirectoryChangesW\nReadDirectoryChangesW.restype = BOOL\nReadDirectoryChangesW.errcheck = _errcheck_bool\nReadDirectoryChangesW.argtypes = (\n    HANDLE,  # hDirectory\n    LPVOID,  # lpBuffer\n    DWORD,  # nBufferLength\n    BOOL,  # bWatchSubtree\n    DWORD,  # dwNotifyFilter\n    ctypes.POINTER(DWORD),  # lpBytesReturned\n    ctypes.POINTER(OVERLAPPED),  # lpOverlapped\n    LPVOID,  # FileIOCompletionRoutine # lpCompletionRoutine\n)\n\nCreateFileW = kernel32.CreateFileW\nCreateFileW.restype = HANDLE\nCreateFileW.errcheck = _errcheck_handle\nCreateFileW.argtypes = (\n    LPCWSTR,  # lpFileName\n    DWORD,  # dwDesiredAccess\n    DWORD,  # dwShareMode\n    LPVOID,  # lpSecurityAttributes\n    DWORD,  # dwCreationDisposition\n    DWORD,  # dwFlagsAndAttributes\n    HANDLE,  # hTemplateFile\n)\n\nCloseHandle = kernel32.CloseHandle\nCloseHandle.restype = BOOL\nCloseHandle.argtypes = (HANDLE,)  # hObject\n\nCancelIoEx = kernel32.CancelIoEx\nCancelIoEx.restype = BOOL\nCancelIoEx.errcheck = _errcheck_bool\nCancelIoEx.argtypes = (\n    HANDLE,  # hObject\n    ctypes.POINTER(OVERLAPPED),  # lpOverlapped\n)\n\nCreateEvent = kernel32.CreateEventW\nCreateEvent.restype = HANDLE\nCreateEvent.errcheck = _errcheck_handle\nCreateEvent.argtypes = (\n    LPVOID,  # lpEventAttributes\n    BOOL,  # bManualReset\n    BOOL,  # bInitialState\n    LPCWSTR,  # lpName\n)\n\nSetEvent = kernel32.SetEvent\nSetEvent.restype = BOOL\nSetEvent.errcheck = _errcheck_bool\nSetEvent.argtypes = (HANDLE,)  # hEvent\n\nWaitForSingleObjectEx = kernel32.WaitForSingleObjectEx\nWaitForSingleObjectEx.restype = DWORD\nWaitForSingleObjectEx.errcheck = _errcheck_dword\nWaitForSingleObjectEx.argtypes = (\n    HANDLE,  # hObject\n    DWORD,  # dwMilliseconds\n    BOOL,  # bAlertable\n)\n\nCreateIoCompletionPort = kernel32.CreateIoCompletionPort\nCreateIoCompletionPort.restype = HANDLE\nCreateIoCompletionPort.errcheck = _errcheck_handle\nCreateIoCompletionPort.argtypes = (\n    HANDLE,  # FileHandle\n    HANDLE,  # ExistingCompletionPort\n    LPVOID,  # CompletionKey\n    DWORD,  # NumberOfConcurrentThreads\n)\n\nGetQueuedCompletionStatus = kernel32.GetQueuedCompletionStatus\nGetQueuedCompletionStatus.restype = BOOL\nGetQueuedCompletionStatus.errcheck = _errcheck_bool\nGetQueuedCompletionStatus.argtypes = (\n    HANDLE,  # CompletionPort\n    LPVOID,  # lpNumberOfBytesTransferred\n    LPVOID,  # lpCompletionKey\n    ctypes.POINTER(OVERLAPPED),  # lpOverlapped\n    DWORD,  # dwMilliseconds\n)\n\nPostQueuedCompletionStatus = kernel32.PostQueuedCompletionStatus\nPostQueuedCompletionStatus.restype = BOOL\nPostQueuedCompletionStatus.errcheck = _errcheck_bool\nPostQueuedCompletionStatus.argtypes = (\n    HANDLE,  # CompletionPort\n    DWORD,  # lpNumberOfBytesTransferred\n    DWORD,  # lpCompletionKey\n    ctypes.POINTER(OVERLAPPED),  # lpOverlapped\n)\n\n\nGetFinalPathNameByHandleW = kernel32.GetFinalPathNameByHandleW\nGetFinalPathNameByHandleW.restype = DWORD\nGetFinalPathNameByHandleW.errcheck = _errcheck_dword\nGetFinalPathNameByHandleW.argtypes = (\n    HANDLE,  # hFile\n    LPWSTR,  # lpszFilePath\n    DWORD,  # cchFilePath\n    DWORD,  # DWORD\n)\n\n\nclass FileNotifyInformation(ctypes.Structure):\n    _fields_ = (\n        (\"NextEntryOffset\", DWORD),\n        (\"Action\", DWORD),\n        (\"FileNameLength\", DWORD),\n        (\"FileName\", (ctypes.c_char * 1)),\n    )\n\n\nLPFNI = ctypes.POINTER(FileNotifyInformation)\n\n\n# We don't need to recalculate these flags every time a call is made to\n# the win32 API functions.\nWATCHDOG_FILE_FLAGS = FILE_FLAG_BACKUP_SEMANTICS\nWATCHDOG_FILE_SHARE_FLAGS = reduce(\n    lambda x, y: x | y,\n    [\n        FILE_SHARE_READ,\n        FILE_SHARE_WRITE,\n        FILE_SHARE_DELETE,\n    ],\n)\nWATCHDOG_FILE_NOTIFY_FLAGS = reduce(\n    lambda x, y: x | y,\n    [\n        FILE_NOTIFY_CHANGE_FILE_NAME,\n        FILE_NOTIFY_CHANGE_DIR_NAME,\n        FILE_NOTIFY_CHANGE_ATTRIBUTES,\n        FILE_NOTIFY_CHANGE_SIZE,\n        FILE_NOTIFY_CHANGE_LAST_WRITE,\n        FILE_NOTIFY_CHANGE_SECURITY,\n        FILE_NOTIFY_CHANGE_LAST_ACCESS,\n        FILE_NOTIFY_CHANGE_CREATION,\n    ],\n)\n\n# ReadDirectoryChangesW buffer length.\n# To handle cases with lot of changes, this seems the highest safest value we can use.\n# Note: it will fail with ERROR_INVALID_PARAMETER when it is greater than 64 KB and\n#       the application is monitoring a directory over the network.\n#       This is due to a packet size limitation with the underlying file sharing protocols.\n#       https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks\nBUFFER_SIZE = 64000\n\n# Buffer length for path-related stuff.\n# Introduced to keep the old behavior when we bumped BUFFER_SIZE from 2048 to 64000 in v1.0.0.\nPATH_BUFFER_SIZE = 2048\n\n\ndef _parse_event_buffer(read_buffer: bytes) -> list[tuple[int, str]]:\n    n_bytes = len(read_buffer)\n    results = []\n    while n_bytes > 0:\n        fni = ctypes.cast(read_buffer, LPFNI)[0]  # type: ignore[arg-type]\n        ptr = ctypes.addressof(fni) + FileNotifyInformation.FileName.offset\n        filename = ctypes.string_at(ptr, fni.FileNameLength)\n        results.append((fni.Action, filename.decode(\"utf-16\")))\n        num_to_skip = fni.NextEntryOffset\n        if num_to_skip <= 0:\n            break\n        read_buffer = read_buffer[num_to_skip:]\n        n_bytes -= num_to_skip  # num_to_skip is long. n_bytes should be long too.\n    return results\n\n\ndef _is_observed_path_deleted(handle: HANDLE, path: str) -> bool:\n    # Comparison of observed path and actual path, returned by\n    # GetFinalPathNameByHandleW. If directory moved to the trash bin, or\n    # deleted, actual path will not be equal to observed path.\n    buff = ctypes.create_unicode_buffer(PATH_BUFFER_SIZE)\n    GetFinalPathNameByHandleW(handle, buff, PATH_BUFFER_SIZE, VOLUME_NAME_NT)\n    return buff.value != path\n\n\ndef _generate_observed_path_deleted_event() -> bytes:\n    # Create synthetic event for notify that observed directory is deleted\n    path = ctypes.create_unicode_buffer(\".\")\n    event = FileNotifyInformation(0, FILE_ACTION_DELETED_SELF, len(path), path.value.encode(\"utf-8\"))\n    event_size = ctypes.sizeof(event)\n    buff = ctypes.create_string_buffer(PATH_BUFFER_SIZE)\n    ctypes.memmove(buff, ctypes.addressof(event), event_size)\n    return buff.raw[:event_size]\n\n\ndef _get_directory_handle(path: str) -> HANDLE:\n    \"\"\"Returns a Windows handle to the specified directory path.\"\"\"\n    return CreateFileW(\n        path,\n        FILE_LIST_DIRECTORY,\n        WATCHDOG_FILE_SHARE_FLAGS,\n        None,\n        OPEN_EXISTING,\n        WATCHDOG_FILE_FLAGS,\n        None,\n    )\n\n\ndef _cancel_handle_io(handle: HANDLE) -> None:\n    try:\n        CancelIoEx(handle, None)\n    except OSError as e:\n        if e.winerror == ERROR_ELEMENT_NOT_FOUND:  # type: ignore[attr-defined]\n            # Happens when the directory has been deleted, ignore.\n            return\n        raise\n\n\ndef _close_directory_handle(handle: HANDLE) -> None:\n    with contextlib.suppress(OSError):\n        CloseHandle(handle)\n\n\n@dataclass(unsafe_hash=True)\nclass WinAPINativeEvent:\n    action: int\n    src_path: str\n\n    @property\n    def is_added(self) -> bool:\n        return self.action == FILE_ACTION_ADDED\n\n    @property\n    def is_removed(self) -> bool:\n        return self.action == FILE_ACTION_REMOVED\n\n    @property\n    def is_modified(self) -> bool:\n        return self.action == FILE_ACTION_MODIFIED\n\n    @property\n    def is_renamed_old(self) -> bool:\n        return self.action == FILE_ACTION_RENAMED_OLD_NAME\n\n    @property\n    def is_renamed_new(self) -> bool:\n        return self.action == FILE_ACTION_RENAMED_NEW_NAME\n\n    @property\n    def is_removed_self(self) -> bool:\n        return self.action == FILE_ACTION_REMOVED_SELF\n\n\nclass DirectoryChangeReader:\n    \"\"\"Uses ReadDirectoryChangesW() to detect file system changes.  A separate\n    thread is used to make that call in order to reduce the time window\n    in that events may be lost.  The processing of data receieved from\n    ReadDirectoryChangesW() is queued and processed by another thread.\n    \"\"\"\n\n    def __init__(self, path: str, *, recursive: bool) -> None:\n        self._path = path\n        self._recursive = recursive\n        self._reader_thread: threading.Thread | None = None\n        self._handle: HANDLE | None = None\n        self._should_stop = False\n        self._buf_queue: queue.Queue = queue.Queue()\n        self._lock = threading.Lock()\n\n    def _run_inner(self, handle: HANDLE, event_buffer: ctypes.Array[ctypes.c_char], nbytes: DWORD) -> None:\n        # This runs in its own thread and it makes a single call to\n        # ReadDirectoryChangesW().  This blocks until at least some events are\n        # available (or there is an error).  We will re-use the _event_buffer\n        # and so we copy the data into a second buffer (double-buffering\n        # technique) and queue it for another thread to process.  This reduces\n        # the time window in which we could miss events.\n        try:\n            ReadDirectoryChangesW(\n                handle,\n                ctypes.byref(event_buffer),\n                len(event_buffer),\n                self._recursive,\n                WATCHDOG_FILE_NOTIFY_FLAGS,\n                ctypes.byref(nbytes),\n                None,\n                None,\n            )\n            buf = event_buffer.raw[: nbytes.value]\n        except OSError as e:\n            if e.winerror == ERROR_OPERATION_ABORTED:  # type: ignore[attr-defined]\n                return\n            if _is_observed_path_deleted(handle, self._path):\n                # Handle the case when the root path is deleted\n                with self._lock:\n                    # Additional calls to ReadDirectoryChangesW() will fail so stop.\n                    self._should_stop = True\n                # Create synthetic event for deletion\n                buf = _generate_observed_path_deleted_event()\n            else:\n                raise\n        self._buf_queue.put(buf)\n\n    def _run(self) -> None:\n        with self._lock:\n            assert self._handle is None\n            handle = self._handle = _get_directory_handle(self._path)\n        # To improve performance and reduce the window in which events can be\n        # missed, we re-use these for each call to _run_inner().\n        event_buffer = ctypes.create_string_buffer(BUFFER_SIZE)\n        nbytes = DWORD()\n        self._buf_queue.put(b'')  # indicates that this thread has started\n        try:\n            while not self._should_stop:\n                self._run_inner(handle, event_buffer, nbytes)\n        finally:\n            # Note that a HANDLE value can be re-used by the OS once we close\n            # it.  So we need to be careful not to hang on to the value after\n            # we close it.\n            with self._lock:\n                self._handle = None\n            _close_directory_handle(handle)\n\n    def start(self) -> None:\n        with self._lock:\n            assert self._reader_thread is None\n            if self._should_stop:\n                return  # stop already called, do not start\n            self._reader_thread = threading.Thread(target=self._run)\n        self._reader_thread.start()\n        # Wait for empty bytes object, indicating reader thread has started.\n        # This reduces the time window between this method returning and the\n        # actual ReadDirectoryChangesW() call, which reduces the time that\n        # events could be missed.\n        _ = self._buf_queue.get(timeout=2)\n\n    def stop(self) -> None:\n        with self._lock:\n            if self._should_stop:\n                return  # already stopping\n            self._should_stop = True\n            reader_thread = self._reader_thread\n            if reader_thread is None:\n                return  # never started\n            self._reader_thread = None\n            if self._handle is not None:\n                # Note that we don't close the handle here but let the reader\n                # thread do that.  The _should_stop flag will tell it to exit the\n                # _run loop, cleanup and exit.  Since it might be blocked inside\n                # ReadDirectoryChangesW, we call CancelIoEx to wake it. This\n                # cancel call is done while holding self._lock so that the run\n                # thread cannot race with us and close the handle before we\n                # make this call.\n                _cancel_handle_io(self._handle)\n        reader_thread.join()\n\n    def get_events(self, timeout: float) -> list[WinAPINativeEvent]:\n        events = []\n        while True:\n            try:\n                buf = self._buf_queue.get(timeout=timeout)\n            except queue.Empty:\n                break\n            events.extend(_parse_event_buffer(buf))\n        return [WinAPINativeEvent(action, src_path) for action, src_path in events]\n"
  },
  {
    "path": "src/watchdog/py.typed",
    "content": ""
  },
  {
    "path": "src/watchdog/tricks/__init__.py",
    "content": "\"\"\":module: watchdog.tricks\n:synopsis: Utility event handlers.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nClasses\n-------\n.. autoclass:: Trick\n   :members:\n   :show-inheritance:\n\n.. autoclass:: LoggerTrick\n   :members:\n   :show-inheritance:\n\n.. autoclass:: ShellCommandTrick\n   :members:\n   :show-inheritance:\n\n.. autoclass:: AutoRestartTrick\n   :members:\n   :show-inheritance:\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport functools\nimport logging\nimport os\nimport signal\nimport subprocess\nimport threading\nimport time\n\nfrom watchdog.events import EVENT_TYPE_CLOSED_NO_WRITE, EVENT_TYPE_OPENED, FileSystemEvent, PatternMatchingEventHandler\nfrom watchdog.utils import echo, platform\nfrom watchdog.utils.event_debouncer import EventDebouncer\nfrom watchdog.utils.process_watcher import ProcessWatcher\n\nlogger = logging.getLogger(__name__)\necho_events = functools.partial(echo.echo, write=lambda msg: logger.info(msg))\n\n\nclass Trick(PatternMatchingEventHandler):\n    \"\"\"Your tricks should subclass this class.\"\"\"\n\n    def __repr__(self) -> str:\n        return f\"<{type(self).__name__}>\"\n\n    @classmethod\n    def generate_yaml(cls) -> str:\n        return f\"\"\"- {cls.__module__}.{cls.__name__}:\n  args:\n  - argument1\n  - argument2\n  kwargs:\n    patterns:\n    - \"*.py\"\n    - \"*.js\"\n    ignore_patterns:\n    - \"version.py\"\n    ignore_directories: false\n\"\"\"\n\n\nclass LoggerTrick(Trick):\n    \"\"\"A simple trick that does only logs events.\"\"\"\n\n    @echo_events\n    def on_any_event(self, event: FileSystemEvent) -> None:\n        pass\n\n\nclass ShellCommandTrick(Trick):\n    \"\"\"Executes shell commands in response to matched events.\"\"\"\n\n    def __init__(\n        self,\n        shell_command: str,\n        *,\n        patterns: list[str] | None = None,\n        ignore_patterns: list[str] | None = None,\n        ignore_directories: bool = False,\n        wait_for_process: bool = False,\n        drop_during_process: bool = False,\n    ):\n        super().__init__(\n            patterns=patterns,\n            ignore_patterns=ignore_patterns,\n            ignore_directories=ignore_directories,\n        )\n        self.shell_command = shell_command\n        self.wait_for_process = wait_for_process\n        self.drop_during_process = drop_during_process\n\n        self.process: subprocess.Popen[bytes] | None = None\n        self._process_watchers: set[ProcessWatcher] = set()\n\n    def on_any_event(self, event: FileSystemEvent) -> None:\n        if event.event_type in {EVENT_TYPE_OPENED, EVENT_TYPE_CLOSED_NO_WRITE}:\n            # FIXME: see issue #949, and find a way to better handle that scenario\n            return\n\n        from string import Template\n\n        if self.drop_during_process and self.is_process_running():\n            return\n\n        object_type = \"directory\" if event.is_directory else \"file\"\n        context = {\n            \"watch_src_path\": event.src_path,\n            \"watch_dest_path\": \"\",\n            \"watch_event_type\": event.event_type,\n            \"watch_object\": object_type,\n        }\n\n        if self.shell_command is None:\n            if hasattr(event, \"dest_path\"):\n                context[\"dest_path\"] = event.dest_path\n                command = 'echo \"${watch_event_type} ${watch_object} from ${watch_src_path} to ${watch_dest_path}\"'\n            else:\n                command = 'echo \"${watch_event_type} ${watch_object} ${watch_src_path}\"'\n        else:\n            if hasattr(event, \"dest_path\"):\n                context[\"watch_dest_path\"] = event.dest_path\n            command = self.shell_command\n\n        command = Template(command).safe_substitute(**context)\n        self.process = subprocess.Popen(command, shell=True)\n        if self.wait_for_process:\n            self.process.wait()\n        else:\n            process_watcher = ProcessWatcher(self.process, None)\n            self._process_watchers.add(process_watcher)\n            process_watcher.process_termination_callback = functools.partial(\n                self._process_watchers.discard,\n                process_watcher,\n            )\n            process_watcher.start()\n\n    def is_process_running(self) -> bool:\n        return bool(self._process_watchers or (self.process is not None and self.process.poll() is None))\n\n\nclass AutoRestartTrick(Trick):\n    \"\"\"Starts a long-running subprocess and restarts it on matched events.\n\n    The command parameter is a list of command arguments, such as\n    `['bin/myserver', '-c', 'etc/myconfig.ini']`.\n\n    Call `start()` after creating the Trick. Call `stop()` when stopping\n    the process.\n    \"\"\"\n\n    def __init__(\n        self,\n        command: list[str],\n        *,\n        patterns: list[str] | None = None,\n        ignore_patterns: list[str] | None = None,\n        ignore_directories: bool = False,\n        stop_signal: signal.Signals | int = signal.SIGINT,\n        kill_after: int = 10,\n        debounce_interval_seconds: int = 0,\n        restart_on_command_exit: bool = True,\n    ):\n        if kill_after < 0:\n            error = \"kill_after must be non-negative.\"\n            raise ValueError(error)\n        if debounce_interval_seconds < 0:\n            error = \"debounce_interval_seconds must be non-negative.\"\n            raise ValueError(error)\n\n        super().__init__(\n            patterns=patterns,\n            ignore_patterns=ignore_patterns,\n            ignore_directories=ignore_directories,\n        )\n\n        self.command = command\n        self.stop_signal = stop_signal.value if isinstance(stop_signal, signal.Signals) else stop_signal\n        self.kill_after = kill_after\n        self.debounce_interval_seconds = debounce_interval_seconds\n        self.restart_on_command_exit = restart_on_command_exit\n\n        self.process: subprocess.Popen[bytes] | None = None\n        self.process_watcher: ProcessWatcher | None = None\n        self.event_debouncer: EventDebouncer | None = None\n        self.restart_count = 0\n\n        self._is_process_stopping = False\n        self._is_trick_stopping = False\n        self._stopping_lock = threading.RLock()\n\n    def start(self) -> None:\n        if self.debounce_interval_seconds:\n            self.event_debouncer = EventDebouncer(\n                debounce_interval_seconds=self.debounce_interval_seconds,\n                events_callback=lambda events: self._restart_process(),\n            )\n            self.event_debouncer.start()\n        self._start_process()\n\n    def stop(self) -> None:\n        # Ensure the body of the function is only run once.\n        with self._stopping_lock:\n            if self._is_trick_stopping:\n                return\n            self._is_trick_stopping = True\n\n        process_watcher = self.process_watcher\n        if self.event_debouncer is not None:\n            self.event_debouncer.stop()\n        self._stop_process()\n\n        # Don't leak threads: Wait for background threads to stop.\n        if self.event_debouncer is not None:\n            self.event_debouncer.join()\n        if process_watcher is not None:\n            process_watcher.join()\n\n    def _start_process(self) -> None:\n        if self._is_trick_stopping:\n            return\n\n        # windows doesn't have setsid\n        self.process = subprocess.Popen(self.command, preexec_fn=getattr(os, \"setsid\", None))\n        if self.restart_on_command_exit:\n            self.process_watcher = ProcessWatcher(self.process, self._restart_process)\n            self.process_watcher.start()\n\n    def _stop_process(self) -> None:\n        # Ensure the body of the function is not run in parallel in different threads.\n        with self._stopping_lock:\n            if self._is_process_stopping:\n                return\n            self._is_process_stopping = True\n\n        try:\n            if self.process_watcher is not None:\n                self.process_watcher.stop()\n                self.process_watcher = None\n\n            if self.process is not None:\n                try:\n                    kill_process(self.process.pid, self.stop_signal)\n                except OSError:\n                    # Process is already gone\n                    pass\n                else:\n                    kill_time = time.time() + self.kill_after\n                    while time.time() < kill_time:\n                        if self.process.poll() is not None:\n                            break\n                        time.sleep(0.25)\n                    else:\n                        # Process is already gone\n                        with contextlib.suppress(OSError):\n                            kill_process(self.process.pid, 9)\n                self.process = None\n        finally:\n            self._is_process_stopping = False\n\n    @echo_events\n    def on_any_event(self, event: FileSystemEvent) -> None:\n        if event.event_type in {EVENT_TYPE_OPENED, EVENT_TYPE_CLOSED_NO_WRITE}:\n            # FIXME: see issue #949, and find a way to better handle that scenario\n            return\n\n        if self.event_debouncer is not None:\n            self.event_debouncer.handle_event(event)\n        else:\n            self._restart_process()\n\n    def _restart_process(self) -> None:\n        if self._is_trick_stopping:\n            return\n        self._stop_process()\n        self._start_process()\n        self.restart_count += 1\n\n\nif platform.is_windows():\n\n    def kill_process(pid: int, stop_signal: int) -> None:\n        os.kill(pid, stop_signal)\n\nelse:\n\n    def kill_process(pid: int, stop_signal: int) -> None:\n        os.killpg(os.getpgid(pid), stop_signal)\n"
  },
  {
    "path": "src/watchdog/utils/__init__.py",
    "content": "\"\"\":module: watchdog.utils\n:synopsis: Utility classes and functions.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nClasses\n-------\n.. autoclass:: BaseThread\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nimport threading\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from types import ModuleType\n\n    from watchdog.tricks import Trick\n\n\nclass UnsupportedLibcError(Exception):\n    pass\n\n\nclass WatchdogShutdownError(Exception):\n    \"\"\"Semantic exception used to signal an external shutdown event.\"\"\"\n\n\nclass BaseThread(threading.Thread):\n    \"\"\"Convenience class for creating stoppable threads.\"\"\"\n\n    def __init__(self) -> None:\n        threading.Thread.__init__(self)\n        if hasattr(self, \"daemon\"):\n            self.daemon = True\n        else:\n            self.setDaemon(True)\n        self._stopped_event = threading.Event()\n\n    @property\n    def stopped_event(self) -> threading.Event:\n        return self._stopped_event\n\n    def should_keep_running(self) -> bool:\n        \"\"\"Determines whether the thread should continue running.\"\"\"\n        return not self._stopped_event.is_set()\n\n    def on_thread_stop(self) -> None:\n        \"\"\"Override this method instead of :meth:`stop()`.\n        :meth:`stop()` calls this method.\n\n        This method is called immediately after the thread is signaled to stop.\n        \"\"\"\n\n    def stop(self) -> None:\n        \"\"\"Signals the thread to stop.\"\"\"\n        self._stopped_event.set()\n        self.on_thread_stop()\n\n    def on_thread_start(self) -> None:\n        \"\"\"Override this method instead of :meth:`start()`. :meth:`start()`\n        calls this method.\n\n        This method is called right before this thread is started and this\n        object's run() method is invoked.\n        \"\"\"\n\n    def start(self) -> None:\n        self.on_thread_start()\n        threading.Thread.start(self)\n\n\ndef load_module(module_name: str) -> ModuleType:\n    \"\"\"Imports a module given its name and returns a handle to it.\"\"\"\n    try:\n        __import__(module_name)\n    except ImportError as e:\n        error = f\"No module named {module_name}\"\n        raise ImportError(error) from e\n    return sys.modules[module_name]\n\n\ndef load_class(dotted_path: str) -> type[Trick]:\n    \"\"\"Loads and returns a class definition provided a dotted path\n    specification the last part of the dotted path is the class name\n    and there is at least one module name preceding the class name.\n\n    Notes\n    -----\n    You will need to ensure that the module you are trying to load\n    exists in the Python path.\n\n    Examples\n    --------\n    - module.name.ClassName    # Provided module.name is in the Python path.\n    - module.ClassName         # Provided module is in the Python path.\n\n    What won't work:\n    - ClassName\n    - modle.name.ClassName     # Typo in module name.\n    - module.name.ClasNam      # Typo in classname.\n\n    \"\"\"\n    dotted_path_split = dotted_path.split(\".\")\n    if len(dotted_path_split) <= 1:\n        error = f\"Dotted module path {dotted_path} must contain a module name and a classname\"\n        raise ValueError(error)\n    klass_name = dotted_path_split[-1]\n    module_name = \".\".join(dotted_path_split[:-1])\n\n    module = load_module(module_name)\n    if hasattr(module, klass_name):\n        return getattr(module, klass_name)\n\n    error = f\"Module {module_name} does not have class attribute {klass_name}\"\n    raise AttributeError(error)\n"
  },
  {
    "path": "src/watchdog/utils/backwards_compat.py",
    "content": "# ruff: noqa\n# fmt: off\n# type: ignore\n\"\"\"\nThis file includes unmodified functions copied from Python 3.13's standard library\nfor use on older Python versions.\n\nFunctions copied:\n- glob.translate (from Lib/glob.py)\n- fnmatch._translate (from Lib/fnmatch.py)\n\nSource: https://github.com/python/cpython\nLicense: Python Software Foundation License Version 2\nCopyright (c) 2001-2024 Python Software Foundation; All Rights Reserved\n\nPlease delete me if/when this project releases forcing python >= 3.13\n\"\"\"\n\nimport os\nimport re\n\n\n# Copied from python 3.13 fnmatch._translate\ndef _translate(pat, STAR, QUESTION_MARK):\n    res = []\n    add = res.append\n    i, n = 0, len(pat)\n    while i < n:\n        c = pat[i]\n        i = i+1\n        if c == '*':\n            # compress consecutive `*` into one\n            if (not res) or res[-1] is not STAR:\n                add(STAR)\n        elif c == '?':\n            add(QUESTION_MARK)\n        elif c == '[':\n            j = i\n            if j < n and pat[j] == '!':\n                j = j+1\n            if j < n and pat[j] == ']':\n                j = j+1\n            while j < n and pat[j] != ']':\n                j = j+1\n            if j >= n:\n                add('\\\\[')\n            else:\n                stuff = pat[i:j]\n                if '-' not in stuff:\n                    stuff = stuff.replace('\\\\', r'\\\\')\n                else:\n                    chunks = []\n                    k = i+2 if pat[i] == '!' else i+1\n                    while True:\n                        k = pat.find('-', k, j)\n                        if k < 0:\n                            break\n                        chunks.append(pat[i:k])\n                        i = k+1\n                        k = k+3\n                    chunk = pat[i:j]\n                    if chunk:\n                        chunks.append(chunk)\n                    else:\n                        chunks[-1] += '-'\n                    # Remove empty ranges -- invalid in RE.\n                    for k in range(len(chunks)-1, 0, -1):\n                        if chunks[k-1][-1] > chunks[k][0]:\n                            chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:]\n                            del chunks[k]\n                    # Escape backslashes and hyphens for set difference (--).\n                    # Hyphens that create ranges shouldn't be escaped.\n                    stuff = '-'.join(s.replace('\\\\', r'\\\\').replace('-', r'\\-')\n                                     for s in chunks)\n                # Escape set operations (&&, ~~ and ||).\n                stuff = re.sub(r'([&~|])', r'\\\\\\1', stuff)\n                i = j+1\n                if not stuff:\n                    # Empty range: never match.\n                    add('(?!)')\n                elif stuff == '!':\n                    # Negated empty range: match any character.\n                    add('.')\n                else:\n                    if stuff[0] == '!':\n                        stuff = '^' + stuff[1:]\n                    elif stuff[0] in ('^', '['):\n                        stuff = '\\\\' + stuff\n                    add(f'[{stuff}]')\n        else:\n            add(re.escape(c))\n    assert i == n\n    return res\n\n\ndef translate(pat, *, recursive=False, include_hidden=False, seps=None):\n    \"\"\"Translate a pathname with shell wildcards to a regular expression.\n\n    If `recursive` is true, the pattern segment '**' will match any number of\n    path segments.\n\n    If `include_hidden` is true, wildcards can match path segments beginning\n    with a dot ('.').\n\n    If a sequence of separator characters is given to `seps`, they will be\n    used to split the pattern into segments and match path separators. If not\n    given, os.path.sep and os.path.altsep (where available) are used.\n    \"\"\"\n    if not seps:\n        if os.path.altsep:\n            seps = (os.path.sep, os.path.altsep)\n        else:\n            seps = os.path.sep\n    escaped_seps = ''.join(map(re.escape, seps))\n    any_sep = f'[{escaped_seps}]' if len(seps) > 1 else escaped_seps\n    not_sep = f'[^{escaped_seps}]'\n    if include_hidden:\n        one_last_segment = f'{not_sep}+'\n        one_segment = f'{one_last_segment}{any_sep}'\n        any_segments = f'(?:.+{any_sep})?'\n        any_last_segments = '.*'\n    else:\n        one_last_segment = f'[^{escaped_seps}.]{not_sep}*'\n        one_segment = f'{one_last_segment}{any_sep}'\n        any_segments = f'(?:{one_segment})*'\n        any_last_segments = f'{any_segments}(?:{one_last_segment})?'\n\n    results = []\n    parts = re.split(any_sep, pat)\n    last_part_idx = len(parts) - 1\n    for idx, part in enumerate(parts):\n        if part == '*':\n            results.append(one_segment if idx < last_part_idx else one_last_segment)\n        elif recursive and part == '**':\n            if idx < last_part_idx:\n                if parts[idx + 1] != '**':\n                    results.append(any_segments)\n            else:\n                results.append(any_last_segments)\n        else:\n            if part:\n                if not include_hidden and part[0] in '*?':\n                    results.append(r'(?!\\.)')\n                results.extend(_translate(part, f'{not_sep}*', not_sep))\n            if idx < last_part_idx:\n                results.append(any_sep)\n    res = ''.join(results)\n    return fr'(?s:{res})\\Z'\n"
  },
  {
    "path": "src/watchdog/utils/bricks.py",
    "content": "\"\"\"Utility collections or \"bricks\".\n\n:module: watchdog.utils.bricks\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: lalinsky@gmail.com (Lukáš Lalinský)\n:author: python@rcn.com (Raymond Hettinger)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\nClasses\n=======\n.. autoclass:: OrderedSetQueue\n   :members:\n   :show-inheritance:\n   :inherited-members:\n\n.. autoclass:: OrderedSet\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport queue\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any\n\n\nclass SkipRepeatsQueue(queue.Queue):\n    \"\"\"Thread-safe implementation of an special queue where a\n    put of the last-item put'd will be dropped.\n\n    The implementation leverages locking already implemented in the base class\n    redefining only the primitives.\n\n    Queued items must be immutable and hashable so that they can be used\n    as dictionary keys. You must implement **only read-only properties** and\n    the :meth:`Item.__hash__()`, :meth:`Item.__eq__()`, and\n    :meth:`Item.__ne__()` methods for items to be hashable.\n\n    An example implementation follows::\n\n        class Item:\n            def __init__(self, a, b):\n                self._a = a\n                self._b = b\n\n            @property\n            def a(self):\n                return self._a\n\n            @property\n            def b(self):\n                return self._b\n\n            def _key(self):\n                return (self._a, self._b)\n\n            def __eq__(self, item):\n                return self._key() == item._key()\n\n            def __ne__(self, item):\n                return self._key() != item._key()\n\n            def __hash__(self):\n                return hash(self._key())\n\n    based on the OrderedSetQueue below\n    \"\"\"\n\n    def __init__(self, maxsize: int = 0) -> None:\n        super().__init__(maxsize)\n        self._last_item = None\n\n    def put(self, item: Any, block: bool = True, timeout: float | None = None) -> None:  # noqa: FBT001,FBT002\n        \"\"\"This method will be used by `eventlet`, when enabled, so we cannot use force proper keyword-only\n        arguments nor touch the signature. Also, the `timeout` argument will be ignored in that case.\n        \"\"\"\n        if self._last_item is None or item != self._last_item:\n            super().put(item, block, timeout)\n\n    def _put(self, item: Any) -> None:\n        super()._put(item)\n        self._last_item = item\n\n    def _get(self) -> Any:\n        item = super()._get()\n        if item is self._last_item:\n            self._last_item = None\n        return item\n"
  },
  {
    "path": "src/watchdog/utils/delayed_queue.py",
    "content": "\"\"\":module: watchdog.utils.delayed_queue\n:author: thomas.amland@gmail.com (Thomas Amland)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\"\"\"\n\nfrom __future__ import annotations\n\nimport threading\nimport time\nfrom collections import deque\nfrom typing import Callable, Generic, TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass DelayedQueue(Generic[T]):\n    def __init__(self, delay: float) -> None:\n        self.delay_sec = delay\n        self._lock = threading.Lock()\n        self._not_empty = threading.Condition(self._lock)\n        self._queue: deque[tuple[T, float, bool]] = deque()\n        self._closed = False\n\n    def put(self, element: T, *, delay: bool = False) -> None:\n        \"\"\"Add element to queue.\"\"\"\n        self._lock.acquire()\n        self._queue.append((element, time.time(), delay))\n        self._not_empty.notify()\n        self._lock.release()\n\n    def close(self) -> None:\n        \"\"\"Close queue, indicating no more items will be added.\"\"\"\n        self._closed = True\n        # Interrupt the blocking _not_empty.wait() call in get\n        self._not_empty.acquire()\n        self._not_empty.notify()\n        self._not_empty.release()\n\n    def get(self) -> T | None:\n        \"\"\"Remove and return an element from the queue, or this queue has been\n        closed raise the Closed exception.\n        \"\"\"\n        while True:\n            # wait for element to be added to queue\n            self._not_empty.acquire()\n            while len(self._queue) == 0 and not self._closed:\n                self._not_empty.wait()\n\n            if self._closed:\n                self._not_empty.release()\n                return None\n            head, insert_time, delay = self._queue[0]\n            self._not_empty.release()\n\n            # wait for delay if required\n            if delay:\n                time_left = insert_time + self.delay_sec - time.time()\n                while time_left > 0:\n                    time.sleep(time_left)\n                    time_left = insert_time + self.delay_sec - time.time()\n\n            # return element if it's still in the queue\n            with self._lock:\n                if len(self._queue) > 0 and self._queue[0][0] is head:\n                    self._queue.popleft()\n                    return head\n\n    def find(self, predicate: Callable[[T], bool]) -> T | None:\n        \"\"\"return the first item for which predicate is True,\n        ignoring delay.\n        \"\"\"\n        with self._lock:\n            i_item = self._index_and_item(predicate)\n            return i_item[1] if i_item is not None else None\n\n    def remove(self, predicate: Callable[[T], bool]) -> T | None:\n        \"\"\"Remove and return the first item for which predicate is True,\n        ignoring delay.\n        \"\"\"\n        with self._lock:\n            i_item = self._index_and_item(predicate)\n            if i_item is not None:\n                del self._queue[i_item[0]]\n                return i_item[1]\n        return None\n\n    def _index_and_item(self, predicate: Callable[[T], bool]) -> tuple[int, T] | None:\n        \"\"\"Return the index and value of the first item for which predicate is\n        True, ignoring delay. Returns -1 if nothing is found. Requires a lock.\n        \"\"\"\n        for i, (elem, *_) in enumerate(self._queue):\n            if predicate(elem):\n                return i, elem\n        return None\n"
  },
  {
    "path": "src/watchdog/utils/dirsnapshot.py",
    "content": "\"\"\":module: watchdog.utils.dirsnapshot\n:synopsis: Directory snapshots and comparison.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\n.. ADMONITION:: Where are the moved events? They \"disappeared\"\n\n    This implementation does not take partition boundaries\n    into consideration. It will only work when the directory\n    tree is entirely on the same file system. More specifically,\n    any part of the code that depends on inode numbers can\n    break if partition boundaries are crossed. In these cases,\n    the snapshot diff will represent file/directory movement as\n    created and deleted events.\n\nClasses\n-------\n.. autoclass:: DirectorySnapshot\n   :members:\n   :show-inheritance:\n\n.. autoclass:: DirectorySnapshotDiff\n   :members:\n   :show-inheritance:\n\n.. autoclass:: EmptyDirectorySnapshot\n   :members:\n   :show-inheritance:\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport errno\nimport os\nfrom stat import S_ISDIR\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from collections.abc import Iterator\n    from typing import Any, Callable\n\n\nclass DirectorySnapshotDiff:\n    \"\"\"Compares two directory snapshots and creates an object that represents\n    the difference between the two snapshots.\n\n    :param ref:\n        The reference directory snapshot.\n    :type ref:\n        :class:`DirectorySnapshot`\n    :param snapshot:\n        The directory snapshot which will be compared\n        with the reference snapshot.\n    :type snapshot:\n        :class:`DirectorySnapshot`\n    :param ignore_device:\n        A boolean indicating whether to ignore the device id or not.\n        By default, a file may be uniquely identified by a combination of its first\n        inode and its device id. The problem is that the device id may (or may not)\n        change between system boots. This problem would cause the DirectorySnapshotDiff\n        to think a file has been deleted and created again but it would be the\n        exact same file.\n        Set to True only if you are sure you will always use the same device.\n    :type ignore_device:\n        :class:`bool`\n    \"\"\"\n\n    def __init__(\n        self,\n        ref: DirectorySnapshot,\n        snapshot: DirectorySnapshot,\n        *,\n        ignore_device: bool = False,\n    ) -> None:\n        created = snapshot.paths - ref.paths\n        deleted = ref.paths - snapshot.paths\n\n        if ignore_device:\n\n            def get_inode(directory: DirectorySnapshot, full_path: bytes | str) -> int | tuple[int, int]:\n                return directory.inode(full_path)[0]\n\n        else:\n\n            def get_inode(directory: DirectorySnapshot, full_path: bytes | str) -> int | tuple[int, int]:\n                return directory.inode(full_path)\n\n        # check that all unchanged paths have the same inode\n        for path in ref.paths & snapshot.paths:\n            if get_inode(ref, path) != get_inode(snapshot, path):\n                created.add(path)\n                deleted.add(path)\n\n        # find moved paths\n        moved: set[tuple[bytes | str, bytes | str]] = set()\n        for path in set(deleted):\n            inode = ref.inode(path)\n            new_path = snapshot.path(inode)\n            if new_path:\n                # file is not deleted but moved\n                deleted.remove(path)\n                moved.add((path, new_path))\n\n        for path in set(created):\n            inode = snapshot.inode(path)\n            old_path = ref.path(inode)\n            if old_path:\n                created.remove(path)\n                moved.add((old_path, path))\n\n        # find modified paths\n        # first check paths that have not moved\n        modified: set[bytes | str] = set()\n        for path in ref.paths & snapshot.paths:\n            if get_inode(ref, path) == get_inode(snapshot, path) and (\n                ref.mtime(path) != snapshot.mtime(path) or ref.size(path) != snapshot.size(path)\n            ):\n                modified.add(path)\n\n        for old_path, new_path in moved:\n            if ref.mtime(old_path) != snapshot.mtime(new_path) or ref.size(old_path) != snapshot.size(new_path):\n                modified.add(old_path)\n\n        self._dirs_created = [path for path in created if snapshot.isdir(path)]\n        self._dirs_deleted = [path for path in deleted if ref.isdir(path)]\n        self._dirs_modified = [path for path in modified if ref.isdir(path)]\n        self._dirs_moved = [(frm, to) for (frm, to) in moved if ref.isdir(frm)]\n\n        self._files_created = list(created - set(self._dirs_created))\n        self._files_deleted = list(deleted - set(self._dirs_deleted))\n        self._files_modified = list(modified - set(self._dirs_modified))\n        self._files_moved = list(moved - set(self._dirs_moved))\n\n    def __str__(self) -> str:\n        return self.__repr__()\n\n    def __repr__(self) -> str:\n        fmt = (\n            \"<{0} files(created={1}, deleted={2}, modified={3}, moved={4}),\"\n            \" folders(created={5}, deleted={6}, modified={7}, moved={8})>\"\n        )\n        return fmt.format(\n            type(self).__name__,\n            len(self._files_created),\n            len(self._files_deleted),\n            len(self._files_modified),\n            len(self._files_moved),\n            len(self._dirs_created),\n            len(self._dirs_deleted),\n            len(self._dirs_modified),\n            len(self._dirs_moved),\n        )\n\n    def __len__(self) -> int:\n        return sum(len(getattr(self, attr)) for attr in dir(self) if attr.startswith((\"_dirs_\", \"_files_\")))\n\n    @property\n    def files_created(self) -> list[bytes | str]:\n        \"\"\"List of files that were created.\"\"\"\n        return self._files_created\n\n    @property\n    def files_deleted(self) -> list[bytes | str]:\n        \"\"\"List of files that were deleted.\"\"\"\n        return self._files_deleted\n\n    @property\n    def files_modified(self) -> list[bytes | str]:\n        \"\"\"List of files that were modified.\"\"\"\n        return self._files_modified\n\n    @property\n    def files_moved(self) -> list[tuple[bytes | str, bytes | str]]:\n        \"\"\"List of files that were moved.\n\n        Each event is a two-tuple the first item of which is the path\n        that has been renamed to the second item in the tuple.\n        \"\"\"\n        return self._files_moved\n\n    @property\n    def dirs_modified(self) -> list[bytes | str]:\n        \"\"\"List of directories that were modified.\"\"\"\n        return self._dirs_modified\n\n    @property\n    def dirs_moved(self) -> list[tuple[bytes | str, bytes | str]]:\n        \"\"\"List of directories that were moved.\n\n        Each event is a two-tuple the first item of which is the path\n        that has been renamed to the second item in the tuple.\n        \"\"\"\n        return self._dirs_moved\n\n    @property\n    def dirs_deleted(self) -> list[bytes | str]:\n        \"\"\"List of directories that were deleted.\"\"\"\n        return self._dirs_deleted\n\n    @property\n    def dirs_created(self) -> list[bytes | str]:\n        \"\"\"List of directories that were created.\"\"\"\n        return self._dirs_created\n\n    class ContextManager:\n        \"\"\"Context manager that creates two directory snapshots and a\n        diff object that represents the difference between the two snapshots.\n\n        :param path:\n            The directory path for which a snapshot should be taken.\n        :type path:\n            ``str``\n        :param recursive:\n            ``True`` if the entire directory tree should be included in the\n            snapshot; ``False`` otherwise.\n        :type recursive:\n            ``bool``\n        :param stat:\n            Use custom stat function that returns a stat structure for path.\n            Currently only st_dev, st_ino, st_mode and st_mtime are needed.\n\n            A function taking a ``path`` as argument which will be called\n            for every entry in the directory tree.\n        :param listdir:\n            Use custom listdir function. For details see ``os.scandir``.\n        :param ignore_device:\n            A boolean indicating whether to ignore the device id or not.\n            By default, a file may be uniquely identified by a combination of its first\n            inode and its device id. The problem is that the device id may (or may not)\n            change between system boots. This problem would cause the DirectorySnapshotDiff\n            to think a file has been deleted and created again but it would be the\n            exact same file.\n            Set to True only if you are sure you will always use the same device.\n        :type ignore_device:\n            :class:`bool`\n        \"\"\"\n\n        def __init__(\n            self,\n            path: str,\n            *,\n            recursive: bool = True,\n            stat: Callable[[str], os.stat_result] = os.stat,\n            listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,\n            ignore_device: bool = False,\n        ) -> None:\n            self.path = path\n            self.recursive = recursive\n            self.stat = stat\n            self.listdir = listdir\n            self.ignore_device = ignore_device\n\n        def __enter__(self) -> None:\n            self.pre_snapshot = self.get_snapshot()\n\n        def __exit__(self, *args: object) -> None:\n            self.post_snapshot = self.get_snapshot()\n            self.diff = DirectorySnapshotDiff(\n                self.pre_snapshot,\n                self.post_snapshot,\n                ignore_device=self.ignore_device,\n            )\n\n        def get_snapshot(self) -> DirectorySnapshot:\n            return DirectorySnapshot(\n                path=self.path,\n                recursive=self.recursive,\n                stat=self.stat,\n                listdir=self.listdir,\n            )\n\n\nclass DirectorySnapshot:\n    \"\"\"A snapshot of stat information of files in a directory.\n\n    :param path:\n        The directory path for which a snapshot should be taken.\n    :type path:\n        ``str``\n    :param recursive:\n        ``True`` if the entire directory tree should be included in the\n        snapshot; ``False`` otherwise.\n    :type recursive:\n        ``bool``\n    :param stat:\n        Use custom stat function that returns a stat structure for path.\n        Currently only st_dev, st_ino, st_mode and st_mtime are needed.\n\n        A function taking a ``path`` as argument which will be called\n        for every entry in the directory tree.\n    :param listdir:\n        Use custom listdir function. For details see ``os.scandir``.\n    \"\"\"\n\n    def __init__(\n        self,\n        path: str,\n        *,\n        recursive: bool = True,\n        stat: Callable[[str], os.stat_result] = os.stat,\n        listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir,\n    ) -> None:\n        self.recursive = recursive\n        self.stat = stat\n        self.listdir = listdir\n\n        self._stat_info: dict[bytes | str, os.stat_result] = {}\n        self._inode_to_path: dict[tuple[int, int], bytes | str] = {}\n\n        st = self.stat(path)\n        self._stat_info[path] = st\n        self._inode_to_path[(st.st_ino, st.st_dev)] = path\n\n        for p, st in self.walk(path):\n            i = (st.st_ino, st.st_dev)\n            self._inode_to_path[i] = p\n            self._stat_info[p] = st\n\n    def walk(self, root: str) -> Iterator[tuple[str, os.stat_result]]:\n        try:\n            paths = [os.path.join(root, entry.name) for entry in self.listdir(root)]\n        except OSError as e:\n            # Directory may have been deleted between finding it in the directory\n            # list of its parent and trying to delete its contents. If this\n            # happens we treat it as empty. Likewise if the directory was replaced\n            # with a file of the same name (less likely, but possible).\n            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):\n                return\n            else:\n                raise\n\n        entries = []\n        for p in paths:\n            with contextlib.suppress(OSError):\n                entry = (p, self.stat(p))\n                entries.append(entry)\n                yield entry\n\n        if self.recursive:\n            for path, st in entries:\n                with contextlib.suppress(PermissionError):\n                    if S_ISDIR(st.st_mode):\n                        yield from self.walk(path)\n\n    @property\n    def paths(self) -> set[bytes | str]:\n        \"\"\"Set of file/directory paths in the snapshot.\"\"\"\n        return set(self._stat_info.keys())\n\n    def path(self, uid: tuple[int, int]) -> bytes | str | None:\n        \"\"\"Returns path for id. None if id is unknown to this snapshot.\"\"\"\n        return self._inode_to_path.get(uid)\n\n    def inode(self, path: bytes | str) -> tuple[int, int]:\n        \"\"\"Returns an id for path.\"\"\"\n        st = self._stat_info[path]\n        return (st.st_ino, st.st_dev)\n\n    def isdir(self, path: bytes | str) -> bool:\n        return S_ISDIR(self._stat_info[path].st_mode)\n\n    def mtime(self, path: bytes | str) -> float:\n        return self._stat_info[path].st_mtime\n\n    def size(self, path: bytes | str) -> int:\n        return self._stat_info[path].st_size\n\n    def stat_info(self, path: bytes | str) -> os.stat_result:\n        \"\"\"Returns a stat information object for the specified path from\n        the snapshot.\n\n        Attached information is subject to change. Do not use unless\n        you specify `stat` in constructor. Use :func:`inode`, :func:`mtime`,\n        :func:`isdir` instead.\n\n        :param path:\n            The path for which stat information should be obtained\n            from a snapshot.\n        \"\"\"\n        return self._stat_info[path]\n\n    def __sub__(self, previous_dirsnap: DirectorySnapshot) -> DirectorySnapshotDiff:\n        \"\"\"Allow subtracting a DirectorySnapshot object instance from\n        another.\n\n        :returns:\n            A :class:`DirectorySnapshotDiff` object.\n        \"\"\"\n        return DirectorySnapshotDiff(previous_dirsnap, self)\n\n    def __str__(self) -> str:\n        return self.__repr__()\n\n    def __repr__(self) -> str:\n        return str(self._stat_info)\n\n\nclass EmptyDirectorySnapshot(DirectorySnapshot):\n    \"\"\"Class to implement an empty snapshot. This is used together with\n    DirectorySnapshot and DirectorySnapshotDiff in order to get all the files/folders\n    in the directory as created.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._stat_info: dict[bytes | str, os.stat_result] = {}\n\n    @staticmethod\n    def path(_: Any) -> None:\n        \"\"\"Mock up method to return the path of the received inode. As the snapshot\n        is intended to be empty, it always returns None.\n\n        :returns:\n            None.\n        \"\"\"\n        return\n\n    @property\n    def paths(self) -> set:\n        \"\"\"Mock up method to return a set of file/directory paths in the snapshot. As\n        the snapshot is intended to be empty, it always returns an empty set.\n\n        :returns:\n            An empty set.\n        \"\"\"\n        return set()\n"
  },
  {
    "path": "src/watchdog/utils/echo.py",
    "content": "# echo.py: Tracing function calls using Python decorators.\n#\n# Written by Thomas Guest <tag@wordaligned.org>\n# Please see http://wordaligned.org/articles/echo\n#\n# Place into the public domain.\n\n\"\"\"Echo calls made to functions in a module.\n\n\"Echoing\" a function call means printing out the name of the function\nand the values of its arguments before making the call (which is more\ncommonly referred to as \"tracing\", but Python already has a trace module).\n\nAlternatively, echo.echo can be used to decorate functions. Calls to the\ndecorated function will be echoed.\n\nExample:\n-------\n\n    @echo.echo\n    def my_function(args):\n        pass\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport functools\nimport sys\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from typing import Any, Callable\n\n\ndef format_arg_value(arg_val: tuple[str, tuple[Any, ...]]) -> str:\n    \"\"\"Return a string representing a (name, value) pair.\"\"\"\n    arg, val = arg_val\n    return f\"{arg}={val!r}\"\n\n\ndef echo(fn: Callable, write: Callable[[str], int | None] = sys.stdout.write) -> Callable:\n    \"\"\"Echo calls to a function.\n\n    Returns a decorated version of the input function which \"echoes\" calls\n    made to it by writing out the function's name and the arguments it was\n    called with.\n    \"\"\"\n    # Unpack function's arg count, arg names, arg defaults\n    code = fn.__code__\n    argcount = code.co_argcount\n    argnames = code.co_varnames[:argcount]\n    fn_defaults: tuple[Any] = fn.__defaults__ or ()\n    argdefs = dict(list(zip(argnames[-len(fn_defaults) :], fn_defaults)))\n\n    @functools.wraps(fn)\n    def wrapped(*v: Any, **k: Any) -> Callable:\n        # Collect function arguments by chaining together positional,\n        # defaulted, extra positional and keyword arguments.\n        positional = list(map(format_arg_value, list(zip(argnames, v))))\n        defaulted = [format_arg_value((a, argdefs[a])) for a in argnames[len(v) :] if a not in k]\n        nameless = list(map(repr, v[argcount:]))\n        keyword = list(map(format_arg_value, list(k.items())))\n        args = positional + defaulted + nameless + keyword\n        write(f\"{fn.__name__}({', '.join(args)})\\n\")\n        return fn(*v, **k)\n\n    return wrapped\n"
  },
  {
    "path": "src/watchdog/utils/event_debouncer.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport threading\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.utils import BaseThread\n\nif TYPE_CHECKING:\n    from typing import Callable\n\n    from watchdog.events import FileSystemEvent\n\nlogger = logging.getLogger(__name__)\n\n\nclass EventDebouncer(BaseThread):\n    \"\"\"Background thread for debouncing event handling.\n\n    When an event is received, wait until the configured debounce interval\n    passes before calling the callback.  If additional events are received\n    before the interval passes, reset the timer and keep waiting.  When the\n    debouncing interval passes, the callback will be called with a list of\n    events in the order in which they were received.\n    \"\"\"\n\n    def __init__(\n        self,\n        debounce_interval_seconds: int,\n        events_callback: Callable[[list[FileSystemEvent]], None],\n    ) -> None:\n        super().__init__()\n        self.debounce_interval_seconds = debounce_interval_seconds\n        self.events_callback = events_callback\n\n        self._events: list[FileSystemEvent] = []\n        self._cond = threading.Condition()\n\n    def handle_event(self, event: FileSystemEvent) -> None:\n        with self._cond:\n            self._events.append(event)\n            self._cond.notify()\n\n    def stop(self) -> None:\n        with self._cond:\n            super().stop()\n            self._cond.notify()\n\n    def run(self) -> None:\n        with self._cond:\n            while True:\n                # Wait for first event (or shutdown).\n                self._cond.wait()\n\n                if self.debounce_interval_seconds:\n                    # Wait for additional events (or shutdown) until the debounce interval passes.\n                    while self.should_keep_running():\n                        if not self._cond.wait(timeout=self.debounce_interval_seconds):\n                            break\n\n                if not self.should_keep_running():\n                    break\n\n                events = self._events\n                self._events = []\n                self.events_callback(events)\n"
  },
  {
    "path": "src/watchdog/utils/patterns.py",
    "content": "\"\"\":module: watchdog.utils.patterns\n:synopsis: Common wildcard searching/filtering functionality for files.\n:author: boris.staletic@gmail.com (Boris Staletic)\n:author: yesudeep@gmail.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\"\"\"\n\nfrom __future__ import annotations\n\n# Non-pure path objects are only allowed on their respective OS's.\n# Thus, these utilities require \"pure\" path objects that don't access the filesystem.\n# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it\n# by converting input paths to `PureWindowsPath` and `PurePosixPath` where:\n#   - `PureWindowsPath` is always case-insensitive.\n#   - `PurePosixPath` is always case-sensitive.\n# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match\nimport re\nfrom pathlib import PurePath, PurePosixPath, PureWindowsPath\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.utils.backwards_compat import translate  # type: ignore[attr-defined]\n\nif TYPE_CHECKING:\n    from collections.abc import Iterator\n\n\ndef _get_sep(path: PurePath) -> str:\n    \"\"\"\n    Python < 3.13 doesn't have a clean way to expose the path separator\n    It's either this, or make use of `path._flavour.sep`\n    \"\"\"\n    if isinstance(path, PureWindowsPath):\n        return \"\\\\\"\n    if isinstance(path, PurePosixPath):\n        return \"/\"\n    raise TypeError(\"Unsupported\")\n\n\ndef _full_match(path: PurePath, pattern: str) -> bool:\n    try:\n        return path.full_match(pattern)\n    except AttributeError:\n        # Replicate for python <3.13\n        # Please remove this, backwards_compat.py, and python license attributions\n        # if/when we can pin a release to python >= 3.13\n        # Construct a pathlib object using the same class as the path to get the\n        # same pattern path separator when constructing the regex\n        normalized_pattern = str(type(path)(pattern))\n        regex = translate(normalized_pattern, recursive=True, include_hidden=True, seps=_get_sep(path))\n        reobj = re.compile(regex)\n        return bool(reobj.match(str(path)))\n\n\ndef _match_path(\n    raw_path: str,\n    included_patterns: set[str],\n    excluded_patterns: set[str],\n    *,\n    case_sensitive: bool,\n) -> bool:\n    \"\"\"Internal function same as :func:`match_path` but does not check arguments.\"\"\"\n    path: PurePosixPath | PureWindowsPath\n    if case_sensitive:\n        path = PurePosixPath(raw_path)\n    else:\n        included_patterns = {pattern.lower() for pattern in included_patterns}\n        excluded_patterns = {pattern.lower() for pattern in excluded_patterns}\n        path = PureWindowsPath(raw_path)\n\n    common_patterns = included_patterns & excluded_patterns\n    if common_patterns:\n        error = f\"conflicting patterns `{common_patterns}` included and excluded\"\n        raise ValueError(error)\n\n    return any(_full_match(path, p) for p in included_patterns) and not any(\n        _full_match(path, p) for p in excluded_patterns\n    )\n\n\ndef filter_paths(\n    paths: list[str],\n    *,\n    included_patterns: list[str] | None = None,\n    excluded_patterns: list[str] | None = None,\n    case_sensitive: bool = True,\n) -> Iterator[str]:\n    \"\"\"Filters from a set of paths based on acceptable patterns and\n    ignorable patterns.\n    :param paths:\n        A list of path names that will be filtered based on matching and\n        ignored patterns.\n    :param included_patterns:\n        Allow filenames matching wildcard patterns specified in this list.\n        If no pattern list is specified, [\"**\"] is used as the default pattern,\n        which matches all files.\n    :param excluded_patterns:\n        Ignores filenames matching wildcard patterns specified in this list.\n        If no pattern list is specified, no files are ignored.\n    :param case_sensitive:\n        ``True`` if matching should be case-sensitive; ``False`` otherwise.\n    :returns:\n        A list of pathnames that matched the allowable patterns and passed\n        through the ignored patterns.\n    \"\"\"\n    included = set([\"**\"] if included_patterns is None else included_patterns)\n    excluded = set([] if excluded_patterns is None else excluded_patterns)\n\n    for path in paths:\n        if _match_path(path, included, excluded, case_sensitive=case_sensitive):\n            yield path\n\n\ndef match_any_paths(\n    paths: list[str],\n    *,\n    included_patterns: list[str] | None = None,\n    excluded_patterns: list[str] | None = None,\n    case_sensitive: bool = True,\n) -> bool:\n    \"\"\"Matches from a set of paths based on acceptable patterns and\n    ignorable patterns.\n    See ``filter_paths()`` for signature details.\n    \"\"\"\n    return any(\n        filter_paths(\n            paths,\n            included_patterns=included_patterns,\n            excluded_patterns=excluded_patterns,\n            case_sensitive=case_sensitive,\n        ),\n    )\n"
  },
  {
    "path": "src/watchdog/utils/platform.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\n\nPLATFORM_WINDOWS = \"windows\"\nPLATFORM_LINUX = \"linux\"\nPLATFORM_BSD = \"bsd\"\nPLATFORM_DARWIN = \"darwin\"\nPLATFORM_UNKNOWN = \"unknown\"\n\n\ndef get_platform_name() -> str:\n    if sys.platform.startswith(\"win\"):\n        return PLATFORM_WINDOWS\n\n    if sys.platform.startswith(\"darwin\"):\n        return PLATFORM_DARWIN\n\n    if sys.platform.startswith(\"linux\"):\n        return PLATFORM_LINUX\n\n    if sys.platform.startswith(\"freebsd\"):\n        release = os.uname().release\n        major = int(release.split(\".\", 1)[0])\n        if major >= 15:\n            return PLATFORM_LINUX  # FreeBSD 15+ have full inotify support\n        return PLATFORM_BSD\n\n    if sys.platform.startswith((\"dragonfly\", \"netbsd\", \"openbsd\", \"bsd\")):\n        return PLATFORM_BSD\n\n    return PLATFORM_UNKNOWN\n\n\n__platform__ = get_platform_name()\n\n\ndef is_linux() -> bool:\n    return __platform__ == PLATFORM_LINUX\n\n\ndef is_bsd() -> bool:\n    return __platform__ == PLATFORM_BSD\n\n\ndef is_darwin() -> bool:\n    return __platform__ == PLATFORM_DARWIN\n\n\ndef is_windows() -> bool:\n    return __platform__ == PLATFORM_WINDOWS\n"
  },
  {
    "path": "src/watchdog/utils/process_watcher.py",
    "content": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING\n\nfrom watchdog.utils import BaseThread\n\nif TYPE_CHECKING:\n    import subprocess\n    from typing import Callable\n\nlogger = logging.getLogger(__name__)\n\n\nclass ProcessWatcher(BaseThread):\n    def __init__(self, popen_obj: subprocess.Popen, process_termination_callback: Callable[[], None] | None) -> None:\n        super().__init__()\n        self.popen_obj = popen_obj\n        self.process_termination_callback = process_termination_callback\n\n    def run(self) -> None:\n        while self.popen_obj.poll() is None:\n            if self.stopped_event.wait(timeout=0.1):\n                return\n\n        try:\n            if not self.stopped_event.is_set() and self.process_termination_callback:\n                self.process_termination_callback()\n        except Exception:\n            logger.exception(\"Error calling process termination callback\")\n"
  },
  {
    "path": "src/watchdog/version.py",
    "content": "from __future__ import annotations\n\n# When updating this version number, please update the\n# ``docs/source/global.rst.inc`` file as well.\nVERSION_MAJOR = 7\nVERSION_MINOR = 0\nVERSION_BUILD = 0\nVERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)\nVERSION_STRING = f\"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}\"\n\n__version__ = VERSION_INFO\n"
  },
  {
    "path": "src/watchdog/watchmedo.py",
    "content": "\"\"\":module: watchdog.watchmedo\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n:synopsis: ``watchmedo`` shell script utility.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport errno\nimport logging\nimport os\nimport os.path\nimport sys\nimport time\nfrom argparse import ArgumentParser, RawDescriptionHelpFormatter\nfrom io import StringIO\nfrom textwrap import dedent\nfrom typing import TYPE_CHECKING, Any\n\nfrom watchdog.utils import WatchdogShutdownError, load_class, platform\nfrom watchdog.version import VERSION_STRING\n\nif TYPE_CHECKING:\n    from argparse import Namespace, _SubParsersAction\n    from typing import Callable\n\n    from watchdog.events import FileSystemEventHandler\n    from watchdog.observers import ObserverType\n    from watchdog.observers.api import BaseObserver\n\n\nlogging.basicConfig(level=logging.INFO)\n\nCONFIG_KEY_TRICKS = \"tricks\"\nCONFIG_KEY_PYTHON_PATH = \"python-path\"\n\n\nclass HelpFormatter(RawDescriptionHelpFormatter):\n    \"\"\"A nicer help formatter.\n\n    Help for arguments can be indented and contain new lines.\n    It will be de-dented and arguments in the help\n    will be separated by a blank line for better readability.\n\n    Source: https://github.com/httpie/httpie/blob/2423f89/httpie/cli/argparser.py#L31\n    \"\"\"\n\n    def __init__(self, *args: Any, max_help_position: int = 6, **kwargs: Any) -> None:\n        # A smaller indent for args help.\n        kwargs[\"max_help_position\"] = max_help_position\n        super().__init__(*args, **kwargs)\n\n    def __repr__(self) -> str:\n        return f\"<{type(self).__name__}>\"\n\n    def _split_lines(self, text: str, width: int) -> list[str]:\n        text = dedent(text).strip() + \"\\n\\n\"\n        return text.splitlines()\n\n\nepilog = \"\"\"\\\nCopyright 2018-2025 Mickaël Schoentgen & contributors\nCopyright 2014-2018 Thomas Amland & contributors\nCopyright 2012-2014 Google, Inc.\nCopyright 2011-2012 Yesudeep Mangalapilly\n\nLicensed under the terms of the Apache license, version 2.0. Please see\nLICENSE in the source code for more information.\"\"\"\n\ncli = ArgumentParser(epilog=epilog, formatter_class=HelpFormatter)\ncli.add_argument(\"--version\", action=\"version\", version=VERSION_STRING)\nsubparsers = cli.add_subparsers(dest=\"top_command\")\ncommand_parsers = {}\n\nArgument = tuple[list[str], Any]\n\n\ndef argument(*name_or_flags: str, **kwargs: Any) -> Argument:\n    \"\"\"Convenience function to properly format arguments to pass to the\n    command decorator.\n    \"\"\"\n    return list(name_or_flags), kwargs\n\n\ndef command(\n    args: list[Argument],\n    *,\n    parent: _SubParsersAction[ArgumentParser] = subparsers,\n    cmd_aliases: list[str] | None = None,\n) -> Callable:\n    \"\"\"Decorator to define a new command in a sanity-preserving way.\n    The function will be stored in the ``func`` variable when the parser\n    parses arguments so that it can be called directly like so::\n\n      >>> args = cli.parse_args()\n      >>> args.func(args)\n\n    \"\"\"\n\n    def decorator(func: Callable) -> Callable:\n        name = func.__name__.replace(\"_\", \"-\")\n        desc = dedent(func.__doc__ or \"\")\n        parser = parent.add_parser(name, aliases=cmd_aliases or [], description=desc, formatter_class=HelpFormatter)\n        command_parsers[name] = parser\n        verbosity_group = parser.add_mutually_exclusive_group()\n        verbosity_group.add_argument(\"-q\", \"--quiet\", dest=\"verbosity\", action=\"append_const\", const=-1)\n        verbosity_group.add_argument(\"-v\", \"--verbose\", dest=\"verbosity\", action=\"append_const\", const=1)\n        for name_or_flags, kwargs in args:\n            parser.add_argument(*name_or_flags, **kwargs)\n            parser.set_defaults(func=func)\n        return func\n\n    return decorator\n\n\ndef path_split(pathname_spec: str, *, separator: str = os.pathsep) -> list[str]:\n    \"\"\"Splits a pathname specification separated by an OS-dependent separator.\n\n    :param pathname_spec:\n        The pathname specification.\n    :param separator:\n        (OS Dependent) `:` on Unix and `;` on Windows or user-specified.\n    \"\"\"\n    return pathname_spec.split(separator)\n\n\ndef add_to_sys_path(pathnames: list[str], *, index: int = 0) -> None:\n    \"\"\"Adds specified paths at specified index into the sys.path list.\n\n    :param paths:\n        A list of paths to add to the sys.path\n    :param index:\n        (Default 0) The index in the sys.path list where the paths will be\n        added.\n    \"\"\"\n    for pathname in pathnames[::-1]:\n        sys.path.insert(index, pathname)\n\n\ndef load_config(tricks_file_pathname: str) -> dict:\n    \"\"\"Loads the YAML configuration from the specified file.\n\n    :param tricks_file_path:\n        The path to the tricks configuration file.\n    :returns:\n        A dictionary of configuration information.\n    \"\"\"\n    import yaml\n\n    with open(tricks_file_pathname, \"rb\") as f:\n        return yaml.safe_load(f.read())\n\n\ndef parse_patterns(\n    patterns_spec: str, ignore_patterns_spec: str, *, separator: str = \";\"\n) -> tuple[list[str], list[str]]:\n    \"\"\"Parses pattern argument specs and returns a two-tuple of\n    (patterns, ignore_patterns).\n    \"\"\"\n    patterns = patterns_spec.split(separator)\n    ignore_patterns = ignore_patterns_spec.split(separator)\n    if ignore_patterns == [\"\"]:\n        ignore_patterns = []\n    return patterns, ignore_patterns\n\n\ndef observe_with(\n    observer: BaseObserver,\n    event_handler: FileSystemEventHandler,\n    pathnames: list[str],\n    *,\n    recursive: bool,\n) -> None:\n    \"\"\"Single observer thread with a scheduled path and event handler.\n\n    :param observer:\n        The observer thread.\n    :param event_handler:\n        Event handler which will be called in response to file system events.\n    :param pathnames:\n        A list of pathnames to monitor.\n    :param recursive:\n        ``True`` if recursive; ``False`` otherwise.\n    \"\"\"\n    for pathname in set(pathnames):\n        observer.schedule(event_handler, pathname, recursive=recursive)\n    observer.start()\n    try:\n        while True:\n            time.sleep(1)\n    except WatchdogShutdownError:\n        observer.stop()\n    observer.join()\n\n\ndef schedule_tricks(observer: BaseObserver, tricks: list[dict], pathname: str, *, recursive: bool) -> None:\n    \"\"\"Schedules tricks with the specified observer and for the given watch\n    path.\n\n    :param observer:\n        The observer thread into which to schedule the trick and watch.\n    :param tricks:\n        A list of tricks.\n    :param pathname:\n        A path name which should be watched.\n    :param recursive:\n        ``True`` if recursive; ``False`` otherwise.\n    \"\"\"\n    for trick in tricks:\n        for name, value in trick.items():\n            trick_cls = load_class(name)\n            handler = trick_cls(**value)\n            trick_pathname = getattr(handler, \"source_directory\", None) or pathname\n            observer.schedule(handler, trick_pathname, recursive=recursive)\n\n\n@command(\n    [\n        argument(\"files\", nargs=\"*\", help=\"perform tricks from given file\"),\n        argument(\n            \"--python-path\",\n            default=\".\",\n            help=f\"Paths separated by {os.pathsep!r} to add to the Python path.\",\n        ),\n        argument(\n            \"--interval\",\n            \"--timeout\",\n            dest=\"timeout\",\n            default=1.0,\n            type=float,\n            help=\"Use this as the polling interval/blocking timeout (in seconds).\",\n        ),\n        argument(\n            \"--recursive\",\n            action=\"store_true\",\n            default=True,\n            help=\"Recursively monitor paths (defaults to True).\",\n        ),\n        argument(\"--debug-force-polling\", action=\"store_true\", help=\"[debug] Forces polling.\"),\n        argument(\n            \"--debug-force-kqueue\",\n            action=\"store_true\",\n            help=\"[debug] Forces BSD kqueue(2).\",\n        ),\n        argument(\n            \"--debug-force-winapi\",\n            action=\"store_true\",\n            help=\"[debug] Forces Windows API.\",\n        ),\n        argument(\n            \"--debug-force-fsevents\",\n            action=\"store_true\",\n            help=\"[debug] Forces macOS FSEvents.\",\n        ),\n        argument(\n            \"--debug-force-inotify\",\n            action=\"store_true\",\n            help=\"[debug] Forces Linux inotify(7).\",\n        ),\n    ],\n    cmd_aliases=[\"tricks\"],\n)\ndef tricks_from(args: Namespace) -> None:\n    \"\"\"Command to execute tricks from a tricks configuration file.\"\"\"\n    observer_cls: ObserverType\n    if args.debug_force_polling:\n        from watchdog.observers.polling import PollingObserver\n\n        observer_cls = PollingObserver\n    elif args.debug_force_kqueue:\n        from watchdog.observers.kqueue import KqueueObserver\n\n        observer_cls = KqueueObserver\n    elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and platform.is_windows()):\n        from watchdog.observers.read_directory_changes import WindowsApiObserver\n\n        observer_cls = WindowsApiObserver\n    elif args.debug_force_inotify:\n        from watchdog.observers.inotify import InotifyObserver\n\n        observer_cls = InotifyObserver\n    elif args.debug_force_fsevents:\n        from watchdog.observers.fsevents import FSEventsObserver\n\n        observer_cls = FSEventsObserver\n    else:\n        # Automatically picks the most appropriate observer for the platform\n        # on which it is running.\n        from watchdog.observers import Observer\n\n        observer_cls = Observer\n\n    add_to_sys_path(path_split(args.python_path))\n    observers = []\n    for tricks_file in args.files:\n        observer = observer_cls(timeout=args.timeout)\n\n        if not os.path.exists(tricks_file):\n            raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), tricks_file)\n\n        config = load_config(tricks_file)\n\n        try:\n            tricks = config[CONFIG_KEY_TRICKS]\n        except KeyError as e:\n            error = f\"No {CONFIG_KEY_TRICKS!r} key specified in {tricks_file!r}.\"\n            raise KeyError(error) from e\n\n        if CONFIG_KEY_PYTHON_PATH in config:\n            add_to_sys_path(config[CONFIG_KEY_PYTHON_PATH])\n\n        dir_path = os.path.dirname(tricks_file) or os.path.relpath(os.getcwd())\n        schedule_tricks(observer, tricks, dir_path, recursive=args.recursive)\n        observer.start()\n        observers.append(observer)\n\n    try:\n        while True:\n            time.sleep(1)\n    except WatchdogShutdownError:\n        for o in observers:\n            o.unschedule_all()\n            o.stop()\n    for o in observers:\n        o.join()\n\n\n@command(\n    [\n        argument(\n            \"trick_paths\",\n            nargs=\"*\",\n            help=\"Dotted paths for all the tricks you want to generate.\",\n        ),\n        argument(\n            \"--python-path\",\n            default=\".\",\n            help=f\"Paths separated by {os.pathsep!r} to add to the Python path.\",\n        ),\n        argument(\n            \"--append-to-file\",\n            default=None,\n            help=\"\"\"\n                   Appends the generated tricks YAML to a file.\n                   If not specified, prints to standard output.\"\"\",\n        ),\n        argument(\n            \"-a\",\n            \"--append-only\",\n            dest=\"append_only\",\n            action=\"store_true\",\n            help=\"\"\"\n                   If --append-to-file is not specified, produces output for\n                   appending instead of a complete tricks YAML file.\"\"\",\n        ),\n    ],\n    cmd_aliases=[\"generate-tricks-yaml\"],\n)\ndef tricks_generate_yaml(args: Namespace) -> None:\n    \"\"\"Command to generate Yaml configuration for tricks named on the command line.\"\"\"\n    import yaml\n\n    python_paths = path_split(args.python_path)\n    add_to_sys_path(python_paths)\n    output = StringIO()\n\n    for trick_path in args.trick_paths:\n        trick_cls = load_class(trick_path)\n        output.write(trick_cls.generate_yaml())\n\n    content = output.getvalue()\n    output.close()\n\n    header = yaml.dump({CONFIG_KEY_PYTHON_PATH: python_paths})\n    header += f\"{CONFIG_KEY_TRICKS}:\\n\"\n    if args.append_to_file is None:\n        # Output to standard output.\n        if not args.append_only:\n            content = header + content\n        sys.stdout.write(content)\n    else:\n        if not os.path.exists(args.append_to_file):\n            content = header + content\n        with open(args.append_to_file, \"a\", encoding=\"utf-8\") as file:\n            file.write(content)\n\n\n@command(\n    [\n        argument(\n            \"directories\",\n            nargs=\"*\",\n            default=\".\",\n            help=\"Directories to watch. (default: '.').\",\n        ),\n        argument(\n            \"-p\",\n            \"--pattern\",\n            \"--patterns\",\n            dest=\"patterns\",\n            default=\"*\",\n            help=\"Matches event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-i\",\n            \"--ignore-pattern\",\n            \"--ignore-patterns\",\n            dest=\"ignore_patterns\",\n            default=\"\",\n            help=\"Ignores event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-D\",\n            \"--ignore-directories\",\n            dest=\"ignore_directories\",\n            action=\"store_true\",\n            help=\"Ignores events for directories.\",\n        ),\n        argument(\n            \"-R\",\n            \"--recursive\",\n            dest=\"recursive\",\n            action=\"store_true\",\n            help=\"Monitors the directories recursively.\",\n        ),\n        argument(\n            \"--interval\",\n            \"--timeout\",\n            dest=\"timeout\",\n            default=1.0,\n            type=float,\n            help=\"Use this as the polling interval/blocking timeout.\",\n        ),\n        argument(\"--debug-force-polling\", action=\"store_true\", help=\"[debug] Forces polling.\"),\n        argument(\n            \"--debug-force-kqueue\",\n            action=\"store_true\",\n            help=\"[debug] Forces BSD kqueue(2).\",\n        ),\n        argument(\n            \"--debug-force-winapi\",\n            action=\"store_true\",\n            help=\"[debug] Forces Windows API.\",\n        ),\n        argument(\n            \"--debug-force-fsevents\",\n            action=\"store_true\",\n            help=\"[debug] Forces macOS FSEvents.\",\n        ),\n        argument(\n            \"--debug-force-inotify\",\n            action=\"store_true\",\n            help=\"[debug] Forces Linux inotify(7).\",\n        ),\n    ],\n)\ndef log(args: Namespace) -> None:\n    \"\"\"Command to log file system events to the console.\"\"\"\n    from watchdog.tricks import LoggerTrick\n\n    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)\n    handler = LoggerTrick(\n        patterns=patterns,\n        ignore_patterns=ignore_patterns,\n        ignore_directories=args.ignore_directories,\n    )\n\n    observer_cls: ObserverType\n    if args.debug_force_polling:\n        from watchdog.observers.polling import PollingObserver\n\n        observer_cls = PollingObserver\n    elif args.debug_force_kqueue:\n        from watchdog.observers.kqueue import KqueueObserver\n\n        observer_cls = KqueueObserver\n    elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and platform.is_windows()):\n        from watchdog.observers.read_directory_changes import WindowsApiObserver\n\n        observer_cls = WindowsApiObserver\n    elif args.debug_force_inotify:\n        from watchdog.observers.inotify import InotifyObserver\n\n        observer_cls = InotifyObserver\n    elif args.debug_force_fsevents:\n        from watchdog.observers.fsevents import FSEventsObserver\n\n        observer_cls = FSEventsObserver\n    else:\n        # Automatically picks the most appropriate observer for the platform\n        # on which it is running.\n        from watchdog.observers import Observer\n\n        observer_cls = Observer\n\n    observer = observer_cls(timeout=args.timeout)\n    observe_with(observer, handler, args.directories, recursive=args.recursive)\n\n\n@command(\n    [\n        argument(\"directories\", nargs=\"*\", default=\".\", help=\"Directories to watch.\"),\n        argument(\n            \"-c\",\n            \"--command\",\n            dest=\"command\",\n            default=None,\n            help=\"\"\"\n    Shell command executed in response to matching events.\n    These interpolation variables are available to your command string:\n\n        ${watch_src_path}   - event source path\n        ${watch_dest_path}  - event destination path (for moved events)\n        ${watch_event_type} - event type\n        ${watch_object}     - 'file' or 'directory'\n\n    Note:\n        Please ensure you do not use double quotes (\") to quote\n        your command string. That will force your shell to\n        interpolate before the command is processed by this\n        command.\n\n    Example:\n\n        --command='echo \"${watch_src_path}\"'\n    \"\"\",\n        ),\n        argument(\n            \"-p\",\n            \"--pattern\",\n            \"--patterns\",\n            dest=\"patterns\",\n            default=\"*\",\n            help=\"Matches event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-i\",\n            \"--ignore-pattern\",\n            \"--ignore-patterns\",\n            dest=\"ignore_patterns\",\n            default=\"\",\n            help=\"Ignores event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-D\",\n            \"--ignore-directories\",\n            dest=\"ignore_directories\",\n            default=False,\n            action=\"store_true\",\n            help=\"Ignores events for directories.\",\n        ),\n        argument(\n            \"-R\",\n            \"--recursive\",\n            dest=\"recursive\",\n            action=\"store_true\",\n            help=\"Monitors the directories recursively.\",\n        ),\n        argument(\n            \"--interval\",\n            \"--timeout\",\n            dest=\"timeout\",\n            default=1.0,\n            type=float,\n            help=\"Use this as the polling interval/blocking timeout.\",\n        ),\n        argument(\n            \"-w\",\n            \"--wait\",\n            dest=\"wait_for_process\",\n            action=\"store_true\",\n            help=\"Wait for process to finish to avoid multiple simultaneous instances.\",\n        ),\n        argument(\n            \"-W\",\n            \"--drop\",\n            dest=\"drop_during_process\",\n            action=\"store_true\",\n            help=\"Ignore events that occur while command is still being\"\n            \" executed to avoid multiple simultaneous instances.\",\n        ),\n        argument(\"--debug-force-polling\", action=\"store_true\", help=\"[debug] Forces polling.\"),\n    ],\n)\ndef shell_command(args: Namespace) -> None:\n    \"\"\"Command to execute shell commands in response to file system events.\"\"\"\n    from watchdog.tricks import ShellCommandTrick\n\n    if not args.command:\n        args.command = None\n\n    observer_cls: ObserverType\n    if args.debug_force_polling:\n        from watchdog.observers.polling import PollingObserver\n\n        observer_cls = PollingObserver\n    else:\n        from watchdog.observers import Observer\n\n        observer_cls = Observer\n\n    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)\n    handler = ShellCommandTrick(\n        args.command,\n        patterns=patterns,\n        ignore_patterns=ignore_patterns,\n        ignore_directories=args.ignore_directories,\n        wait_for_process=args.wait_for_process,\n        drop_during_process=args.drop_during_process,\n    )\n    observer = observer_cls(timeout=args.timeout)\n    observe_with(observer, handler, args.directories, recursive=args.recursive)\n\n\n@command(\n    [\n        argument(\"command\", help=\"Long-running command to run in a subprocess.\"),\n        argument(\n            \"command_args\",\n            metavar=\"arg\",\n            nargs=\"*\",\n            help=\"\"\"\n    Command arguments.\n\n    Note: Use -- before the command arguments, otherwise watchmedo will\n    try to interpret them.\n    \"\"\",\n        ),\n        argument(\n            \"-d\",\n            \"--directory\",\n            dest=\"directories\",\n            metavar=\"DIRECTORY\",\n            action=\"append\",\n            help=\"Directory to watch. Use another -d or --directory option for each directory.\",\n        ),\n        argument(\n            \"-p\",\n            \"--pattern\",\n            \"--patterns\",\n            dest=\"patterns\",\n            default=\"*\",\n            help=\"Matches event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-i\",\n            \"--ignore-pattern\",\n            \"--ignore-patterns\",\n            dest=\"ignore_patterns\",\n            default=\"\",\n            help=\"Ignores event paths with these patterns (separated by ;).\",\n        ),\n        argument(\n            \"-D\",\n            \"--ignore-directories\",\n            dest=\"ignore_directories\",\n            default=False,\n            action=\"store_true\",\n            help=\"Ignores events for directories.\",\n        ),\n        argument(\n            \"-R\",\n            \"--recursive\",\n            dest=\"recursive\",\n            action=\"store_true\",\n            help=\"Monitors the directories recursively.\",\n        ),\n        argument(\n            \"--interval\",\n            \"--timeout\",\n            dest=\"timeout\",\n            default=1.0,\n            type=float,\n            help=\"Use this as the polling interval/blocking timeout.\",\n        ),\n        argument(\n            \"--signal\",\n            dest=\"signal\",\n            default=\"SIGINT\",\n            help=\"Stop the subprocess with this signal (default SIGINT).\",\n        ),\n        argument(\"--debug-force-polling\", action=\"store_true\", help=\"[debug] Forces polling.\"),\n        argument(\n            \"--kill-after\",\n            dest=\"kill_after\",\n            default=10.0,\n            type=float,\n            help=\"When stopping, kill the subprocess after the specified timeout in seconds (default 10.0).\",\n        ),\n        argument(\n            \"--debounce-interval\",\n            dest=\"debounce_interval\",\n            default=0.0,\n            type=float,\n            help=\"After a file change, Wait until the specified interval (in \"\n            \"seconds) passes with no file changes, and only then restart.\",\n        ),\n        argument(\n            \"--no-restart-on-command-exit\",\n            dest=\"restart_on_command_exit\",\n            default=True,\n            action=\"store_false\",\n            help=\"Don't auto-restart the command after it exits.\",\n        ),\n    ],\n)\ndef auto_restart(args: Namespace) -> None:\n    \"\"\"Command to start a long-running subprocess and restart it on matched events.\"\"\"\n    observer_cls: ObserverType\n    if args.debug_force_polling:\n        from watchdog.observers.polling import PollingObserver\n\n        observer_cls = PollingObserver\n    else:\n        from watchdog.observers import Observer\n\n        observer_cls = Observer\n\n    import signal\n\n    from watchdog.tricks import AutoRestartTrick\n\n    if not args.directories:\n        args.directories = [\".\"]\n\n    # Allow either signal name or number.\n    stop_signal = getattr(signal, args.signal) if args.signal.startswith(\"SIG\") else int(args.signal)\n\n    # Handle termination signals by raising a semantic exception which will\n    # allow us to gracefully unwind and stop the observer\n    termination_signals = {signal.SIGTERM, signal.SIGINT}\n\n    if hasattr(signal, \"SIGHUP\"):\n        termination_signals.add(signal.SIGHUP)\n\n    def handler_termination_signal(_signum: signal._SIGNUM, _frame: object) -> None:\n        # Neuter all signals so that we don't attempt a double shutdown\n        for signum in termination_signals:\n            signal.signal(signum, signal.SIG_IGN)\n        raise WatchdogShutdownError\n\n    for signum in termination_signals:\n        signal.signal(signum, handler_termination_signal)\n\n    patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns)\n    command = [args.command]\n    command.extend(args.command_args)\n    handler = AutoRestartTrick(\n        command,\n        patterns=patterns,\n        ignore_patterns=ignore_patterns,\n        ignore_directories=args.ignore_directories,\n        stop_signal=stop_signal,\n        kill_after=args.kill_after,\n        debounce_interval_seconds=args.debounce_interval,\n        restart_on_command_exit=args.restart_on_command_exit,\n    )\n    handler.start()\n    observer = observer_cls(timeout=args.timeout)\n    try:\n        observe_with(observer, handler, args.directories, recursive=args.recursive)\n    except WatchdogShutdownError:\n        pass\n    finally:\n        handler.stop()\n\n\nclass LogLevelError(Exception):\n    pass\n\n\ndef _get_log_level_from_args(args: Namespace) -> str:\n    verbosity = sum(args.verbosity or [])\n    if verbosity < -1:\n        error = \"-q/--quiet may be specified only once.\"\n        raise LogLevelError(error)\n    if verbosity > 2:\n        error = \"-v/--verbose may be specified up to 2 times.\"\n        raise LogLevelError(error)\n    return [\"ERROR\", \"WARNING\", \"INFO\", \"DEBUG\"][1 + verbosity]\n\n\ndef main() -> int:\n    \"\"\"Entry-point function.\"\"\"\n    args = cli.parse_args()\n    if args.top_command is None:\n        cli.print_help()\n        return 1\n\n    try:\n        log_level = _get_log_level_from_args(args)\n    except LogLevelError as exc:\n        print(f\"Error: {exc.args[0]}\", file=sys.stderr)  # noqa:T201\n        command_parsers[args.top_command].print_help()\n        return 1\n    logging.getLogger(\"watchdog\").setLevel(log_level)\n\n    try:\n        args.func(args)\n    except KeyboardInterrupt:\n        return 130\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "src/watchdog_fsevents.c",
    "content": "/**\n * watchdog_fsevents.c: Python-C bridge to the OS X FSEvents API.\n *\n * Copyright 2018-2025 Mickaël Schoentgen & contributors\n * Copyright 2012-2018 Google, Inc.\n * Copyright 2011-2012 Yesudeep Mangalapilly <yesudeep@gmail.com>\n * Copyright 2010-2011 Malthe Borch <mborch@gmail.com>\n */\n\n\n#include <Python.h>\n#include <Availability.h>\n#include <CoreFoundation/CoreFoundation.h>\n#include <CoreServices/CoreServices.h>\n#include <stdlib.h>\n#include <signal.h>\n#include \"pythoncapi_compat.h\"\n\n\n/* Compatibility; since fsevents won't set these on earlier macOS versions the properties will always be False */\n#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13\n#error Watchdog module requires at least macOS 10.13\n#endif\n\n/* Convenience macros to make code more readable. */\n#define G_NOT(o)                        !o\n#define G_IS_NULL(o)                    o == NULL\n#define G_IS_NOT_NULL(o)                o != NULL\n#define G_RETURN_NULL_IF_NULL(o)        do { if (NULL == o) { return NULL; } } while (0)\n#define G_RETURN_NULL_IF(condition)     do { if (condition) { return NULL; } } while (0)\n#define G_RETURN_NULL_IF_NOT(condition) do { if (!condition) { return NULL; } } while (0)\n#define G_RETURN_IF(condition)          do { if (condition) { return; } } while (0)\n#define G_RETURN_IF_NOT(condition)      do { if (!condition) { return; } } while (0)\n#define UNUSED(x)                       (void)x\n\n/* Error message definitions. */\n#define ERROR_CANNOT_CALL_CALLBACK \"Unable to call Python callback.\"\n\n/* Other information. */\n#define MODULE_NAME  \"_watchdog_fsevents\"\n\n/**\n * Event stream callback contextual information passed to\n * our ``watchdog_FSEventStreamCallback`` function by the\n * FSEvents API whenever an event occurs.\n */\ntypedef struct {\n    /**\n     * A pointer to the Python callback which will\n     * will in turn be called by our event handler\n     * with event information. The Python callback\n     * function must accept 2 arguments, both of which\n     * are Python lists::\n     *\n     *    def python_callback(event_paths, event_inodes, event_flags, event_ids):\n     *        pass\n     */\n    PyObject        *python_callback;\n    /**\n     * A pointer to the associated ``FSEventStream``\n     * instance.\n     */\n    FSEventStreamRef stream_ref;\n    /**\n     * A pointer to the associated ``CFRunLoop``\n     * instance.\n     */\n    CFRunLoopRef     run_loop_ref;\n    /**\n     * A pointer to the state of the Python thread.\n     */\n    PyThreadState   *thread_state;\n} StreamCallbackInfo;\n\n\n/**\n * NativeEvent type so that we don't need to expose the FSEvents constants to Python land\n */\ntypedef struct {\n    PyObject_HEAD\n    const char *path;\n    PyObject *inode;\n    FSEventStreamEventFlags flags;\n    FSEventStreamEventId id;\n} NativeEventObject;\n\nPyObject* NativeEventRepr(PyObject* instance) {\n    NativeEventObject *self = (NativeEventObject*)instance;\n\n    return PyUnicode_FromFormat(\n        \"NativeEvent(path=\\\"%s\\\", inode=%S, flags=%x, id=%llu)\",\n        self->path,\n        self->inode,\n        self->flags,\n        self->id\n    );\n}\n\nPyObject* NativeEventTypeFlags(PyObject* instance, void* closure)\n{\n    UNUSED(closure);\n    NativeEventObject *self = (NativeEventObject*)instance;\n    return PyLong_FromLong(self->flags);\n}\n\nPyObject* NativeEventTypePath(PyObject* instance, void* closure)\n{\n    UNUSED(closure);\n    NativeEventObject *self = (NativeEventObject*)instance;\n    return PyUnicode_FromString(self->path);\n}\n\nPyObject* NativeEventTypeInode(PyObject* instance, void* closure)\n{\n    UNUSED(closure);\n    NativeEventObject *self = (NativeEventObject*)instance;\n    Py_INCREF(self->inode);\n    return self->inode;\n}\n\nPyObject* NativeEventTypeID(PyObject* instance, void* closure)\n{\n    UNUSED(closure);\n    NativeEventObject *self = (NativeEventObject*)instance;\n    return PyLong_FromLong(self->id);\n}\n\nPyObject* NativeEventTypeIsCoalesced(PyObject* instance, void* closure)\n{\n    UNUSED(closure);\n    NativeEventObject *self = (NativeEventObject*)instance;\n\n    // if any of these bitmasks match then we have a coalesced event and need to do sys calls to figure out what happened\n    FSEventStreamEventFlags coalesced_masks[] = {\n        kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRemoved,\n        kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed,\n        kFSEventStreamEventFlagItemRemoved | kFSEventStreamEventFlagItemRenamed,\n    };\n    for (size_t i = 0; i < sizeof(coalesced_masks) / sizeof(FSEventStreamEventFlags); ++i) {\n        if ((self->flags & coalesced_masks[i]) == coalesced_masks[i]) {\n            Py_RETURN_TRUE;\n        }\n    }\n\n    Py_RETURN_FALSE;\n}\n\n#define FLAG_PROPERTY(suffix, flag) \\\n    PyObject* NativeEventType##suffix(PyObject* instance, void* closure) \\\n    { \\\n        UNUSED(closure); \\\n        NativeEventObject *self = (NativeEventObject*)instance; \\\n        if (self->flags & flag) { \\\n            Py_RETURN_TRUE; \\\n        } \\\n        Py_RETURN_FALSE; \\\n    }\n\nFLAG_PROPERTY(IsMustScanSubDirs, kFSEventStreamEventFlagMustScanSubDirs)\nFLAG_PROPERTY(IsUserDropped, kFSEventStreamEventFlagUserDropped)\nFLAG_PROPERTY(IsKernelDropped, kFSEventStreamEventFlagKernelDropped)\nFLAG_PROPERTY(IsEventIdsWrapped, kFSEventStreamEventFlagEventIdsWrapped)\nFLAG_PROPERTY(IsHistoryDone, kFSEventStreamEventFlagHistoryDone)\nFLAG_PROPERTY(IsRootChanged, kFSEventStreamEventFlagRootChanged)\nFLAG_PROPERTY(IsMount, kFSEventStreamEventFlagMount)\nFLAG_PROPERTY(IsUnmount, kFSEventStreamEventFlagUnmount)\nFLAG_PROPERTY(IsCreated, kFSEventStreamEventFlagItemCreated)\nFLAG_PROPERTY(IsRemoved, kFSEventStreamEventFlagItemRemoved)\nFLAG_PROPERTY(IsInodeMetaMod, kFSEventStreamEventFlagItemInodeMetaMod)\nFLAG_PROPERTY(IsRenamed, kFSEventStreamEventFlagItemRenamed)\nFLAG_PROPERTY(IsModified, kFSEventStreamEventFlagItemModified)\nFLAG_PROPERTY(IsItemFinderInfoMod, kFSEventStreamEventFlagItemFinderInfoMod)\nFLAG_PROPERTY(IsChangeOwner, kFSEventStreamEventFlagItemChangeOwner)\nFLAG_PROPERTY(IsXattrMod, kFSEventStreamEventFlagItemXattrMod)\nFLAG_PROPERTY(IsFile, kFSEventStreamEventFlagItemIsFile)\nFLAG_PROPERTY(IsDirectory, kFSEventStreamEventFlagItemIsDir)\nFLAG_PROPERTY(IsSymlink, kFSEventStreamEventFlagItemIsSymlink)\nFLAG_PROPERTY(IsOwnEvent, kFSEventStreamEventFlagOwnEvent)\nFLAG_PROPERTY(IsHardlink, kFSEventStreamEventFlagItemIsHardlink)\nFLAG_PROPERTY(IsLastHardlink, kFSEventStreamEventFlagItemIsLastHardlink)\nFLAG_PROPERTY(IsCloned, kFSEventStreamEventFlagItemCloned)\n\nstatic int NativeEventInit(NativeEventObject *self, PyObject *args, PyObject *kwds)\n{\n    static char *kwlist[] = {\"path\", \"inode\", \"flags\", \"id\", NULL};\n\n    self->inode = NULL;\n\n    if (!PyArg_ParseTupleAndKeywords(args, kwds, \"|sOIL\", kwlist, &self->path, &self->inode, &self->flags, &self->id)) {\n        return -1;\n    }\n\n    Py_INCREF(self->inode);\n\n    return 0;\n}\n\nstatic void NativeEventDealloc(NativeEventObject *self) {\n    Py_XDECREF(self->inode);\n}\n\nstatic PyGetSetDef NativeEventProperties[] = {\n    {\"flags\", NativeEventTypeFlags, NULL, \"The raw mask of flags as returned by FSEvents\", NULL},\n    {\"path\", NativeEventTypePath, NULL, \"The path for which this event was generated\", NULL},\n    {\"inode\", NativeEventTypeInode, NULL, \"The inode for which this event was generated\", NULL},\n    {\"event_id\", NativeEventTypeID, NULL, \"The id of the generated event\", NULL},\n    {\"is_coalesced\", NativeEventTypeIsCoalesced, NULL, \"True if multiple ambiguous changes to the monitored path happened\", NULL},\n    {\"must_scan_subdirs\", NativeEventTypeIsMustScanSubDirs, NULL, \"True if application must rescan all subdirectories\", NULL},\n    {\"is_user_dropped\", NativeEventTypeIsUserDropped, NULL, \"True if a failure during event buffering occurred\", NULL},\n    {\"is_kernel_dropped\", NativeEventTypeIsKernelDropped, NULL, \"True if a failure during event buffering occurred\", NULL},\n    {\"is_event_ids_wrapped\", NativeEventTypeIsEventIdsWrapped, NULL, \"True if event_id wrapped around\", NULL},\n    {\"is_history_done\", NativeEventTypeIsHistoryDone, NULL, \"True if all historical events are done\", NULL},\n    {\"is_root_changed\", NativeEventTypeIsRootChanged, NULL, \"True if a change to one of the directories along the path to one of the directories you watch occurred\", NULL},\n    {\"is_mount\", NativeEventTypeIsMount, NULL, \"True if a volume is mounted underneath one of the paths being monitored\", NULL},\n    {\"is_unmount\", NativeEventTypeIsUnmount, NULL, \"True if a volume is unmounted underneath one of the paths being monitored\", NULL},\n    {\"is_created\", NativeEventTypeIsCreated, NULL, \"True if self.path was created on the filesystem\", NULL},\n    {\"is_removed\", NativeEventTypeIsRemoved, NULL, \"True if self.path was removed from the filesystem\", NULL},\n    {\"is_inode_meta_mod\", NativeEventTypeIsInodeMetaMod, NULL, \"True if meta data for self.path was modified \", NULL},\n    {\"is_renamed\", NativeEventTypeIsRenamed, NULL, \"True if self.path was renamed on the filesystem\", NULL},\n    {\"is_modified\", NativeEventTypeIsModified, NULL, \"True if self.path was modified\", NULL},\n    {\"is_item_finder_info_modified\", NativeEventTypeIsItemFinderInfoMod, NULL, \"True if FinderInfo for self.path was modified\", NULL},\n    {\"is_owner_change\", NativeEventTypeIsChangeOwner, NULL, \"True if self.path had its ownership changed\", NULL},\n    {\"is_xattr_mod\", NativeEventTypeIsXattrMod, NULL, \"True if extended attributes for self.path were modified \", NULL},\n    {\"is_file\", NativeEventTypeIsFile, NULL, \"True if self.path is a file\", NULL},\n    {\"is_directory\", NativeEventTypeIsDirectory, NULL, \"True if self.path is a directory\", NULL},\n    {\"is_symlink\", NativeEventTypeIsSymlink, NULL, \"True if self.path is a symbolic link\", NULL},\n    {\"is_own_event\", NativeEventTypeIsOwnEvent, NULL, \"True if the event originated from our own process\", NULL},\n    {\"is_hardlink\", NativeEventTypeIsHardlink, NULL, \"True if self.path is a hard link\", NULL},\n    {\"is_last_hardlink\", NativeEventTypeIsLastHardlink, NULL, \"True if self.path was the last hard link\", NULL},\n    {\"is_cloned\", NativeEventTypeIsCloned, NULL, \"True if self.path is a clone or was cloned\", NULL},\n    {NULL, NULL, NULL, NULL, NULL},\n};\n\n\nstatic PyTypeObject NativeEventType = {\n    PyVarObject_HEAD_INIT(NULL, 0)\n    .tp_name = \"_watchdog_fsevents.NativeEvent\",\n    .tp_doc = \"A wrapper around native FSEvents events\",\n    .tp_basicsize = sizeof(NativeEventObject),\n    .tp_itemsize = 0,\n    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,\n    .tp_new = PyType_GenericNew,\n    .tp_getset = NativeEventProperties,\n    .tp_init = (initproc) NativeEventInit,\n    .tp_repr = (reprfunc) NativeEventRepr,\n    .tp_dealloc = (destructor) NativeEventDealloc,\n};\n\n\n/**\n * Dictionary to keep track of which run loop\n * belongs to which emitter thread.\n */\nPyObject *thread_to_run_loop = NULL;\n\n/**\n * Dictionary to keep track of which stream\n * belongs to which watch.\n */\nPyObject *watch_to_stream = NULL;\n\n\n/**\n * PyCapsule destructor.\n */\nstatic void watchdog_pycapsule_destructor(PyObject *ptr)\n{\n    void *p = PyCapsule_GetPointer(ptr, NULL);\n    if (p) {\n        PyMem_Free(p);\n    }\n}\n\n\n\n/**\n * Converts a ``CFStringRef`` to a Python string object.\n *\n * :param cf_string:\n *      A ``CFStringRef``.\n * :returns:\n *      A Python unicode or utf-8 encoded bytestring object.\n */\nPyObject * CFString_AsPyUnicode(CFStringRef cf_string_ref)\n{\n\n    if (G_IS_NULL(cf_string_ref)) {\n        return PyUnicode_FromString(\"\");\n    }\n\n    PyObject *py_string;\n\n    const char *c_string_ptr = CFStringGetCStringPtr(cf_string_ref, kCFStringEncodingUTF8);\n\n    if (G_IS_NULL(c_string_ptr)) {\n        CFIndex length = CFStringGetLength(cf_string_ref);\n        CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;\n        char *buffer = (char *)malloc(max_size);\n        if (CFStringGetCString(cf_string_ref, buffer, max_size, kCFStringEncodingUTF8)) {\n            py_string = PyUnicode_FromString(buffer);\n        }\n        else {\n            py_string = PyUnicode_FromString(\"\");\n        }\n        free(buffer);\n    } else {\n        py_string = PyUnicode_FromString(c_string_ptr);\n    }\n\n    return py_string;\n\n}\n\n/**\n * Converts a ``CFNumberRef`` to a Python string object.\n *\n * :param cf_number:\n *      A ``CFNumberRef``.\n * :returns:\n *      A Python unicode or utf-8 encoded bytestring object.\n */\nPyObject * CFNumberRef_AsPyLong(CFNumberRef cf_number)\n{\n    long c_int;\n    PyObject *py_long;\n\n    CFNumberGetValue(cf_number, kCFNumberSInt64Type, &c_int);\n\n    py_long = PyLong_FromLong(c_int);\n\n    return py_long;\n}\n\n\n/**\n * This is the callback passed to the FSEvents API, which calls\n * the Python callback function, in turn, by passing in event data\n * as Python objects.\n *\n * :param stream_ref:\n *     A pointer to an ``FSEventStream`` instance.\n * :param stream_callback_info_ref:\n *     Callback context information passed by the FSEvents API.\n *     This contains a reference to the Python callback that this\n *     function calls in turn with information about the events.\n * :param num_events:\n *     An unsigned integer representing the number of events\n *     captured by the FSEvents API.\n * :param event_paths:\n *     An array of NUL-terminated C strings representing event paths.\n * :param event_flags:\n *     An array of ``FSEventStreamEventFlags`` unsigned integral\n *     mask values.\n * :param event_ids:\n *     An array of 64-bit unsigned integers representing event\n *     identifiers.\n */\nstatic void\nwatchdog_FSEventStreamCallback(ConstFSEventStreamRef          stream_ref,\n                               StreamCallbackInfo            *stream_callback_info_ref,\n                               size_t                         num_events,\n                               CFArrayRef                     event_path_info_array_ref,\n                               const FSEventStreamEventFlags  event_flags[],\n                               const FSEventStreamEventId     event_ids[])\n{\n    UNUSED(stream_ref);\n    size_t i = 0;\n    CFDictionaryRef path_info_dict;\n    CFStringRef cf_path;\n    CFNumberRef cf_inode;\n    PyObject *callback_result = NULL;\n    PyObject *path = NULL;\n    PyObject *inode = NULL;\n    PyObject *id = NULL;\n    PyObject *flags = NULL;\n    PyObject *py_event_flags = NULL;\n    PyObject *py_event_ids = NULL;\n    PyObject *py_event_paths = NULL;\n    PyObject *py_event_inodes = NULL;\n    PyThreadState *saved_thread_state = NULL;\n\n    /* Acquire interpreter lock and save original thread state. */\n    PyGILState_STATE gil_state = PyGILState_Ensure();\n    saved_thread_state = PyThreadState_Swap(stream_callback_info_ref->thread_state);\n\n    /* Convert event flags and paths to Python ints and strings. */\n    py_event_paths = PyList_New(num_events);\n    py_event_inodes = PyList_New(num_events);\n    py_event_flags = PyList_New(num_events);\n    py_event_ids = PyList_New(num_events);\n    if (G_NOT(py_event_paths && py_event_inodes && py_event_flags && py_event_ids))\n    {\n        Py_XDECREF(py_event_paths);\n        Py_XDECREF(py_event_inodes);\n        Py_XDECREF(py_event_ids);\n        Py_XDECREF(py_event_flags);\n        return /*NULL*/;\n    }\n    for (i = 0; i < num_events; ++i)\n    {\n        id = PyLong_FromLongLong(event_ids[i]);\n        flags = PyLong_FromLong(event_flags[i]);\n\n        path_info_dict = CFArrayGetValueAtIndex(event_path_info_array_ref, i);\n        cf_path = CFDictionaryGetValue(path_info_dict, kFSEventStreamEventExtendedDataPathKey);\n        cf_inode = CFDictionaryGetValue(path_info_dict, kFSEventStreamEventExtendedFileIDKey);\n\n        path = CFString_AsPyUnicode(cf_path);\n\n        if (G_IS_NOT_NULL(cf_inode)) {\n            inode = CFNumberRef_AsPyLong(cf_inode);\n        } else {\n            Py_INCREF(Py_None);\n            inode = Py_None;\n        }\n\n        if (G_NOT(path && inode && flags && id))\n        {\n            Py_DECREF(py_event_paths);\n            Py_DECREF(py_event_inodes);\n            Py_DECREF(py_event_ids);\n            Py_DECREF(py_event_flags);\n            return /*NULL*/;\n        }\n        PyList_SET_ITEM(py_event_paths, i, path);\n        PyList_SET_ITEM(py_event_inodes, i, inode);\n        PyList_SET_ITEM(py_event_flags, i, flags);\n        PyList_SET_ITEM(py_event_ids, i, id);\n    }\n\n    /* Call the Python callback function supplied by the stream information\n     * struct. The Python callback function should accept two arguments,\n     * both being Python lists:\n     *\n     *    def python_callback(event_paths, event_flags, event_ids):\n     *        pass\n     */\n    callback_result = \\\n        PyObject_CallFunction(stream_callback_info_ref->python_callback,\n                              \"OOOO\", py_event_paths, py_event_inodes, py_event_flags, py_event_ids);\n    if (G_IS_NULL(callback_result))\n    {\n        if (G_NOT(PyErr_Occurred()))\n        {\n            PyErr_SetString(PyExc_ValueError, ERROR_CANNOT_CALL_CALLBACK);\n        }\n        CFRunLoopStop(stream_callback_info_ref->run_loop_ref);\n    }\n\n    /* Clean up callback result reference */\n    Py_XDECREF(callback_result);\n\n    /* Release the lock and restore thread state. */\n    PyThreadState_Swap(saved_thread_state);\n    PyGILState_Release(gil_state);\n}\n\n\n/**\n * Converts a Python string object to an UTF-8 encoded ``CFStringRef``.\n *\n * :param py_string:\n *      A Python unicode or utf-8 encoded bytestring object.\n * :returns:\n *      A new ``CFStringRef`` with the contents of ``py_string``, or ``NULL`` if an error occurred.\n */\nCFStringRef PyString_AsUTF8EncodedCFStringRef(PyObject *py_string)\n{\n    CFStringRef cf_string = NULL;\n\n    if (PyUnicode_Check(py_string)) {\n        PyObject* helper = PyUnicode_AsUTF8String(py_string);\n        if (!helper) {\n            return NULL;\n        }\n        cf_string = CFStringCreateWithCString(kCFAllocatorDefault, PyBytes_AS_STRING(helper), kCFStringEncodingUTF8);\n        Py_DECREF(helper);\n    } else if (PyBytes_Check(py_string)) {\n        PyObject *utf8 = PyUnicode_FromEncodedObject(py_string, NULL, \"strict\");\n        if (!utf8) {\n            return NULL;\n        }\n        Py_DECREF(utf8);\n        cf_string = CFStringCreateWithCString(kCFAllocatorDefault, PyBytes_AS_STRING(py_string), kCFStringEncodingUTF8);\n    } else {\n        PyErr_SetString(PyExc_TypeError, \"Path to watch must be a string or a UTF-8 encoded bytes object.\");\n        return NULL;\n    }\n\n    return cf_string;\n}\n\n/**\n * Converts a list of Python strings to a ``CFMutableArray`` of\n * UTF-8 encoded ``CFString`` instances and returns a pointer to\n * the array.\n *\n * :param py_string_list:\n *     List of Python strings.\n * :returns:\n *     A pointer to ``CFMutableArray`` (that is, a\n *     ``CFMutableArrayRef``) of UTF-8 encoded ``CFString``\n *     instances.\n */\nstatic CFMutableArrayRef\nwatchdog_CFMutableArrayRef_from_PyStringList(PyObject *py_string_list)\n{\n    Py_ssize_t i = 0;\n    Py_ssize_t string_list_size = 0;\n    CFMutableArrayRef array_of_cf_string = NULL;\n    CFStringRef cf_string = NULL;\n    PyObject *py_string = NULL;\n\n    G_RETURN_NULL_IF_NULL(py_string_list);\n\n    string_list_size = PyList_Size(py_string_list);\n\n    /* Allocate a CFMutableArray. */\n    array_of_cf_string = CFArrayCreateMutable(kCFAllocatorDefault, 1,\n                                              &kCFTypeArrayCallBacks);\n    G_RETURN_NULL_IF_NULL(array_of_cf_string);\n\n    /* Loop through the Python string list and copy strings to the\n     * CFString array list. */\n    for (i = 0; i < string_list_size; ++i)\n    {\n        py_string = PyList_GetItemRef(py_string_list, i);\n        G_RETURN_NULL_IF_NULL(py_string);\n        cf_string = PyString_AsUTF8EncodedCFStringRef(py_string);\n        if (cf_string == NULL) {\n            Py_DECREF(py_string);\n            return NULL;\n        }\n        CFArraySetValueAtIndex(array_of_cf_string, i, cf_string);\n        CFRelease(cf_string);\n        Py_DECREF(py_string);\n    }\n\n    return array_of_cf_string;\n}\n\n\n/**\n * Creates an instance of ``FSEventStream`` and returns a pointer\n * to the instance.\n *\n * :param stream_callback_info_ref:\n *      Pointer to the callback context information that will be\n *      passed by the FSEvents API to the callback handler specified\n *      by the ``callback`` argument to this function. This\n *      information contains a reference to the Python callback that\n *      it must call in turn passing on the event information\n *      as Python objects to the the Python callback.\n * :param py_paths:\n *      A Python list of Python strings representing path names\n *      to monitor.\n * :param callback:\n *      A function pointer of type ``FSEventStreamCallback``.\n * :returns:\n *      A pointer to an ``FSEventStream`` instance (that is, it returns\n *      an ``FSEventStreamRef``).\n */\nstatic FSEventStreamRef\nwatchdog_FSEventStreamCreate(StreamCallbackInfo *stream_callback_info_ref,\n                             PyObject *py_paths,\n                             FSEventStreamCallback callback)\n{\n    CFAbsoluteTime stream_latency = 0.01;\n    CFMutableArrayRef paths = NULL;\n    FSEventStreamRef stream_ref = NULL;\n\n    /* Check arguments. */\n    G_RETURN_NULL_IF_NULL(py_paths);\n    G_RETURN_NULL_IF_NULL(callback);\n\n    /* Convert the Python paths list to a CFMutableArray. */\n    paths = watchdog_CFMutableArrayRef_from_PyStringList(py_paths);\n    G_RETURN_NULL_IF_NULL(paths);\n\n    /* Create the event stream. */\n    FSEventStreamContext stream_context = {\n        0, stream_callback_info_ref, NULL, NULL, NULL\n    };\n    stream_ref = FSEventStreamCreate(kCFAllocatorDefault,\n                                     callback,\n                                     &stream_context,\n                                     paths,\n                                     kFSEventStreamEventIdSinceNow,\n                                     stream_latency,\n                                     kFSEventStreamCreateFlagNoDefer\n                                     | kFSEventStreamCreateFlagFileEvents\n                                     | kFSEventStreamCreateFlagWatchRoot\n                                     | kFSEventStreamCreateFlagUseExtendedData\n                                     | kFSEventStreamCreateFlagUseCFTypes);\n    CFRelease(paths);\n    return stream_ref;\n}\n\n\nPyDoc_STRVAR(watchdog_add_watch__doc__,\n        MODULE_NAME \".add_watch(emitter_thread, watch, callback, paths) -> None\\\n\\nAdds a watch into the event loop for the given emitter thread.\\n\\n\\\n:param emitter_thread:\\n\\\n    The emitter thread.\\n\\\n:param watch:\\n\\\n    The watch to add.\\n\\\n:param callback:\\n\\\n    The callback function to call when an event occurs.\\n\\n\\\n    Example::\\n\\n\\\n        def callback(paths, flags, ids):\\n\\\n            for path, flag, event_id in zip(paths, flags, ids):\\n\\\n                print(\\\"%d: %s=%ul\\\" % (event_id, path, flag))\\n\\\n:param paths:\\n\\\n    A list of paths to monitor.\\n\");\nstatic PyObject *\nwatchdog_add_watch(PyObject *self, PyObject *args)\n{\n    UNUSED(self);\n    FSEventStreamRef stream_ref = NULL;\n    StreamCallbackInfo *stream_callback_info_ref = NULL;\n    CFRunLoopRef run_loop_ref = NULL;\n    PyObject *emitter_thread = NULL;\n    PyObject *watch = NULL;\n    PyObject *paths_to_watch = NULL;\n    PyObject *python_callback = NULL;\n    PyObject *value = NULL;\n\n    /* Ensure all arguments are received. */\n    G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, \"OOOO:schedule\",\n                                          &emitter_thread, &watch,\n                                          &python_callback, &paths_to_watch));\n\n    /* Watch must not already be scheduled. */\n    if(PyDict_Contains(watch_to_stream, watch) == 1) {\n        PyErr_Format(PyExc_RuntimeError, \"Cannot add watch %S - it is already scheduled\", watch);\n        return NULL;\n    }\n\n    /* Create an instance of the callback information structure. */\n    stream_callback_info_ref = PyMem_New(StreamCallbackInfo, 1);\n    if(stream_callback_info_ref == NULL) {\n        PyErr_SetString(PyExc_SystemError, \"Failed allocating stream callback info\");\n        return NULL;\n    }\n\n    /* Create an FSEvent stream and\n     * Save the stream reference to the global watch-to-stream dictionary. */\n    stream_ref = watchdog_FSEventStreamCreate(stream_callback_info_ref,\n                                              paths_to_watch,\n                                              (FSEventStreamCallback) &watchdog_FSEventStreamCallback);\n    if (!stream_ref) {\n        PyMem_Del(stream_callback_info_ref);\n        PyErr_SetString(PyExc_RuntimeError, \"Failed creating fsevent stream\");\n        return NULL;\n    }\n    value = PyCapsule_New(stream_ref, NULL, watchdog_pycapsule_destructor);\n    if (!value || !PyCapsule_IsValid(value, NULL)) {\n        PyMem_Del(stream_callback_info_ref);\n        FSEventStreamInvalidate(stream_ref);\n        FSEventStreamRelease(stream_ref);\n        return NULL;\n    }\n    PyDict_SetItem(watch_to_stream, watch, value);\n\n    /* Get a reference to the runloop for the emitter thread\n     * or to the current runloop. */\n    int res = PyDict_GetItemRef(thread_to_run_loop, emitter_thread, &value);\n    if (res == 0)\n    {\n        run_loop_ref = CFRunLoopGetCurrent();\n    }\n    else if (res < 0) {\n        PyMem_Del(stream_callback_info_ref);\n        FSEventStreamInvalidate(stream_ref);\n        FSEventStreamRelease(stream_ref);\n        return NULL;\n    }\n    else\n    {\n        run_loop_ref = PyCapsule_GetPointer(value, NULL);\n    }\n\n    /* Schedule the stream with the obtained runloop. */\n    FSEventStreamScheduleWithRunLoop(stream_ref, run_loop_ref, kCFRunLoopDefaultMode);\n\n    /* Set the stream information for the callback.\n     * This data will be passed to our watchdog_FSEventStreamCallback function\n     * by the FSEvents API whenever an event occurs.\n     */\n    stream_callback_info_ref->python_callback = python_callback;\n    stream_callback_info_ref->stream_ref = stream_ref;\n    stream_callback_info_ref->run_loop_ref = run_loop_ref;\n    stream_callback_info_ref->thread_state = PyThreadState_Get();\n    Py_INCREF(python_callback);\n\n    /* Start the event stream. */\n    if (G_NOT(FSEventStreamStart(stream_ref)))\n    {\n        FSEventStreamInvalidate(stream_ref);\n        FSEventStreamRelease(stream_ref);\n        // There's no documentation on _why_ this might fail - \"it ought to always succeed\". But if it fails the\n        // documentation says to \"fall back to performing recursive scans of the directories [...] as appropriate\".\n        PyErr_SetString(PyExc_SystemError, \"Cannot start fsevents stream. Use a kqueue or polling observer instead.\");\n        Py_XDECREF(value);\n        return NULL;\n    }\n\n    Py_XDECREF(value);\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\n\nPyDoc_STRVAR(watchdog_read_events__doc__,\n             MODULE_NAME \".read_events(emitter_thread) -> None\\n\\\nBlocking function that runs an event loop associated with an emitter thread.\\n\\n\\\n:param emitter_thread:\\n\\\n    The emitter thread for which the event loop will be run.\\n\");\nstatic PyObject *\nwatchdog_read_events(PyObject *self, PyObject *args)\n{\n    UNUSED(self);\n    CFRunLoopRef run_loop_ref = NULL;\n    PyObject *emitter_thread = NULL;\n    PyObject *value = NULL;\n\n    G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, \"O:loop\", &emitter_thread));\n\n// PyEval_InitThreads() does nothing as of Python 3.7 and is deprecated in 3.9.\n// https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads\n#if PY_VERSION_HEX < 0x030700f0\n    PyEval_InitThreads();\n#endif\n\n    /* Allocate information and store thread state. */\n    int res = PyDict_GetItemRef(thread_to_run_loop, emitter_thread, &value);\n    if (res == 0)\n    {\n        run_loop_ref = CFRunLoopGetCurrent();\n        value = PyCapsule_New(run_loop_ref, NULL, watchdog_pycapsule_destructor);\n        PyDict_SetItem(thread_to_run_loop, emitter_thread, value);\n        Py_INCREF(emitter_thread);\n        Py_INCREF(value);\n    }\n    else if (res < 0) {\n        return NULL;\n    }\n\n    /* No timeout, block until events. */\n    Py_BEGIN_ALLOW_THREADS;\n    CFRunLoopRun();\n    Py_END_ALLOW_THREADS;\n\n    /* Clean up state information. */\n    if (PyDict_DelItem(thread_to_run_loop, emitter_thread) == 0)\n    {\n        Py_DECREF(emitter_thread);\n    } else {\n        Py_DECREF(value);\n        return NULL;\n    }\n\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\nPyDoc_STRVAR(watchdog_flush_events__doc__,\n        MODULE_NAME \".flush_events(watch) -> None\\n\\\nFlushes events for the watch.\\n\\n\\\n:param watch:\\n\\\n    The watch to flush.\\n\");\nstatic PyObject *\nwatchdog_flush_events(PyObject *self, PyObject *watch)\n{\n    UNUSED(self);\n    PyObject *value;\n    int res = PyDict_GetItemRef(watch_to_stream, watch, &value);\n    if (res < 0) {\n        return NULL;\n    }\n    if (res == 0) {\n        Py_INCREF(Py_None);\n        return Py_None;\n    }\n\n    FSEventStreamRef stream_ref = PyCapsule_GetPointer(value, NULL);\n\n    FSEventStreamFlushSync(stream_ref);\n\n    Py_DECREF(value);\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\nPyDoc_STRVAR(watchdog_remove_watch__doc__,\n        MODULE_NAME \".remove_watch(watch) -> None\\n\\\nRemoves a watch from the event loop.\\n\\n\\\n:param watch:\\n\\\n    The watch to remove.\\n\");\nstatic PyObject *\nwatchdog_remove_watch(PyObject *self, PyObject *watch)\n{\n    UNUSED(self);\n    PyObject *streamref_capsule;\n    int res = PyDict_GetItemRef(watch_to_stream, watch, &streamref_capsule);\n    if (res < 0) {\n        return NULL;\n    }\n    if (res == 0) {\n        // A watch might have been removed explicitly before, in which case we can simply early out.\n        Py_RETURN_NONE;\n    }\n    if (PyDict_DelItem(watch_to_stream, watch) == 0) {\n        FSEventStreamRef stream_ref = PyCapsule_GetPointer(streamref_capsule, NULL);\n\n        FSEventStreamStop(stream_ref);\n        FSEventStreamInvalidate(stream_ref);\n        FSEventStreamRelease(stream_ref);\n    } else {\n        // Found a valid entry in watch_to_stream earlier in this function, so\n        // there must be a race and another thread simultaneously removed the\n        // watch. Since the other thread already cleaned up, we just clear the\n        // KeyError. This should only be possible on the free-threaded build.\n        PyErr_Clear();\n    }\n\n    Py_DECREF(streamref_capsule);\n    Py_RETURN_NONE;\n}\n\nPyDoc_STRVAR(watchdog_stop__doc__,\n        MODULE_NAME \".stop(emitter_thread) -> None\\n\\\nStops running the event loop from the specified thread.\\n\\n\\\n:param emitter_thread:\\n\\\n    The thread for which the event loop will be stopped.\\n\");\nstatic PyObject *\nwatchdog_stop(PyObject *self, PyObject *emitter_thread)\n{\n    UNUSED(self);\n    PyObject *value;\n    int res = PyDict_GetItemRef(thread_to_run_loop, emitter_thread, &value);\n    if (res < 0) {\n        return NULL;\n    }\n    else if (res == 0) {\n      goto success;\n    }\n\n    CFRunLoopRef run_loop_ref = PyCapsule_GetPointer(value, NULL);\n    if (PyErr_Occurred()) {\n        Py_DECREF(value);\n        return NULL;\n    }\n\n    /* Stop the run loop. */\n    if (G_IS_NOT_NULL(run_loop_ref))\n    {\n        CFRunLoopStop(run_loop_ref);\n    }\n\n    Py_DECREF(value);\n\n success:\n    Py_INCREF(Py_None);\n    return Py_None;\n}\n\n\n/******************************************************************************\n * Module initialization.\n *****************************************************************************/\n\nPyDoc_STRVAR(watchdog_fsevents_module__doc__,\n             \"Low-level FSEvents Python/C API bridge.\");\n\nstatic PyMethodDef watchdog_fsevents_methods[] =\n{\n    {\"add_watch\",    watchdog_add_watch,    METH_VARARGS, watchdog_add_watch__doc__},\n    {\"read_events\",  watchdog_read_events,  METH_VARARGS, watchdog_read_events__doc__},\n    {\"flush_events\", watchdog_flush_events, METH_O,       watchdog_flush_events__doc__},\n    {\"remove_watch\", watchdog_remove_watch, METH_O,       watchdog_remove_watch__doc__},\n\n    /* Aliases for compatibility with macfsevents. */\n    {\"schedule\",     watchdog_add_watch,    METH_VARARGS, \"Alias for add_watch.\"},\n    {\"loop\",         watchdog_read_events,  METH_VARARGS, \"Alias for read_events.\"},\n    {\"unschedule\",   watchdog_remove_watch, METH_O,       \"Alias for remove_watch.\"},\n\n    {\"stop\",         watchdog_stop,         METH_O,       watchdog_stop__doc__},\n\n    {NULL, NULL, 0, NULL},\n};\n\n\n/**\n * Initialize the module globals.\n */\nstatic void\nwatchdog_module_init(void)\n{\n    thread_to_run_loop = PyDict_New();\n    watch_to_stream = PyDict_New();\n}\n\n\n/**\n * Adds various attributes to the Python module.\n *\n * :param module:\n *     A pointer to the Python module object to inject\n *     the attributes into.\n */\nstatic void\nwatchdog_module_add_attributes(PyObject *module)\n{\n    PyObject *version_tuple = Py_BuildValue(\"(iii)\",\n                                            WATCHDOG_VERSION_MAJOR,\n                                            WATCHDOG_VERSION_MINOR,\n                                            WATCHDOG_VERSION_BUILD);\n    PyModule_AddIntConstant(module,\n                            \"POLLIN\",\n                            kCFFileDescriptorReadCallBack);\n    PyModule_AddIntConstant(module,\n                            \"POLLOUT\",\n                            kCFFileDescriptorWriteCallBack);\n\n    /* Adds version information. */\n    PyModule_AddObject(module,\n                       \"__version__\",\n                       version_tuple);\n    PyModule_AddObject(module,\n                       \"version_string\",\n                       Py_BuildValue(\"s\", WATCHDOG_VERSION_STRING));\n}\n\nstatic struct PyModuleDef watchdog_fsevents_module = {\n    PyModuleDef_HEAD_INIT,\n    MODULE_NAME,\n    watchdog_fsevents_module__doc__,\n    -1,\n    watchdog_fsevents_methods,\n    NULL,  /* m_slots */\n    NULL,  /* m_traverse */\n    0,     /* m_clear */\n    NULL   /* m_free */\n};\n\n/**\n * Initialize the Python 3.x module.\n */\nPyMODINIT_FUNC\nPyInit__watchdog_fsevents(void){\n    G_RETURN_NULL_IF(PyType_Ready(&NativeEventType) < 0);\n    PyObject *module = PyModule_Create(&watchdog_fsevents_module);\n    G_RETURN_NULL_IF_NULL(module);\n    Py_INCREF(&NativeEventType);\n    if (PyModule_AddObject(module, \"NativeEvent\", (PyObject*)&NativeEventType) < 0) {\n        Py_DECREF(&NativeEventType);\n        Py_DECREF(module);\n        return NULL;\n    }\n    watchdog_module_add_attributes(module);\n    watchdog_module_init();\n    return module;\n}\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport gc\nimport os\nimport threading\nfrom functools import partial\n\nimport pytest\n\nfrom .utils import EventsChecker, ExpectEvent, Helper, P, StartWatching, TestEventQueue\n\n\n@pytest.fixture\ndef p(tmpdir, *args):\n    \"\"\"\n    Convenience function to join the temporary directory path\n    with the provided arguments.\n    \"\"\"\n    return partial(os.path.join, tmpdir)\n\n\n@pytest.fixture(autouse=True)\ndef _no_thread_leaks():\n    \"\"\"\n    Fail on thread leak.\n    We do not use pytest-threadleak because it is not reliable.\n    \"\"\"\n    old_thread_count = threading.active_count()\n    yield\n    gc.collect()  # Clear the stuff from other function-level fixtures\n    assert threading.active_count() == old_thread_count  # Only previously existing threads\n\n\n@pytest.fixture(autouse=True)\ndef _no_warnings(recwarn):\n    \"\"\"Fail on warning.\"\"\"\n\n    yield\n\n    warnings = []\n    for warning in recwarn:  # pragma: no cover\n        message = str(warning.message)\n        filename = warning.filename\n        if (\n            \"Not importing directory\" in message\n            or \"Using or importing the ABCs\" in message\n            or \"dns.hash module will be removed in future versions\" in message\n            or \"is still running\" in message\n            or \"eventlet\" in filename\n        ):\n            continue\n        warnings.append(f\"{warning.filename}:{warning.lineno} {warning.message}\")\n    assert not warnings, warnings\n\n\n@pytest.fixture(name=\"helper\")\ndef helper_fixture(tmpdir):\n    with contextlib.closing(Helper(tmp=os.fspath(tmpdir))) as helper:\n        yield helper\n\n\n@pytest.fixture(name=\"p\")\ndef p_fixture(helper: Helper) -> P:\n    return helper.joinpath\n\n\n@pytest.fixture(name=\"event_queue\")\ndef event_queue_fixture(helper: Helper) -> TestEventQueue:\n    return helper.event_queue\n\n\n@pytest.fixture(name=\"start_watching\")\ndef start_watching_fixture(helper: Helper) -> StartWatching:\n    return helper.start_watching\n\n\n@pytest.fixture(name=\"expect_event\")\ndef expect_event_fixture(helper: Helper) -> ExpectEvent:\n    return helper.expect_event\n\n\n@pytest.fixture(name=\"events_checker\")\ndef events_checker_fixture(helper: Helper) -> EventsChecker:\n    return helper.events_checker\n"
  },
  {
    "path": "tests/isolated/__init__.py",
    "content": ""
  },
  {
    "path": "tests/isolated/eventlet_observer_stops.py",
    "content": "if __name__ == \"__main__\":\n    import eventlet\n\n    eventlet.monkey_patch()\n\n    import signal\n    import sys\n    import tempfile\n\n    from watchdog.events import LoggingEventHandler\n    from watchdog.observers import Observer\n\n    with tempfile.TemporaryDirectory() as temp_dir:\n\n        def run_observer():\n            event_handler = LoggingEventHandler()\n            observer = Observer()\n            observer.schedule(event_handler, temp_dir)\n            observer.start()\n            eventlet.sleep(1)\n            observer.stop()\n\n        def on_alarm(signum, frame):\n            print(\"Observer.stop() never finished!\", file=sys.stderr)  # noqa: T201\n            sys.exit(1)\n\n        signal.signal(signal.SIGALRM, on_alarm)\n        signal.alarm(4)\n\n        thread = eventlet.spawn(run_observer)\n        thread.wait()\n"
  },
  {
    "path": "tests/isolated/eventlet_skip_repeat_queue.py",
    "content": "if __name__ == \"__main__\":\n    import eventlet\n\n    eventlet.monkey_patch()\n\n    from watchdog.utils.bricks import SkipRepeatsQueue\n\n    q = SkipRepeatsQueue(10)\n    q.put(\"A\")\n    q.put(\"A\")\n    q.put(\"A\")\n    q.put(\"A\")\n    q.put(\"B\")\n    q.put(\"A\")\n\n    value = q.get()\n    assert value == \"A\"\n    q.task_done()\n\n    assert q.unfinished_tasks == 2\n\n    value = q.get()\n    assert value == \"B\"\n    q.task_done()\n\n    assert q.unfinished_tasks == 1\n\n    value = q.get()\n    assert value == \"A\"\n    q.task_done()\n\n    assert q.empty()\n    assert q.unfinished_tasks == 0\n"
  },
  {
    "path": "tests/shell.py",
    "content": "\"\"\"\n:module: tests.shell\n:synopsis: Common shell operations for testing.\n:author: yesudeep@google.com (Yesudeep Mangalapilly)\n:author: Mickaël Schoentgen <contact@tiger-222.fr>\n\"\"\"\n\nfrom __future__ import annotations\n\nimport errno\nimport os\nimport os.path\nimport shutil\nimport tempfile\nimport time\n\n\ndef cd(path):\n    os.chdir(path)\n\n\ndef pwd():\n    return os.getcwd()\n\n\ndef mkfile(path):\n    \"\"\"Creates a file\"\"\"\n    with open(path, \"ab\"):\n        pass\n\n\ndef mkdir(path, *, parents=False):\n    \"\"\"Creates a directory (optionally also creates all the parent directories\n    in the path).\"\"\"\n    if parents:\n        try:\n            os.makedirs(path)\n        except OSError as e:\n            if e.errno != errno.EEXIST:\n                raise\n    else:\n        os.mkdir(path)\n\n\ndef symlink(source, destination, *, target_is_directory: bool = False):\n    os.symlink(source, destination, target_is_directory=target_is_directory)\n\n\ndef rm(path, *, recursive=False):\n    \"\"\"Deletes files or directories.\"\"\"\n    if os.path.isdir(path):\n        if recursive:\n            shutil.rmtree(path)\n        else:\n            raise OSError(errno.EISDIR, os.strerror(errno.EISDIR), path)\n    else:\n        os.remove(path)\n\n\ndef touch(path, times=None):\n    \"\"\"Updates the modified timestamp of a file or directory.\"\"\"\n    if os.path.isdir(path):\n        os.utime(path, times)\n    else:\n        with open(path, \"ab\"):\n            os.utime(path, times)\n\n\ndef truncate(path):\n    \"\"\"Truncates a file.\"\"\"\n    with open(path, \"wb\"):\n        os.utime(path, None)\n\n\ndef mv(src_path, dest_path):\n    \"\"\"Moves files or directories.\"\"\"\n    try:\n        os.rename(src_path, dest_path)\n    except OSError:\n        # this will happen on windows\n        os.remove(dest_path)\n        os.rename(src_path, dest_path)\n\n\ndef mkdtemp():\n    return tempfile.mkdtemp()\n\n\ndef ls(path=\".\"):\n    return os.listdir(path)\n\n\ndef msize(path):\n    \"\"\"Modify the file size without updating the modified time.\"\"\"\n    with open(path, \"w\") as w:\n        w.write(\"\")\n    os.utime(path, (0, 0))\n    time.sleep(0.4)\n    with open(path, \"w\") as w:\n        w.write(\"0\")\n    os.utime(path, (0, 0))\n\n\ndef mount_tmpfs(path):\n    os.system(f\"sudo mount -t tmpfs none {path}\")\n\n\ndef unmount(path):\n    os.system(f\"sudo umount {path}\")\n"
  },
  {
    "path": "tests/test_0_watchmedo.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport os\nimport sys\nimport time\nfrom unittest.mock import patch\n\nimport pytest\n\n# Skip if import PyYAML failed. PyYAML missing possible because\n# watchdog installed without watchmedo. See Installation section\n# in README.rst\nyaml = pytest.importorskip(\"yaml\")\n\nfrom yaml.constructor import ConstructorError  # noqa: E402\nfrom yaml.scanner import ScannerError  # noqa: E402\n\nfrom watchdog import watchmedo  # noqa: E402\nfrom watchdog.events import FileModifiedEvent, FileOpenedEvent  # noqa: E402\nfrom watchdog.tricks import AutoRestartTrick, LoggerTrick, ShellCommandTrick  # noqa: E402\nfrom watchdog.utils import WatchdogShutdownError, platform  # noqa: E402\n\n\ndef test_load_config_valid(tmpdir):\n    \"\"\"Verifies the load of a valid yaml file\"\"\"\n\n    yaml_file = os.path.join(tmpdir, \"config_file.yaml\")\n    with open(yaml_file, \"w\") as f:\n        f.write(\"one: value\\ntwo:\\n- value1\\n- value2\\n\")\n\n    config = watchmedo.load_config(yaml_file)\n    assert isinstance(config, dict)\n    assert \"one\" in config\n    assert \"two\" in config\n    assert isinstance(config[\"two\"], list)\n    assert config[\"one\"] == \"value\"\n    assert config[\"two\"] == [\"value1\", \"value2\"]\n\n\ndef test_load_config_invalid(tmpdir):\n    \"\"\"Verifies if safe load avoid the execution\n    of untrusted code inside yaml files\"\"\"\n\n    critical_dir = os.path.join(tmpdir, \"critical\")\n    yaml_file = os.path.join(tmpdir, \"tricks_file.yaml\")\n    with open(yaml_file, \"w\") as f:\n        content = f'one: value\\nrun: !!python/object/apply:os.system [\"mkdir {critical_dir}\"]\\n'\n        f.write(content)\n\n    # PyYAML get_single_data() raises different exceptions for Linux and Windows\n    with pytest.raises((ConstructorError, ScannerError)):\n        watchmedo.load_config(yaml_file)\n\n    assert not os.path.exists(critical_dir)\n\n\ndef make_dummy_script(tmpdir, n=10):\n    script = os.path.join(tmpdir, f\"auto-test-{n}.py\")\n    with open(script, \"w\") as f:\n        f.write('import time\\nfor i in range(%d):\\n\\tprint(\"+++++ %%d\" %% i, flush=True)\\n\\ttime.sleep(1)\\n' % n)\n    return script\n\n\ndef test_kill_auto_restart(tmpdir, capfd):\n    script = make_dummy_script(tmpdir)\n    a = AutoRestartTrick([sys.executable, script])\n    a.start()\n    time.sleep(3)\n    a.stop()\n    cap = capfd.readouterr()\n    assert \"+++++ 0\" in cap.out\n    assert \"+++++ 9\" not in cap.out  # we killed the subprocess before the end\n    # in windows we seem to lose the subprocess stderr\n    # assert 'KeyboardInterrupt' in cap.err\n\n\ndef test_shell_command_wait_for_completion(tmpdir, capfd):\n    script = make_dummy_script(tmpdir, n=1)\n    command = f\"{sys.executable} {script}\"\n    trick = ShellCommandTrick(command, wait_for_process=True)\n    assert not trick.is_process_running()\n    start_time = time.monotonic()\n    trick.on_any_event(FileModifiedEvent(\"foo/bar.baz\"))\n    elapsed = time.monotonic() - start_time\n    assert not trick.is_process_running()\n    assert elapsed >= 1\n\n\ndef test_shell_command_subprocess_termination_nowait(tmpdir):\n    script = make_dummy_script(tmpdir, n=1)\n    command = f\"{sys.executable} {script}\"\n    trick = ShellCommandTrick(command, wait_for_process=False)\n    assert not trick.is_process_running()\n    trick.on_any_event(FileModifiedEvent(\"foo/bar.baz\"))\n    assert trick.is_process_running()\n    time.sleep(5)\n    assert not trick.is_process_running()\n\n\ndef test_shell_command_subprocess_termination_not_happening_on_file_opened_event(\n    tmpdir,\n):\n    # FIXME: see issue #949, and find a way to better handle that scenario\n    script = make_dummy_script(tmpdir, n=1)\n    command = f\"{sys.executable} {script}\"\n    trick = ShellCommandTrick(command, wait_for_process=False)\n    assert not trick.is_process_running()\n    trick.on_any_event(FileOpenedEvent(\"foo/bar.baz\"))\n    assert not trick.is_process_running()\n    time.sleep(5)\n    assert not trick.is_process_running()\n\n\ndef test_auto_restart_not_happening_on_file_opened_event(tmpdir, capfd):\n    # FIXME: see issue #949, and find a way to better handle that scenario\n    script = make_dummy_script(tmpdir, n=2)\n    trick = AutoRestartTrick([sys.executable, script])\n    trick.start()\n    time.sleep(1)\n    trick.on_any_event(FileOpenedEvent(\"foo/bar.baz\"))\n    trick.on_any_event(FileOpenedEvent(\"foo/bar2.baz\"))\n    trick.on_any_event(FileOpenedEvent(\"foo/bar3.baz\"))\n    time.sleep(1)\n    trick.stop()\n    cap = capfd.readouterr()\n    assert cap.out.splitlines(keepends=False).count(\"+++++ 0\") == 1\n    assert trick.restart_count == 0\n\n\ndef test_auto_restart_on_file_change(tmpdir, capfd):\n    \"\"\"Simulate changing 3 files.\n\n    Expect 3 restarts.\n    \"\"\"\n    script = make_dummy_script(tmpdir, n=2)\n    trick = AutoRestartTrick([sys.executable, script])\n    trick.start()\n    time.sleep(1)\n    trick.on_any_event(FileModifiedEvent(\"foo/bar.baz\"))\n    trick.on_any_event(FileModifiedEvent(\"foo/bar2.baz\"))\n    trick.on_any_event(FileModifiedEvent(\"foo/bar3.baz\"))\n    time.sleep(1)\n    trick.stop()\n    cap = capfd.readouterr()\n    assert cap.out.splitlines(keepends=False).count(\"+++++ 0\") >= 2\n    assert trick.restart_count == 3\n\n\n@pytest.mark.xfail(\n    condition=platform.is_darwin() or platform.is_windows() or sys.implementation.name == \"pypy\",\n    reason=\"known to be problematic, see #973\",\n)\ndef test_auto_restart_on_file_change_debounce(tmpdir, capfd):\n    \"\"\"Simulate changing 3 files quickly and then another change later.\n\n    Expect 2 restarts due to debouncing.\n    \"\"\"\n    script = make_dummy_script(tmpdir, n=2)\n    trick = AutoRestartTrick([sys.executable, script], debounce_interval_seconds=0.5)\n    trick.start()\n    time.sleep(1)\n    trick.on_any_event(FileModifiedEvent(\"foo/bar.baz\"))\n    trick.on_any_event(FileModifiedEvent(\"foo/bar2.baz\"))\n    time.sleep(0.1)\n    trick.on_any_event(FileModifiedEvent(\"foo/bar3.baz\"))\n    time.sleep(1)\n    trick.on_any_event(FileModifiedEvent(\"foo/bar.baz\"))\n    time.sleep(1)\n    trick.stop()\n    cap = capfd.readouterr()\n    assert cap.out.splitlines(keepends=False).count(\"+++++ 0\") == 3\n    assert trick.restart_count == 2\n\n\n@pytest.mark.flaky(max_runs=5, min_passes=1)\n@pytest.mark.parametrize(\n    \"restart_on_command_exit\",\n    [\n        True,\n        pytest.param(\n            False,\n            marks=pytest.mark.xfail(\n                condition=platform.is_darwin() or platform.is_windows(),\n                reason=\"known to be problematic, see #972\",\n            ),\n        ),\n    ],\n)\ndef test_auto_restart_subprocess_termination(tmpdir, capfd, restart_on_command_exit):\n    \"\"\"Run auto-restart with a script that terminates in about 2 seconds.\n\n    After 5 seconds, expect it to have been restarted at least once.\n    \"\"\"\n    script = make_dummy_script(tmpdir, n=2)\n    trick = AutoRestartTrick([sys.executable, script], restart_on_command_exit=restart_on_command_exit)\n    trick.start()\n    time.sleep(5)\n    trick.stop()\n    cap = capfd.readouterr()\n    if restart_on_command_exit:\n        assert cap.out.splitlines(keepends=False).count(\"+++++ 0\") > 1\n        assert trick.restart_count >= 1\n    else:\n        assert cap.out.splitlines(keepends=False).count(\"+++++ 0\") == 1\n        assert trick.restart_count == 0\n\n\ndef test_auto_restart_arg_parsing_basic():\n    args = watchmedo.cli.parse_args([\"auto-restart\", \"-d\", \".\", \"--recursive\", \"--debug-force-polling\", \"cmd\"])\n    assert args.func is watchmedo.auto_restart\n    assert args.command == \"cmd\"\n    assert args.directories == [\".\"]\n    assert args.recursive\n    assert args.debug_force_polling\n\n\ndef test_auto_restart_arg_parsing():\n    args = watchmedo.cli.parse_args(\n        [\n            \"auto-restart\",\n            \"-d\",\n            \".\",\n            \"--kill-after\",\n            \"12.5\",\n            \"--debounce-interval=0.2\",\n            \"cmd\",\n        ]\n    )\n    assert args.func is watchmedo.auto_restart\n    assert args.command == \"cmd\"\n    assert args.directories == [\".\"]\n    assert args.kill_after == pytest.approx(12.5)\n    assert args.debounce_interval == pytest.approx(0.2)\n\n\ndef test_auto_restart_events_echoed(tmpdir, caplog):\n    script = make_dummy_script(tmpdir, n=2)\n\n    with caplog.at_level(logging.INFO):\n        trick = AutoRestartTrick([sys.executable, script])\n        trick.on_any_event(FileOpenedEvent(\"foo/bar.baz\"))\n        trick.on_any_event(FileOpenedEvent(\"foo/bar2.baz\"))\n        trick.on_any_event(FileOpenedEvent(\"foo/bar3.baz\"))\n\n    records = [record.getMessage().strip() for record in caplog.get_records(when=\"call\")]\n    assert records == [\n        \"on_any_event(self=<AutoRestartTrick>, event=FileOpenedEvent(src_path='foo/bar.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n        \"on_any_event(self=<AutoRestartTrick>, event=FileOpenedEvent(src_path='foo/bar2.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n        \"on_any_event(self=<AutoRestartTrick>, event=FileOpenedEvent(src_path='foo/bar3.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n    ]\n\n\ndef test_logger_events_echoed(caplog):\n    with caplog.at_level(logging.INFO):\n        trick = LoggerTrick()\n        trick.on_any_event(FileOpenedEvent(\"foo/bar.baz\"))\n        trick.on_any_event(FileOpenedEvent(\"foo/bar2.baz\"))\n        trick.on_any_event(FileOpenedEvent(\"foo/bar3.baz\"))\n\n    records = [record.getMessage().strip() for record in caplog.get_records(when=\"call\")]\n    assert records == [\n        \"on_any_event(self=<LoggerTrick>, event=FileOpenedEvent(src_path='foo/bar.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n        \"on_any_event(self=<LoggerTrick>, event=FileOpenedEvent(src_path='foo/bar2.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n        \"on_any_event(self=<LoggerTrick>, event=FileOpenedEvent(src_path='foo/bar3.baz', dest_path='', event_type='opened', is_directory=False, is_synthetic=False))\",  # noqa: E501\n    ]\n\n\ndef test_shell_command_arg_parsing():\n    args = watchmedo.cli.parse_args([\"shell-command\", \"--command='cmd'\"])\n    assert args.command == \"'cmd'\"\n\n\n@pytest.mark.parametrize(\"cmdline\", [[\"auto-restart\", \"-d\", \".\", \"cmd\"], [\"log\", \".\"]])\n@pytest.mark.parametrize(\n    \"verbosity\",\n    [\n        ([], \"WARNING\"),\n        ([\"-q\"], \"ERROR\"),\n        ([\"--quiet\"], \"ERROR\"),\n        ([\"-v\"], \"INFO\"),\n        ([\"--verbose\"], \"INFO\"),\n        ([\"-vv\"], \"DEBUG\"),\n        ([\"-v\", \"-v\"], \"DEBUG\"),\n        ([\"--verbose\", \"-v\"], \"DEBUG\"),\n    ],\n)\ndef test_valid_verbosity(cmdline, verbosity):\n    (verbosity_cmdline_args, expected_log_level) = verbosity\n    cmd = [cmdline[0], *verbosity_cmdline_args, *cmdline[1:]]\n    args = watchmedo.cli.parse_args(cmd)\n    log_level = watchmedo._get_log_level_from_args(args)  # noqa: SLF001\n    assert log_level == expected_log_level\n\n\n@pytest.mark.parametrize(\"cmdline\", [[\"auto-restart\", \"-d\", \".\", \"cmd\"], [\"log\", \".\"]])\n@pytest.mark.parametrize(\n    \"verbosity_cmdline_args\",\n    [\n        [\"-q\", \"-v\"],\n        [\"-v\", \"-q\"],\n        [\"-qq\"],\n        [\"-q\", \"-q\"],\n        [\"--quiet\", \"--quiet\"],\n        [\"--quiet\", \"-q\"],\n        [\"-vvv\"],\n        [\"-vvvv\"],\n        [\"-v\", \"-v\", \"-v\"],\n        [\"-vv\", \"-v\"],\n        [\"--verbose\", \"-vv\"],\n    ],\n)\ndef test_invalid_verbosity(cmdline, verbosity_cmdline_args):\n    cmd = [cmdline[0], *verbosity_cmdline_args, *cmdline[1:]]\n    with pytest.raises((watchmedo.LogLevelError, SystemExit)):  # noqa: PT012\n        args = watchmedo.cli.parse_args(cmd)\n        watchmedo._get_log_level_from_args(args)  # noqa: SLF001\n\n\n@pytest.mark.parametrize(\"command\", [\"tricks-from\", \"tricks\"])\ndef test_tricks_from_file(command, tmp_path):\n    tricks_file = tmp_path / \"tricks.yaml\"\n    tricks_file.write_text(\n        \"\"\"\ntricks:\n- watchdog.tricks.LoggerTrick:\n    patterns: [\"**/*.py\", \"**/*.js\"]\n\"\"\"\n    )\n    args = watchmedo.cli.parse_args([command, str(tricks_file)])\n\n    checkpoint = False\n\n    def mocked_sleep(_):\n        nonlocal checkpoint\n        checkpoint = True\n        raise WatchdogShutdownError\n\n    with patch(\"time.sleep\", mocked_sleep):\n        watchmedo.tricks_from(args)\n    assert checkpoint\n"
  },
  {
    "path": "tests/test_delayed_queue.py",
    "content": "from __future__ import annotations\n\nfrom time import time\n\nimport pytest\n\nfrom watchdog.utils.delayed_queue import DelayedQueue\n\n\n@pytest.mark.flaky(max_runs=5, min_passes=1)\ndef test_delayed_get():\n    q = DelayedQueue[str](2)\n    q.put(\"\", delay=True)\n    inserted = time()\n    q.get()\n    elapsed = time() - inserted\n    # 2.10 instead of 2.05 for slow macOS slaves on Travis\n    assert 2.10 > elapsed > 1.99\n\n\n@pytest.mark.flaky(max_runs=5, min_passes=1)\ndef test_nondelayed_get():\n    q = DelayedQueue[str](2)\n    q.put(\"\")\n    inserted = time()\n    q.get()\n    elapsed = time() - inserted\n    # Far less than 1 second\n    assert elapsed < 1\n"
  },
  {
    "path": "tests/test_echo.py",
    "content": "from typing import Any\n\nimport pytest\n\nfrom watchdog.utils import echo\n\n\n@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    [\n        ((\"x\", (1, 2, 3)), \"x=(1, 2, 3)\"),\n    ],\n)\ndef test_format_arg_value(value: tuple[str, tuple[Any, ...]], expected: str) -> None:\n    assert echo.format_arg_value(value) == expected\n"
  },
  {
    "path": "tests/test_emitter.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport os\nimport stat\nimport time\nfrom queue import Empty\nfrom typing import TYPE_CHECKING\n\nimport pytest\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileClosedEvent,\n    FileClosedNoWriteEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    FileOpenedEvent,\n)\nfrom watchdog.utils import platform\n\nfrom .shell import mkdir, mkfile, mv, rm, touch\n\nif TYPE_CHECKING:\n    from .utils import EventsChecker, P, StartWatching, TestEventQueue\n\nlogging.basicConfig(level=logging.DEBUG)\nlogger = logging.getLogger(__name__)\n\n\nif platform.is_darwin():\n    # enable more verbose logs\n    fsevents_logger = logging.getLogger(\"fsevents\")\n    fsevents_logger.setLevel(logging.DEBUG)\n\n\ndef test_create(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    start_watching()\n    open(p(\"a\"), \"a\").close()\n\n    with events_checker() as ec:\n        if platform.is_darwin():\n            ec.add(FileCreatedEvent, \"a\")\n        else:\n            ec.add(FileCreatedEvent, \"a\")\n            if not platform.is_windows():\n                ec.add(DirModifiedEvent, \".\")\n            if platform.is_linux():\n                ec.add(FileOpenedEvent, \"a\")\n                ec.add(FileClosedEvent, \"a\")\n            if not platform.is_windows():\n                ec.add(DirModifiedEvent, \".\")\n\n\n@pytest.mark.skipif(not platform.is_linux(), reason=\"FileClosed*Event only supported in GNU/Linux\")\ndef test_closed(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    with open(p(\"a\"), \"a\"):\n        start_watching()\n\n    with events_checker() as ec:\n        # After file creation/open in append mode\n        ec.add(FileClosedEvent, \"a\")\n\n        ec.add(DirModifiedEvent, \".\")\n\n    # After read-only, only IN_CLOSE_NOWRITE is emitted\n    open(p(\"a\")).close()\n\n    with events_checker() as ec:\n        ec.add(FileOpenedEvent, \"a\")\n        ec.add(FileClosedNoWriteEvent, \"a\")\n\n\n@pytest.mark.skipif(\n    platform.is_darwin() or platform.is_windows(),\n    reason=\"Windows and macOS enforce proper encoding\",\n)\ndef test_create_wrong_encoding(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    start_watching()\n    filename = \"a_\\udce4\"\n    open(p(filename), \"a\").close()\n\n    with events_checker() as ec:\n        ec.add(FileCreatedEvent, filename)\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \".\")\n            ec.add(FileOpenedEvent, filename)\n            ec.add(FileClosedEvent, filename)\n            ec.add(DirModifiedEvent, \".\")\n\n\ndef test_delete(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkfile(p(\"a\"))\n\n    start_watching()\n    rm(p(\"a\"))\n\n    with events_checker() as ec:\n        if platform.is_darwin():\n            ec.add(DirModifiedEvent, \".\")\n        else:\n            ec.add(FileDeletedEvent, \"a\")\n            if not platform.is_windows():\n                ec.add(DirModifiedEvent, \".\")\n\n\ndef test_modify(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkfile(p(\"a\"))\n    start_watching()\n\n    touch(p(\"a\"))\n\n    with events_checker() as ec:\n        if platform.is_darwin():\n            ec.add(FileModifiedEvent, \"a\")\n        else:\n            if platform.is_linux():\n                ec.add(FileOpenedEvent, \"a\")\n            ec.add(FileModifiedEvent, \"a\")\n            if platform.is_linux():\n                ec.add(FileClosedEvent, \"a\")\n            if not platform.is_windows():\n                ec.add(DirModifiedEvent, \".\")\n\n\ndef test_chmod(p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker) -> None:\n    mkfile(p(\"a\"))\n    start_watching()\n\n    # Note: We use S_IREAD here because chmod on Windows only\n    # allows setting the read-only flag.\n    os.chmod(p(\"a\"), stat.S_IREAD)\n\n    with events_checker() as ec:\n        ec.add(FileModifiedEvent, \"a\")\n\n    # Reset permissions to allow cleanup.\n    os.chmod(p(\"a\"), stat.S_IWRITE)\n\n\ndef test_move_simple(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"a\"))\n    start_watching()\n\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    with events_checker() as ec:\n        if not platform.is_windows():\n            ec.add(FileMovedEvent, \"dir1/a\", dest_path=\"dir2/b\")\n            ec.add(DirModifiedEvent, \"dir1\")\n            ec.add(DirModifiedEvent, \"dir2\")\n        else:\n            ec.add(FileDeletedEvent, \"dir1/a\")\n            ec.add(FileCreatedEvent, \"dir2/b\")\n            ec.add(DirModifiedEvent, \"dir2\")\n\n\ndef test_case_change(\n    p: P,\n    event_queue: TestEventQueue,\n    start_watching: StartWatching,\n    events_checker: EventsChecker,\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"file\"))\n    start_watching()\n\n    mv(p(\"dir1\", \"file\"), p(\"dir2\", \"FILE\"))\n\n    with events_checker() as ec:\n        if not platform.is_windows():\n            ec.add(FileMovedEvent, \"dir1/file\", dest_path=\"dir2/FILE\")\n            ec.add(DirModifiedEvent, \"dir1\")\n            ec.add(DirModifiedEvent, \"dir2\")\n        else:\n            ec.add(FileDeletedEvent, \"dir1/file\")\n            ec.add(FileCreatedEvent, \"dir2/FILE\")\n            ec.add(DirModifiedEvent, \"dir2\")\n\n\ndef test_move_to(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"a\"))\n    start_watching(path=p(\"dir2\"))\n\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    with events_checker() as ec:\n        ec.add(FileCreatedEvent, \"dir2/b\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \"dir2\")\n\n\n@pytest.mark.skipif(not platform.is_linux(), reason=\"InotifyFullEmitter only supported in Linux\")\ndef test_move_to_full(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"a\"))\n    start_watching(path=p(\"dir2\"), use_full_emitter=True)\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    with events_checker() as ec:\n        # The src_path should be blank since the path was not watched\n        ec.add(FileMovedEvent, \"\", dest_path=\"dir2/b\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \"dir2\")\n\n\ndef test_move_from(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"a\"))\n    start_watching(path=p(\"dir1\"))\n\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    with events_checker() as ec:\n        ec.add(FileDeletedEvent, \"dir1/a\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \"dir1\")\n\n\n@pytest.mark.skipif(not platform.is_linux(), reason=\"InotifyFullEmitter only supported in Linux\")\ndef test_move_from_full(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    mkfile(p(\"dir1\", \"a\"))\n    start_watching(path=p(\"dir1\"), use_full_emitter=True)\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    with events_checker() as ec:\n        # dest_path should be blank since not watched\n        ec.add(FileMovedEvent, \"dir1/a\", dest_path=\"\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \"dir1\")\n\n\ndef test_separate_consecutive_moves(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    mkfile(p(\"dir1\", \"a\"))\n    mkfile(p(\"b\"))\n    start_watching(path=p(\"dir1\"))\n    mv(p(\"dir1\", \"a\"), p(\"c\"))\n    mv(p(\"b\"), p(\"dir1\", \"d\"))\n\n    with events_checker() as ec:\n        if not platform.is_windows():\n            ec.add(FileDeletedEvent, \"dir1/a\")\n            ec.add(DirModifiedEvent, \"dir1\")\n            ec.add(FileCreatedEvent, \"dir1/d\")\n            ec.add(DirModifiedEvent, \"dir1\")\n        else:\n            ec.add(FileDeletedEvent, \"dir1/a\")\n            ec.add(FileCreatedEvent, \"dir1/d\")\n\n\n@pytest.mark.skipif(platform.is_bsd(), reason=\"BSD create another set of events for this test\")\ndef test_delete_self(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\"))\n    emitter = start_watching(path=p(\"dir1\"))\n    rm(p(\"dir1\"), recursive=True)\n\n    with events_checker() as ec:\n        ec.add(DirDeletedEvent, \"dir1\")\n\n    emitter.join(5)\n    assert not emitter.is_alive()\n\n\n@pytest.mark.skipif(\n    platform.is_windows() or platform.is_bsd(),\n    reason=\"Windows|BSD create another set of events for this test\",\n)\ndef test_fast_subdirectory_creation_deletion(\n    p: P, events_checker: EventsChecker, event_queue: TestEventQueue, start_watching: StartWatching\n) -> None:\n    root_dir = p(\"dir1\")\n    sub_dir = p(\"dir1\", \"subdir1\")\n    times = 30\n    mkdir(root_dir)\n    start_watching(path=root_dir)\n    for _ in range(times):\n        mkdir(sub_dir)\n        rm(sub_dir, recursive=True)\n        time.sleep(0.1)  # required for macOS emitter to catch up with us\n\n    with events_checker() as ec:\n        for _ in range(times):\n            ec.add(DirCreatedEvent, \"dir1/subdir1\")\n            ec.add(DirModifiedEvent, \"dir1\")\n            ec.add(DirDeletedEvent, \"dir1/subdir1\")\n            ec.add(DirModifiedEvent, \"dir1\")\n\n\ndef test_passing_unicode_should_give_unicode(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    start_watching(path=str(p()))\n    mkfile(p(\"a\"))\n    event = event_queue.get(timeout=5)[0]\n    assert isinstance(event.src_path, str)\n\n\n@pytest.mark.skipif(\n    platform.is_windows(),\n    reason=\"Windows ReadDirectoryChangesW supports only unicode for paths.\",\n)\ndef test_passing_bytes_should_give_bytes(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    start_watching(path=p().encode())\n    mkfile(p(\"a\"))\n    event = event_queue.get(timeout=5)[0]\n    assert isinstance(event.src_path, bytes)\n\n\ndef test_recursive_on(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    mkdir(p(\"dir1\", \"dir2\", \"dir3\"), parents=True)\n    start_watching()\n    touch(p(\"dir1\", \"dir2\", \"dir3\", \"a\"))\n\n    with events_checker() as ec:\n        if platform.is_darwin():\n            ec.add(FileCreatedEvent, \"dir1/dir2/dir3/a\")\n            ec.add(DirModifiedEvent, \"dir1/dir2/dir3\")\n            ec.add(FileModifiedEvent, \"dir1/dir2/dir3/a\")\n        elif platform.is_windows():\n            ec.add(FileCreatedEvent, \"dir1/dir2/dir3/a\")\n            ec.add(FileModifiedEvent, \"dir1/dir2/dir3/a\")\n        else:\n            ec.add(FileCreatedEvent, \"dir1/dir2/dir3/a\")\n            ec.add(DirModifiedEvent, \"dir1/dir2/dir3\")\n\n            if platform.is_linux():\n                ec.add(FileOpenedEvent, \"dir1/dir2/dir3/a\")\n\n            if not platform.is_bsd():\n                ec.add(FileModifiedEvent, \"dir1/dir2/dir3/a\")\n\n            if platform.is_linux():\n                ec.add(FileClosedEvent, \"dir1/dir2/dir3/a\")\n\n            ec.add(DirModifiedEvent, \"dir1/dir2/dir3\")\n\n\ndef check_empty_queue(event_queue: TestEventQueue) -> None:\n    events = []\n    while True:\n        try:\n            _ = event_queue.get(timeout=4)\n        except Empty:\n            break\n    assert not events, \"queue was not empty\"\n\n\ndef test_recursive_off(\n    p: P,\n    event_queue: TestEventQueue,\n    start_watching: StartWatching,\n    events_checker: EventsChecker,\n) -> None:\n    mkdir(p(\"dir1\"))\n    start_watching(recursive=False)\n    touch(p(\"dir1\", \"a\"))\n\n    check_empty_queue(event_queue)\n\n    mkfile(p(\"b\"))\n\n    with events_checker() as ec:\n        if platform.is_darwin():\n            ec.add(FileCreatedEvent, \"b\")\n            ec.add(DirModifiedEvent, \".\")\n        else:\n            ec.add(FileCreatedEvent, \"b\")\n            if not platform.is_windows():\n                ec.add(DirModifiedEvent, \".\")\n                if platform.is_linux():\n                    ec.add(FileOpenedEvent, \"b\")\n                    ec.add(FileClosedEvent, \"b\")\n                ec.add(DirModifiedEvent, \".\")\n\n    # currently limiting these additional events to macOS only, see https://github.com/gorakhargosh/watchdog/pull/779\n    if platform.is_darwin():\n        mkdir(p(\"dir1\", \"dir2\"))\n        check_empty_queue(event_queue)\n        mkfile(p(\"dir1\", \"dir2\", \"somefile\"))\n        check_empty_queue(event_queue)\n\n        mkdir(p(\"dir3\"))\n        with events_checker() as ec:\n            ec.add(DirModifiedEvent, \".\")  # the contents of the parent directory changed\n\n        mv(p(\"dir1\", \"dir2\", \"somefile\"), p(\"somefile\"))\n        with events_checker() as ec:\n            ec.add(FileMovedEvent, \"dir1/dir2/somefile\", dest_path=\"somefile\")\n            ec.add(DirModifiedEvent, \".\")\n\n        mv(p(\"dir1\", \"dir2\"), p(\"dir2\"))\n        with events_checker() as ec:\n            ec.add(DirMovedEvent, \"dir1/dir2\", dest_path=\"dir2\")\n            ec.add(DirModifiedEvent, \".\")\n\n\ndef test_renaming_top_level_directory(\n    p: P,\n    event_queue: TestEventQueue,\n    start_watching: StartWatching,\n    events_checker: EventsChecker,\n) -> None:\n    start_watching()\n\n    mkdir(p(\"a\"))\n    with events_checker() as ec:\n        ec.add(DirCreatedEvent, \"a\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \".\")\n\n    mkdir(p(\"a\", \"b\"))\n    with events_checker() as ec:\n        ec.add(DirCreatedEvent, \"a/b\")\n        if not platform.is_windows():\n            ec.add(DirModifiedEvent, \"a\")\n\n    mv(p(\"a\"), p(\"a2\"))\n    with events_checker() as ec:\n        if platform.is_windows():\n            ec.add(DirMovedEvent, \"a\", dest_path=\"a2\")\n            ec.add(DirMovedEvent, \"a/b\", dest_path=\"a2/b\")\n            ec.add(DirModifiedEvent, \"a2\")\n        else:\n            ec.add(DirMovedEvent, \"a\", dest_path=\"a2\")\n            ec.add(DirModifiedEvent, \".\")\n            ec.add(DirModifiedEvent, \".\")\n            ec.add(DirMovedEvent, \"a/b\", dest_path=\"a2/b\")\n\n    open(p(\"a2\", \"b\", \"c\"), \"a\").close()\n\n    with events_checker() as ec:\n        ec.add(FileCreatedEvent, \"a2/b/c\")\n        if platform.is_linux():\n            ec.add(DirModifiedEvent, \"a2/b\")\n            ec.add(FileOpenedEvent, \"a2/b/c\")\n            ec.add(FileClosedEvent, \"a2/b/c\")\n            ec.add(DirModifiedEvent, \"a2/b\")\n\n\n@pytest.mark.skipif(platform.is_windows(), reason=\"Windows create another set of events for this test\")\ndef test_move_nested_subdirectories(\n    p: P,\n    event_queue: TestEventQueue,\n    start_watching: StartWatching,\n    events_checker: EventsChecker,\n) -> None:\n    mkdir(p(\"dir1/dir2/dir3\"), parents=True)\n    mkfile(p(\"dir1/dir2/dir3\", \"a\"))\n    start_watching()\n    mv(p(\"dir1/dir2\"), p(\"dir2\"))\n\n    with events_checker() as ec:\n        ec.add(DirMovedEvent, \"dir1/dir2\", dest_path=\"dir2\")\n        ec.add(DirModifiedEvent, \"dir1\")\n        ec.add(DirModifiedEvent, \".\")\n        ec.add(DirMovedEvent, \"dir1/dir2/dir3\", dest_path=\"dir2/dir3\")\n        ec.add(FileMovedEvent, \"dir1/dir2/dir3/a\", dest_path=\"dir2/dir3/a\")\n\n    touch(p(\"dir2/dir3\", \"a\"))\n\n    with events_checker() as ec:\n        if platform.is_linux():\n            ec.add(FileOpenedEvent, \"dir2/dir3/a\")\n        ec.add(FileModifiedEvent, \"dir2/dir3/a\")\n        if platform.is_linux():\n            ec.add(FileClosedEvent, \"dir2/dir3/a\")\n            ec.add(DirModifiedEvent, \"dir2/dir3\")\n\n\n@pytest.mark.skipif(\n    not platform.is_windows(),\n    reason=\"Non-Windows create another set of events for this test\",\n)\ndef test_move_nested_subdirectories_on_windows(\n    p: P,\n    event_queue: TestEventQueue,\n    start_watching: StartWatching,\n    events_checker: EventsChecker,\n) -> None:\n    mkdir(p(\"dir1/dir2/dir3\"), parents=True)\n    mkfile(p(\"dir1/dir2/dir3\", \"a\"))\n    start_watching(path=p(\"\"))\n    mv(p(\"dir1/dir2\"), p(\"dir2\"))\n\n    with events_checker() as ec:\n        ec.add(FileDeletedEvent, \"dir1/dir2\")\n        ec.add(DirCreatedEvent, \"dir2\")\n        ec.add(DirCreatedEvent, \"dir2/dir3\")\n        ec.add(FileCreatedEvent, \"dir2/dir3/a\")\n        ec.add(DirModifiedEvent, \"dir2\")\n        if False:\n            # The following event is not consistently generated.\n            ec.add(DirModifiedEvent, \"dir2/dir3\")\n        else:\n            # Allow test to pass if above event is present or not\n            ec.allow_extra_events()\n\n    touch(p(\"dir2/dir3\", \"a\"))\n\n    with events_checker() as ec:\n        ec.add(FileModifiedEvent, \"dir2/dir3/a\")\n\n\n@pytest.mark.skipif(platform.is_bsd(), reason=\"BSD create another set of events for this test\")\ndef test_file_lifecyle(\n    p: P, event_queue: TestEventQueue, start_watching: StartWatching, events_checker: EventsChecker\n) -> None:\n    start_watching()\n\n    mkfile(p(\"a\"))\n    touch(p(\"a\"))\n    mv(p(\"a\"), p(\"b\"))\n    rm(p(\"b\"))\n\n    with events_checker() as ec:\n        if platform.is_linux():\n            ec.add(FileCreatedEvent, \"a\")\n            ec.add(DirModifiedEvent, \".\")\n\n            ec.add(FileOpenedEvent, \"a\")\n            ec.add(FileClosedEvent, \"a\")\n            ec.add(DirModifiedEvent, \".\")\n\n            ec.add(FileOpenedEvent, \"a\")\n            ec.add(FileModifiedEvent, \"a\")\n            ec.add(FileClosedEvent, \"a\")\n            ec.add(DirModifiedEvent, \".\")\n\n            ec.add(FileMovedEvent, \"a\", dest_path=\"b\")\n            ec.add(DirModifiedEvent, \".\")\n            ec.add(DirModifiedEvent, \".\")\n\n            ec.add(FileDeletedEvent, \"b\")\n            ec.add(DirModifiedEvent, \".\")\n        else:\n            ec.add(FileCreatedEvent, \"a\")\n            ec.add(FileModifiedEvent, \"a\")\n            ec.add(FileMovedEvent, \"a\", dest_path=\"b\")\n            ec.add(FileDeletedEvent, \"b\")\n"
  },
  {
    "path": "tests/test_events.py",
    "content": "from __future__ import annotations\n\nimport os\nimport tempfile\n\nfrom watchdog.events import (\n    EVENT_TYPE_CLOSED,\n    EVENT_TYPE_CLOSED_NO_WRITE,\n    EVENT_TYPE_CREATED,\n    EVENT_TYPE_DELETED,\n    EVENT_TYPE_MODIFIED,\n    EVENT_TYPE_MOVED,\n    EVENT_TYPE_OPENED,\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileClosedEvent,\n    FileClosedNoWriteEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    FileOpenedEvent,\n    FileSystemEventHandler,\n    generate_sub_moved_events,\n)\n\npath_1 = \"/path/xyz\"\npath_2 = \"/path/abc\"\n\n\ndef test_file_deleted_event():\n    event = FileDeletedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_DELETED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_delete_event_is_directory():\n    # Inherited properties.\n    event = FileDeletedEvent(path_1)\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_modified_event():\n    event = FileModifiedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_MODIFIED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_modified_event_is_directory():\n    # Inherited Properties\n    event = FileModifiedEvent(path_1)\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_created_event():\n    event = FileCreatedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_CREATED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_moved_event():\n    event = FileMovedEvent(path_1, path_2)\n    assert path_1 == event.src_path\n    assert path_2 == event.dest_path\n    assert event.event_type == EVENT_TYPE_MOVED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_closed_event():\n    event = FileClosedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_CLOSED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_closed_no_write_event():\n    event = FileClosedNoWriteEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_CLOSED_NO_WRITE\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_opened_event():\n    event = FileOpenedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_OPENED\n    assert not event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_dir_deleted_event():\n    event = DirDeletedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_DELETED\n    assert event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_dir_modified_event():\n    event = DirModifiedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_MODIFIED\n    assert event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_dir_created_event():\n    event = DirCreatedEvent(path_1)\n    assert path_1 == event.src_path\n    assert event.event_type == EVENT_TYPE_CREATED\n    assert event.is_directory\n    assert not event.is_synthetic\n\n\ndef test_file_system_event_handler_dispatch():\n    dir_del_event = DirDeletedEvent(\"/path/blah.py\")\n    file_del_event = FileDeletedEvent(\"/path/blah.txt\")\n    dir_cre_event = DirCreatedEvent(\"/path/blah.py\")\n    file_cre_event = FileCreatedEvent(\"/path/blah.txt\")\n    file_cls_event = FileClosedEvent(\"/path/blah.txt\")\n    file_cls_nw_event = FileClosedNoWriteEvent(\"/path/blah.txt\")\n    file_opened_event = FileOpenedEvent(\"/path/blah.txt\")\n    dir_mod_event = DirModifiedEvent(\"/path/blah.py\")\n    file_mod_event = FileModifiedEvent(\"/path/blah.txt\")\n    dir_mov_event = DirMovedEvent(\"/path/blah.py\", \"/path/blah\")\n    file_mov_event = FileMovedEvent(\"/path/blah.txt\", \"/path/blah\")\n\n    all_events = [\n        dir_mod_event,\n        dir_del_event,\n        dir_cre_event,\n        dir_mov_event,\n        file_mod_event,\n        file_del_event,\n        file_cre_event,\n        file_mov_event,\n        file_cls_event,\n        file_cls_nw_event,\n        file_opened_event,\n    ]\n\n    checkpoint = 0\n\n    class TestableEventHandler(FileSystemEventHandler):\n        def on_any_event(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n\n        def on_modified(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_MODIFIED\n\n        def on_deleted(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_DELETED\n\n        def on_moved(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_MOVED\n\n        def on_created(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_CREATED\n\n        def on_closed(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_CLOSED\n\n        def on_closed_no_write(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_CLOSED_NO_WRITE\n\n        def on_opened(self, event):\n            nonlocal checkpoint\n            checkpoint += 1\n            assert event.event_type == EVENT_TYPE_OPENED\n\n    handler = TestableEventHandler()\n\n    for event in all_events:\n        assert not event.is_synthetic\n        handler.dispatch(event)\n\n    assert checkpoint == len(all_events) * 2  # `on_any_event()` + specific `on_XXX()`\n\n\ndef test_event_comparison():\n    creation1 = FileCreatedEvent(\"foo\")\n    creation2 = FileCreatedEvent(\"foo\")\n    creation3 = FileCreatedEvent(\"bar\")\n    assert creation1 == creation2\n    assert creation1 != creation3\n    assert creation2 != creation3\n\n    move1 = FileMovedEvent(\"a\", \"b\")\n    move2 = FileMovedEvent(\"a\", \"b\")\n    move3 = FileMovedEvent(\"a\", \"c\")\n    move4 = FileMovedEvent(\"b\", \"a\")\n    assert creation1 != move1  # type: ignore[comparison-overlap]\n    assert move1 == move2\n    assert move1 != move3\n    assert move1 != move4\n    assert move2 != move3\n    assert move2 != move4\n    assert move3 != move4\n\n\ndef test_generate_sub_moved_events_repeated_dirname():\n    \"\"\"Paths with repeated directory names should not have all occurrences replaced.\n\n    Regression test: str.replace() was used to swap src_dir_path for\n    dest_dir_path, which replaces every occurrence of the substring.\n    When a directory name appears more than once in the full path,\n    the extra occurrences get corrupted.\n    \"\"\"\n    with tempfile.TemporaryDirectory() as tmpdir:\n        # Build a structure like: <tmp>/data/data/file.txt\n        # where \"data\" appears twice in the path.\n        dest = os.path.join(tmpdir, \"data\", \"data\")\n        os.makedirs(dest)\n        with open(os.path.join(dest, \"file.txt\"), \"w\") as f:\n            f.write(\"\")\n\n        dest_root = os.path.join(tmpdir, \"data\")\n        src_root = os.path.join(tmpdir, \"src_data\")\n\n        events = list(generate_sub_moved_events(src_root, dest_root))\n\n        # The inner \"data\" subdir should remain untouched.\n        dir_events = [e for e in events if isinstance(e, DirMovedEvent)]\n        file_events = [e for e in events if isinstance(e, FileMovedEvent)]\n\n        assert len(dir_events) == 1\n        assert dir_events[0].src_path == os.path.join(src_root, \"data\")\n\n        assert len(file_events) == 1\n        assert file_events[0].src_path == os.path.join(src_root, \"data\", \"file.txt\")\n\n"
  },
  {
    "path": "tests/test_fsevents.py",
    "content": "from __future__ import annotations\n\nimport contextlib\n\nimport pytest\n\nfrom watchdog.events import DirCreatedEvent\nfrom watchdog.utils import platform\n\nif not platform.is_darwin():\n    pytest.skip(\"macOS only.\", allow_module_level=True)\n\nimport logging\nimport os\nimport time\nfrom os import mkdir, rmdir\nfrom random import random\nfrom threading import Thread\nfrom time import sleep\nfrom typing import TYPE_CHECKING\nfrom unittest.mock import patch\n\nimport _watchdog_fsevents as _fsevents  # type: ignore[import-not-found]\n\nfrom watchdog.events import FileSystemEventHandler\nfrom watchdog.observers import Observer\nfrom watchdog.observers.api import BaseObserver, ObservedWatch\nfrom watchdog.observers.fsevents import FSEventsEmitter\n\nfrom .shell import touch\n\nif TYPE_CHECKING:\n    from .utils import EventsChecker, P, StartWatching\n\nlogging.basicConfig(level=logging.DEBUG)\nlogger = logging.getLogger(__name__)\n\n\n@pytest.fixture\ndef observer():\n    obs = Observer()\n    obs.start()\n    yield obs\n    obs.stop()\n    with contextlib.suppress(RuntimeError):\n        obs.join()\n\n\n@pytest.mark.parametrize(\n    (\"event\", \"expectation\"),\n    [\n        # invalid flags\n        (_fsevents.NativeEvent(\"\", 0, 0, 0), False),\n        # renamed\n        (_fsevents.NativeEvent(\"\", 0, 0x00000800, 0), False),\n        # renamed, removed\n        (_fsevents.NativeEvent(\"\", 0, 0x00000800 | 0x00000200, 0), True),\n        # renamed, removed, created\n        (_fsevents.NativeEvent(\"\", 0, 0x00000800 | 0x00000200 | 0x00000100, 0), True),\n        # renamed, removed, created, itemfindermod\n        (\n            _fsevents.NativeEvent(\"\", 0, 0x00000800 | 0x00000200 | 0x00000100 | 0x00002000, 0),\n            True,\n        ),\n        # xattr, removed, modified, itemfindermod\n        (\n            _fsevents.NativeEvent(\"\", 0, 0x00008000 | 0x00000200 | 0x00001000 | 0x00002000, 0),\n            False,\n        ),\n    ],\n)\ndef test_coalesced_event_check(event, expectation):\n    assert event.is_coalesced == expectation\n\n\ndef test_add_watch_twice(observer: BaseObserver, p: P) -> None:\n    \"\"\"Adding the same watch twice used to result in a null pointer return without an exception.\n\n    See https://github.com/gorakhargosh/watchdog/issues/765\n    \"\"\"\n\n    a = p(\"a\")\n    mkdir(a)\n    h = FileSystemEventHandler()\n    w = ObservedWatch(a, recursive=False)\n\n    def callback(path, inodes, flags, ids):\n        pass\n\n    _fsevents.add_watch(h, w, callback, [w.path])\n    with pytest.raises(RuntimeError):\n        _fsevents.add_watch(h, w, callback, [w.path])\n    _fsevents.remove_watch(w)\n    rmdir(a)\n\n\ndef test_watcher_deletion_while_receiving_events_1(\n    caplog: pytest.LogCaptureFixture,\n    p: P,\n    start_watching: StartWatching,\n) -> None:\n    \"\"\"\n    When the watcher is stopped while there are events, such exception could happen:\n\n        Traceback (most recent call last):\n            File \"observers/fsevents.py\", line 327, in events_callback\n            self.queue_events(self.timeout, events)\n            File \"observers/fsevents.py\", line 187, in queue_events\n            src_path = self._encode_path(event.path)\n            File \"observers/fsevents.py\", line 352, in _encode_path\n            if isinstance(self.watch.path, bytes):\n        AttributeError: 'NoneType' object has no attribute 'path'\n    \"\"\"\n    tmpdir = p()\n\n    orig = FSEventsEmitter.events_callback\n\n    def cb(*args):\n        FSEventsEmitter.stop(emitter)\n        orig(*args)\n\n    with caplog.at_level(logging.ERROR), patch.object(FSEventsEmitter, \"events_callback\", new=cb):\n        emitter = start_watching(path=tmpdir)\n        # Less than 100 is not enough events to trigger the error\n        for n in range(100):\n            touch(p(f\"{n}.txt\"))\n        emitter.stop()\n        assert not caplog.records\n\n\ndef test_watcher_deletion_while_receiving_events_2(\n    caplog: pytest.LogCaptureFixture,\n    p: P,\n    start_watching: StartWatching,\n) -> None:\n    \"\"\"Note: that test takes about 20 seconds to complete.\n\n    Quite similar test to prevent another issue\n    when the watcher is stopped while there are events, such exception could happen:\n\n        Traceback (most recent call last):\n            File \"observers/fsevents.py\", line 327, in events_callback\n              self.queue_events(self.timeout, events)\n            File \"observers/fsevents.py\", line 235, in queue_events\n              self._queue_created_event(event, src_path, src_dirname)\n            File \"observers/fsevents.py\", line 132, in _queue_created_event\n              self.queue_event(cls(src_path))\n            File \"observers/fsevents.py\", line 104, in queue_event\n              if self._watch.is_recursive:\n        AttributeError: 'NoneType' object has no attribute 'is_recursive'\n    \"\"\"\n\n    def try_to_fail():\n        tmpdir = p()\n        emitter = start_watching(path=tmpdir)\n\n        def create_files():\n            # Less than 2000 is not enough events to trigger the error\n            for n in range(2000):\n                touch(p(f\"{n}.txt\"))\n\n        def stop(em):\n            sleep(random())\n            em.stop()\n\n        th1 = Thread(target=create_files)\n        th2 = Thread(target=stop, args=(emitter,))\n\n        try:\n            th1.start()\n            th2.start()\n            th1.join()\n            th2.join()\n        finally:\n            emitter.stop()\n\n    # 20 attempts to make the random failure happen\n    with caplog.at_level(logging.ERROR):\n        for _ in range(20):\n            try_to_fail()\n            sleep(random())\n\n        assert not caplog.records\n\n\ndef test_remove_watch_twice(start_watching: StartWatching) -> None:\n    \"\"\"\n    ValueError: PyCapsule_GetPointer called with invalid PyCapsule object\n    The above exception was the direct cause of the following exception:\n\n    src/watchdog/utils/__init__.py:92: in stop\n        self.on_thread_stop()\n\n    src/watchdog/observers/fsevents.py:73: SystemError\n        def on_thread_stop(self):\n    >       _fsevents.remove_watch(self.watch)\n    E       SystemError: <built-in function remove_watch> returned a result with an error set\n\n    (FSEvents.framework) FSEventStreamStop(): failed assertion 'streamRef != NULL'\n    (FSEvents.framework) FSEventStreamInvalidate(): failed assertion 'streamRef != NULL'\n    (FSEvents.framework) FSEventStreamRelease(): failed assertion 'streamRef != NULL'\n    \"\"\"\n    emitter = start_watching()\n    # This one must work\n    emitter.stop()\n    # This is allowed to call several times .stop()\n    emitter.stop()\n\n\ndef test_unschedule_removed_folder(observer: BaseObserver, p: P) -> None:\n    \"\"\"\n    TypeError: PyCObject_AsVoidPtr called with null pointer\n    The above exception was the direct cause of the following exception:\n\n    def on_thread_stop(self):\n        if self.watch:\n            _fsevents.remove_watch(self.watch)\n    E       SystemError: <built-in function stop> returned a result with an error set\n\n    (FSEvents.framework) FSEventStreamStop(): failed assertion 'streamRef != NULL'\n    (FSEvents.framework) FSEventStreamInvalidate(): failed assertion 'streamRef != NULL'\n    (FSEvents.framework) FSEventStreamRelease(): failed assertion 'streamRef != NULL'\n    \"\"\"\n    a = p(\"a\")\n    mkdir(a)\n    w = observer.schedule(FileSystemEventHandler(), a, recursive=False)\n    rmdir(a)\n    time.sleep(0.1)\n    observer.unschedule(w)\n\n\ndef test_converting_cfstring_to_pyunicode(p: P, start_watching: StartWatching, events_checker: EventsChecker) -> None:\n    \"\"\"See https://github.com/gorakhargosh/watchdog/issues/762\"\"\"\n\n    tmpdir = p()\n    emitter = start_watching(path=tmpdir)\n\n    dirname = \"TéstClass\"\n\n    try:\n        mkdir(p(dirname))\n        with events_checker() as ec:\n            ec.add(DirCreatedEvent, dirname)\n    finally:\n        emitter.stop()\n\n\ndef test_recursive_check_accepts_relative_paths(p: P) -> None:\n    \"\"\"See https://github.com/gorakhargosh/watchdog/issues/797\n\n    The test code provided in the defect observes the current working directory\n    using \".\". Since the watch path wasn't normalized then that failed.\n    This test emulates the scenario.\n    \"\"\"\n    from watchdog.events import FileCreatedEvent, FileModifiedEvent, PatternMatchingEventHandler\n\n    class TestEventHandler(PatternMatchingEventHandler):\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n            # the TestEventHandler instance is set to ignore_directories,\n            # as such we won't get a DirModifiedEvent(p()) here.\n            self.expected_events = [\n                FileCreatedEvent(p(\"foo.json\")),\n                FileModifiedEvent(p(\"foo.json\")),\n            ]\n            self.observed_events = set()\n\n        def on_any_event(self, event):\n            self.expected_events.remove(event)\n            self.observed_events.add(event)\n\n        def done(self):\n            return not self.expected_events\n\n    cwd = os.getcwd()\n    os.chdir(p())\n    event_handler = TestEventHandler(patterns=[\"**/*.json\"], ignore_patterns=[], ignore_directories=True)\n    observer = Observer()\n    observer.schedule(event_handler, \".\")\n    observer.start()\n    time.sleep(0.1)\n\n    try:\n        touch(p(\"foo.json\"))\n        timeout_at = time.time() + 5\n        while not event_handler.done() and time.time() < timeout_at:\n            time.sleep(0.1)\n\n        assert event_handler.done()\n    finally:\n        os.chdir(cwd)\n        observer.stop()\n        observer.join()\n\n\ndef test_watchdog_recursive(p: P) -> None:\n    \"\"\"See https://github.com/gorakhargosh/watchdog/issues/706\"\"\"\n    import os.path\n\n    from watchdog.events import FileSystemEventHandler\n    from watchdog.observers import Observer\n\n    class Handler(FileSystemEventHandler):\n        def __init__(self):\n            super().__init__()\n            self.changes = []\n\n        def on_any_event(self, event):\n            self.changes.append(os.path.basename(event.src_path))\n\n    handler = Handler()\n    observer = Observer()\n\n    watches = [observer.schedule(handler, str(p(\"\")), recursive=True)]\n    try:\n        observer.start()\n        time.sleep(0.1)\n\n        touch(p(\"my0.txt\"))\n        mkdir(p(\"dir_rec\"))\n        touch(p(\"dir_rec\", \"my1.txt\"))\n\n        expected = {\"dir_rec\", \"my0.txt\", \"my1.txt\"}\n        timeout_at = time.time() + 5\n        while not expected.issubset(handler.changes) and time.time() < timeout_at:\n            time.sleep(0.2)\n\n        assert expected.issubset(handler.changes), f\"Did not find expected changes. Found: {handler.changes}\"\n    finally:\n        for watch in watches:\n            observer.unschedule(watch)\n        observer.stop()\n        observer.join(1)\n"
  },
  {
    "path": "tests/test_inotify_c.py",
    "content": "from __future__ import annotations\n\nfrom contextlib import ExitStack\n\nimport pytest\n\nfrom watchdog.utils import platform\n\nif not platform.is_linux():\n    pytest.skip(\"GNU/Linux only.\", allow_module_level=True)\n\nimport ctypes\nimport errno\nimport logging\nimport os\nimport select\nimport struct\nfrom typing import TYPE_CHECKING\nfrom unittest.mock import patch\n\nfrom watchdog.events import DirCreatedEvent, DirDeletedEvent, DirModifiedEvent\nfrom watchdog.observers.inotify_c import InotifyConstants, InotifyEvent, InotifyFD, WatchDescriptor\n\nif TYPE_CHECKING:\n    from .utils import Helper, P, StartWatching, TestEventQueue\n\nlogging.basicConfig(level=logging.DEBUG)\nlogger = logging.getLogger(__name__)\n\n\ndef struct_inotify(wd, mask, cookie=0, length=0, name=b\"\") -> bytes:\n    assert len(name) <= length\n    struct_format = (\n        \"=\"  # (native endianness, standard sizes)\n        \"i\"  # int      wd\n        \"i\"  # uint32_t mask\n        \"i\"  # uint32_t cookie\n        \"i\"  # uint32_t len\n        f\"{length}s\"  # char[] name\n    )\n    return struct.pack(struct_format, wd, mask, cookie, length, name)\n\n\ndef test_late_double_deletion(helper: Helper, p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    inotify_fd = type(\"FD\", (object,), {})()\n    inotify_fd.last = 0\n    inotify_fd.wds = []\n    inotify_fd.buf = b\"\"\n\n    const = InotifyConstants()\n\n    # CREATE DELETE CREATE DELETE DELETE_SELF IGNORE DELETE_SELF IGNORE\n    inotify_fd_buf = (\n        struct_inotify(wd=1, mask=const.IN_CREATE | const.IN_ISDIR, length=16, name=b\"subdir1\")\n        + struct_inotify(wd=1, mask=const.IN_DELETE | const.IN_ISDIR, length=16, name=b\"subdir1\")\n    ) * 2 + (\n        struct_inotify(wd=2, mask=const.IN_DELETE_SELF)\n        + struct_inotify(wd=2, mask=const.IN_IGNORED)\n        + struct_inotify(wd=3, mask=const.IN_DELETE_SELF)\n        + struct_inotify(wd=3, mask=const.IN_IGNORED)\n    )\n\n    select_bkp = select.select\n\n    def fakeselect(read_list, *args, **kwargs):\n        if inotify_fd in read_list:\n            return [inotify_fd], [], []\n        return select_bkp(read_list, *args, **kwargs)\n\n    poll_bkp = select.poll\n\n    class Fakepoll:\n        def __init__(self):\n            self._orig = poll_bkp()\n            self._fake = False\n\n        def register(self, fd, *args, **kwargs):\n            if fd == inotify_fd:\n                self._fake = True\n                return None\n            return self._orig.register(fd, *args, **kwargs)\n\n        def poll(self, *args, **kwargs):\n            if self._fake:\n                return [(inotify_fd, select.POLLIN)]\n            return self._orig.poll(*args, **kwargs)\n\n    os_read_bkp = os.read\n\n    def fakeread(fd, length):\n        if fd is inotify_fd:\n            result, fd.buf = fd.buf[:length], fd.buf[length:]\n            return result\n        return os_read_bkp(fd, length)\n\n    os_close_bkp = os.close\n\n    def fakeclose(fd):\n        if fd is not inotify_fd:\n            os_close_bkp(fd)\n\n    def inotify_init():\n        return inotify_fd\n\n    def inotify_add_watch(fd, path, mask):\n        fd.last += 1\n        logger.debug(\"New wd = %d\", fd.last)\n        fd.wds.append(fd.last)\n        return fd.last\n\n    def inotify_rm_watch(fd, wd):\n        logger.debug(\"Removing wd = %d\", wd)\n        fd.wds.remove(wd)\n        return 0\n\n    # Mocks the API!\n    from watchdog.observers import inotify_c\n\n    mock1 = patch.object(os, \"read\", new=fakeread)\n    mock2 = patch.object(os, \"close\", new=fakeclose)\n    mock3 = patch.object(inotify_c, \"inotify_init\", new=inotify_init)\n    mock4 = patch.object(inotify_c, \"inotify_add_watch\", new=inotify_add_watch)\n    mock5 = patch.object(inotify_c, \"inotify_rm_watch\", new=inotify_rm_watch)\n    mock6 = patch.object(select, \"select\", new=fakeselect)\n    mock7 = patch.object(select, \"poll\", new=Fakepoll)\n    mock8 = patch.object(inotify_c.InotifyFD, \"_instance\", new=None)\n\n    with mock1, mock2, mock3, mock4, mock5, mock6, mock7, mock8:\n        try:\n            try:\n                start_watching(path=p(\"\"))\n                inotify_fd.buf = inotify_fd_buf\n                # Watchdog Events\n                for evt_cls in [DirCreatedEvent, DirDeletedEvent] * 2:\n                    event = event_queue.get(timeout=5)[0]\n                    assert isinstance(event, evt_cls)\n                    assert event.src_path == p(\"subdir1\")\n                    event = event_queue.get(timeout=5)[0]\n                    assert isinstance(event, DirModifiedEvent)\n                    assert event.src_path == p(\"\").rstrip(os.path.sep)\n            finally:\n                helper.close()\n        finally:\n            # reset InotifyFD singleton\n            inotify_fd_instance = inotify_c.InotifyFD._instance  # noqa: SLF001\n            if inotify_fd_instance.is_alive():\n                inotify_fd_instance.stop()\n                inotify_fd_instance.join()\n            elif not inotify_fd_instance._closed:  # noqa: SLF001\n                inotify_fd_instance.close()\n\n    assert inotify_fd.last == 3  # Number of directories\n    assert inotify_fd.buf == b\"\"  # Didn't miss any event\n    assert inotify_fd.wds == [2, 3]  # Only 1 is removed explicitly\n\n\n@pytest.mark.parametrize(\n    (\"error\", \"pattern\"),\n    [\n        (errno.ENOSPC, \"inotify watch limit reached\"),\n        (errno.EMFILE, \"inotify instance limit reached\"),\n        (errno.ENOENT, \"No such file or directory\"),\n        (-1, \"error\"),\n    ],\n)\ndef test_raise_error(error, pattern):\n    with patch.object(ctypes, \"get_errno\", new=lambda: error), pytest.raises(OSError, match=pattern) as exc:\n        InotifyFD._raise_error()  # noqa: SLF001\n    assert exc.value.errno == error\n\n\ndef test_non_ascii_path(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    \"\"\"\n    Inotify can construct an event for a path containing non-ASCII.\n    \"\"\"\n    path = p(\"\\N{SNOWMAN}\")\n    start_watching(path=p(\"\"))\n    os.mkdir(path)\n    event, _ = event_queue.get(timeout=5)\n    assert isinstance(event.src_path, str)\n    assert event.src_path == path\n    # Just make sure it doesn't raise an exception.\n    assert repr(event)\n\n\ndef test_watch_file(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    path = p(\"this_is_a_file\")\n    with open(path, \"a\"):\n        pass\n    start_watching(path=path)\n    os.remove(path)\n    event, _ = event_queue.get(timeout=5)\n    assert repr(event)\n\n\ndef test_event_equality(p: P) -> None:\n    wd_parent_dir = WatchDescriptor(42)\n    filename = b\"file.ext\"\n    event1 = InotifyEvent(wd_parent_dir, InotifyConstants.IN_CREATE, 0, filename)\n    event2 = InotifyEvent(wd_parent_dir, InotifyConstants.IN_CREATE, 0, filename)\n    event3 = InotifyEvent(wd_parent_dir, InotifyConstants.IN_ACCESS, 0, filename)\n    assert event1 == event2\n    assert event1 != event3\n    assert event2 != event3\n\n\ndef test_select_fd(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None:\n    # We open a file 2048 times to ensure that we exhaust 1024 file\n    # descriptors, the limit of a select() call.\n    path = p(\"new_file\")\n    with open(path, \"a\"):\n        pass\n    with ExitStack() as stack:\n        for _i in range(2048):\n            stack.enter_context(open(path))\n\n        # Watch this file for deletion (copied from `test_watch_file`)\n        path = p(\"this_is_a_file\")\n        with open(path, \"a\"):\n            pass\n        start_watching(path=path)\n        os.remove(path)\n        event, _ = event_queue.get(timeout=5)\n        assert repr(event)\n"
  },
  {
    "path": "tests/test_inotify_watch_group.py",
    "content": "from __future__ import annotations\n\nfrom typing import Callable\n\nimport pytest\n\nfrom watchdog.utils import platform\n\nif not platform.is_linux():\n    pytest.skip(\"GNU/Linux only.\", allow_module_level=True)\n\nimport os\nimport random\n\nfrom watchdog.observers.inotify import InotifyWatchGroup\nfrom watchdog.observers.inotify_c import WATCHDOG_ALL_EVENTS, InotifyConstants, InotifyFD, Mask\nfrom watchdog.observers.inotify_move_event_grouper import GroupedInotifyEvent, PathedInotifyEvent\n\nfrom .shell import mkdir, mount_tmpfs, mv, rm, symlink, touch, unmount\n\n\ndef wait_for_move_event(read_event: Callable[[], GroupedInotifyEvent]) -> GroupedInotifyEvent:\n    while True:\n        event = read_event()\n        if not isinstance(event, PathedInotifyEvent) or event.ev.is_move:\n            return event\n\n\ndef create_inotify_watch(path: bytes, *, recursive: bool = False, follow_symlink: bool = False) -> InotifyWatchGroup:\n    return InotifyWatchGroup(\n        InotifyFD.get_instance(),\n        path,\n        is_recursive=recursive,\n        follow_symlink=follow_symlink,\n        event_mask=WATCHDOG_ALL_EVENTS,\n    )\n\n\n@pytest.mark.timeout(5)\ndef test_move_from(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n\n    inotify = create_inotify_watch(p(\"dir1\").encode())\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    assert_event(inotify, p(\"dir1\", \"a\"), InotifyConstants.IN_MOVED_FROM)\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_move_to(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n\n    inotify = create_inotify_watch(p(\"dir2\").encode())\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    assert_event(inotify, p(\"dir2\", \"b\"), InotifyConstants.IN_MOVED_TO)\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_move_internal(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n\n    inotify = create_inotify_watch(p(\"\").encode(), recursive=True)\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    frm, to = wait_for_move_event(inotify.read_event)\n    assert frm.path == p(\"dir1\", \"a\").encode()\n    assert to.path == p(\"dir2\", \"b\").encode()\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_move_internal_symlink_followed(p):\n    mkdir(p(\"dir\", \"dir1\"), parents=True)\n    mkdir(p(\"dir\", \"dir2\"))\n    touch(p(\"dir\", \"dir1\", \"a\"))\n    symlink(p(\"dir\"), p(\"symdir\"), target_is_directory=True)\n\n    inotify = create_inotify_watch(p(\"symdir\").encode(), recursive=True, follow_symlink=True)\n    mv(p(\"dir\", \"dir1\", \"a\"), p(\"dir\", \"dir2\", \"b\"))\n    frm, to = wait_for_move_event(inotify.read_event)\n    assert frm.path == p(\"symdir\", \"dir1\", \"a\").encode()\n    assert to.path == p(\"symdir\", \"dir2\", \"b\").encode()\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(10)\ndef test_move_internal_batch(p):\n    n = 100\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    files = [str(i) for i in range(n)]\n    for f in files:\n        touch(p(\"dir1\", f))\n\n    inotify = create_inotify_watch(p(\"\").encode(), recursive=True)\n\n    random.shuffle(files)\n    for f in files:\n        mv(p(\"dir1\", f), p(\"dir2\", f))\n\n    # Check that all n events are paired\n    for _ in range(n):\n        frm, to = wait_for_move_event(inotify.read_event)\n        assert os.path.dirname(frm.path).endswith(b\"/dir1\")\n        assert os.path.dirname(to.path).endswith(b\"/dir2\")\n        assert frm.ev.name == to.ev.name\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_delete_watched_directory(p):\n    mkdir(p(\"dir\"))\n    inotify = create_inotify_watch(p(\"dir\").encode())\n    rm(p(\"dir\"), recursive=True)\n\n    # Wait for the event to be picked up\n    inotify.read_event()\n\n    # Ensure InotifyBuffer shuts down cleanly without raising an exception\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_delete_watched_directory_symlink_followed(p):\n    mkdir(p(\"dir\", \"dir2\"), parents=True)\n    symlink(p(\"dir\"), p(\"symdir\"), target_is_directory=True)\n\n    inotify = create_inotify_watch(p(\"symdir\").encode(), follow_symlink=True)\n    rm(p(\"dir\", \"dir2\"), recursive=True)\n\n    # Wait for the event to be picked up\n    event = inotify.read_event()\n    while not isinstance(event, PathedInotifyEvent) or (\n        event.ev.mask != (InotifyConstants.IN_DELETE | InotifyConstants.IN_ISDIR)\n    ):\n        event = inotify.read_event()\n\n    # Ensure InotifyBuffer shuts down cleanly without raising an exception\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\ndef test_delete_watched_directory_symlink_followed_recursive(p):\n    mkdir(p(\"dir\"), parents=True)\n    mkdir(p(\"dir2\", \"dir3\", \"dir4\"), parents=True)\n    symlink(p(\"dir2\"), p(\"dir\", \"symdir\"), target_is_directory=True)\n\n    inotify = create_inotify_watch(p(\"dir\").encode(), follow_symlink=True, recursive=True)\n    rm(p(\"dir2\", \"dir3\", \"dir4\"), recursive=True)\n\n    # Wait for the event to be picked up\n    event = inotify.read_event()\n    while not isinstance(event, PathedInotifyEvent) or (\n        event.ev.mask != (InotifyConstants.IN_DELETE | InotifyConstants.IN_ISDIR)\n    ):\n        event = inotify.read_event()\n\n    # Ensure InotifyBuffer shuts down cleanly without raising an exception\n    inotify.deactivate()\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.skipif(\"GITHUB_REF\" not in os.environ, reason=\"sudo password prompt\")\ndef test_unmount_watched_directory_filesystem(p):\n    mkdir(p(\"dir1\"))\n    mount_tmpfs(p(\"dir1\"))\n    mkdir(p(\"dir1/dir2\"))\n    inotify = create_inotify_watch(p(\"dir1/dir2\").encode())\n    unmount(p(\"dir1\"))\n\n    # Wait for the event to be picked up\n    inotify.read_event()\n\n    # Ensure InotifyBuffer shuts down cleanly without raising an exception\n    inotify.deactivate()\n    assert not inotify.is_active\n    assert not inotify._active_callbacks_by_watch  # noqa: SLF001\n    assert not inotify._active_callbacks_by_watch  # noqa: SLF001\n\n\ndef assert_event(inotify: InotifyWatchGroup, expected_path: str, expected_kind: Mask):\n    event = inotify.read_event()\n    assert event.path == expected_path.encode()\n    assert event.ev.mask & expected_kind\n\n\ndef assert_touch_events(inotify: InotifyWatchGroup, expected_path: str):\n    assert_event(inotify, expected_path, InotifyConstants.IN_OPEN)\n    assert_event(inotify, expected_path, InotifyConstants.IN_ATTRIB)\n    assert_event(inotify, expected_path, InotifyConstants.IN_CLOSE_WRITE)\n\n\n@pytest.mark.timeout(5)\ndef test_watch_groups_are_independent(p):\n    original_path = p(\"rootdir\", \"dir1\", \"a\")\n    destination_path = p(\"rootdir\", \"dir2\", \"b\")\n\n    def setup() -> None:\n        mkdir(p(\"rootdir\"))\n        mkdir(p(\"rootdir\", \"dir1\"))\n        mkdir(p(\"rootdir\", \"dir2\"))\n        touch(original_path)\n\n    def run() -> None:\n        mv(original_path, destination_path)\n        touch(destination_path)  # generates events after the move.\n        rm(destination_path)  # generates delete event after the move.\n\n    def cleanup() -> None:\n        rm(p(\"rootdir\"), recursive=True)\n\n    def assert_inotify_a_events(inotify_a: InotifyWatchGroup) -> None:\n        # check inotify_a uses the original path of the file.\n        assert_touch_events(inotify_a, original_path)\n        assert_event(inotify_a, original_path, InotifyConstants.IN_ATTRIB)\n        assert_event(inotify_a, original_path, InotifyConstants.IN_DELETE_SELF)\n\n    def assert_inotify_root_events(inotify_root: InotifyWatchGroup) -> None:\n        # check inotify_root tracks the new path of the file.\n        ev1_move = wait_for_move_event(inotify_root.read_event)\n        assert not isinstance(ev1_move, PathedInotifyEvent)\n        assert_touch_events(inotify_root, destination_path)\n        assert_event(inotify_root, destination_path, InotifyConstants.IN_DELETE)\n\n    # inotify_a works alone:\n    setup()\n    inotify_a = create_inotify_watch(original_path.encode())\n    run()\n    assert_inotify_a_events(inotify_a)\n    inotify_a.deactivate()\n    cleanup()\n\n    # inotify_a is not affected by inotify_root:\n    setup()\n    inotify_a = create_inotify_watch(original_path.encode())\n    inotify_root = create_inotify_watch(p(\"rootdir\").encode(), recursive=True)\n    run()\n    assert_inotify_a_events(inotify_a)\n    assert_inotify_root_events(inotify_root)\n    inotify_root.deactivate()\n    inotify_a.deactivate()\n    cleanup()\n"
  },
  {
    "path": "tests/test_isolated.py",
    "content": "import importlib\n\nimport pytest\n\nfrom watchdog.utils import platform\n\nfrom .utils import run_isolated_test\n\n\n# Kqueue isn't supported by Eventlet, so BSD is out\n# Current usage ReadDirectoryChangesW on Windows is blocking, though async may be possible\n@pytest.mark.skipif(not platform.is_linux(), reason=\"Eventlet only supported in Linux\")\ndef test_observer_stops_in_eventlet():\n    if not importlib.util.find_spec(\"eventlet\"):\n        pytest.skip(\"eventlet not installed\")\n\n    run_isolated_test(\"eventlet_observer_stops.py\")\n\n\n@pytest.mark.skipif(not platform.is_linux(), reason=\"Eventlet only supported in Linux\")\ndef test_eventlet_skip_repeat_queue():\n    if not importlib.util.find_spec(\"eventlet\"):\n        pytest.skip(\"eventlet not installed\")\n\n    run_isolated_test(\"eventlet_skip_repeat_queue.py\")\n"
  },
  {
    "path": "tests/test_logging_event_handler.py",
    "content": "from __future__ import annotations\n\nfrom watchdog.events import (\n    EVENT_TYPE_CLOSED,\n    EVENT_TYPE_CLOSED_NO_WRITE,\n    EVENT_TYPE_CREATED,\n    EVENT_TYPE_DELETED,\n    EVENT_TYPE_MODIFIED,\n    EVENT_TYPE_MOVED,\n    EVENT_TYPE_OPENED,\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileClosedEvent,\n    FileClosedNoWriteEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    FileOpenedEvent,\n    FileSystemEvent,\n    LoggingEventHandler,\n)\n\npath_1 = \"/path/xyz\"\npath_2 = \"/path/abc\"\n\n\nclass _TestableEventHandler(LoggingEventHandler):\n    def on_any_event(self, event):\n        assert isinstance(event, FileSystemEvent)\n\n    def on_modified(self, event):\n        super().on_modified(event)\n        assert event.event_type == EVENT_TYPE_MODIFIED\n\n    def on_deleted(self, event):\n        super().on_deleted(event)\n        assert event.event_type == EVENT_TYPE_DELETED\n\n    def on_moved(self, event):\n        super().on_moved(event)\n        assert event.event_type == EVENT_TYPE_MOVED\n\n    def on_created(self, event):\n        super().on_created(event)\n        assert event.event_type == EVENT_TYPE_CREATED\n\n    def on_closed(self, event):\n        super().on_closed(event)\n        assert event.event_type == EVENT_TYPE_CLOSED\n\n    def on_closed_no_write(self, event):\n        super().on_closed_no_write(event)\n        assert event.event_type == EVENT_TYPE_CLOSED_NO_WRITE\n\n    def on_opened(self, event):\n        super().on_opened(event)\n        assert event.event_type == EVENT_TYPE_OPENED\n\n\ndef test_logging_event_handler_dispatch():\n    dir_del_event = DirDeletedEvent(\"/path/blah.py\")\n    file_del_event = FileDeletedEvent(\"/path/blah.txt\")\n    dir_cre_event = DirCreatedEvent(\"/path/blah.py\")\n    file_cre_event = FileCreatedEvent(\"/path/blah.txt\")\n    dir_mod_event = DirModifiedEvent(\"/path/blah.py\")\n    file_mod_event = FileModifiedEvent(\"/path/blah.txt\")\n    dir_mov_event = DirMovedEvent(\"/path/blah.py\", \"/path/blah\")\n    file_mov_event = FileMovedEvent(\"/path/blah.txt\", \"/path/blah\")\n    file_ope_event = FileOpenedEvent(\"/path/blah.txt\")\n    file_clo_event = FileClosedEvent(\"/path/blah.txt\")\n    file_clo_nw_event = FileClosedNoWriteEvent(\"/path/blah.txt\")\n\n    all_events = [\n        dir_mod_event,\n        dir_del_event,\n        dir_cre_event,\n        dir_mov_event,\n        file_mod_event,\n        file_del_event,\n        file_cre_event,\n        file_mov_event,\n        file_ope_event,\n        file_clo_event,\n        file_clo_nw_event,\n    ]\n\n    handler = _TestableEventHandler()\n    for event in all_events:\n        handler.dispatch(event)\n"
  },
  {
    "path": "tests/test_observer.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport threading\nfrom typing import TYPE_CHECKING\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom watchdog.events import FileModifiedEvent, FileSystemEventHandler\nfrom watchdog.observers.api import BaseObserver, EventEmitter\n\nif TYPE_CHECKING:\n    from collections.abc import Iterator\n\n\n@pytest.fixture\ndef observer() -> Iterator[BaseObserver]:\n    obs = BaseObserver(EventEmitter)\n    yield obs\n    obs.stop()\n    with contextlib.suppress(RuntimeError):\n        obs.join()\n\n\n@pytest.fixture\ndef observer2():\n    obs = BaseObserver(EventEmitter)\n    yield obs\n    obs.stop()\n    with contextlib.suppress(RuntimeError):\n        obs.join()\n\n\ndef test_schedule_should_start_emitter_if_running(observer):\n    observer.start()\n    observer.schedule(None, \"\")\n    (emitter,) = observer.emitters\n    assert emitter.is_alive()\n\n\ndef test_schedule_should_not_start_emitter_if_not_running(observer):\n    observer.schedule(None, \"\")\n    (emitter,) = observer.emitters\n    assert not emitter.is_alive()\n\n\ndef test_start_should_start_emitter(observer):\n    observer.schedule(None, \"\")\n    observer.start()\n    (emitter,) = observer.emitters\n    assert emitter.is_alive()\n\n\ndef test_stop_should_stop_emitter(observer):\n    observer.schedule(None, \"\")\n    observer.start()\n    (emitter,) = observer.emitters\n    assert emitter.is_alive()\n    observer.stop()\n    observer.join()\n    assert not observer.is_alive()\n    assert not emitter.is_alive()\n\n\ndef test_unschedule_self(observer):\n    \"\"\"\n    Tests that unscheduling a watch from within an event handler correctly\n    correctly unregisters emitter and handler without deadlocking.\n    \"\"\"\n\n    class EventHandler(FileSystemEventHandler):\n        def on_modified(self, event):\n            observer.unschedule(watch)\n            unschedule_finished.set()\n\n    unschedule_finished = threading.Event()\n    watch = observer.schedule(EventHandler(), \"\")\n    observer.start()\n\n    (emitter,) = observer.emitters\n    emitter.queue_event(FileModifiedEvent(\"\"))\n\n    assert unschedule_finished.wait()\n    assert len(observer.emitters) == 0\n\n\ndef test_schedule_after_unschedule_all(observer):\n    observer.start()\n    observer.schedule(None, \"\")\n    assert len(observer.emitters) == 1\n\n    observer.unschedule_all()\n    assert len(observer.emitters) == 0\n\n    observer.schedule(None, \"\")\n    assert len(observer.emitters) == 1\n\n\ndef test_2_observers_on_the_same_path(observer, observer2):\n    assert observer is not observer2\n\n    observer.schedule(None, \"\")\n    assert len(observer.emitters) == 1\n\n    observer2.schedule(None, \"\")\n    assert len(observer2.emitters) == 1\n\n\ndef test_start_failure_should_not_prevent_further_try(observer):\n    observer.schedule(None, \"\")\n    emitters = observer.emitters\n    assert len(emitters) == 1\n\n    # Make the emitter to fail on start()\n\n    def mocked_start():\n        raise OSError(\"Mock'ed!\")\n\n    emitter = next(iter(emitters))\n    with patch.object(emitter, \"start\", new=mocked_start), pytest.raises(OSError, match=\"Mock'ed!\"):\n        observer.start()\n    # The emitter should be removed from the list\n    assert len(observer.emitters) == 0\n\n    # Restoring the original behavior should work like there never be emitters\n    observer.start()\n    assert len(observer.emitters) == 0\n\n    # Re-scheduling the watch should work\n    observer.schedule(None, \"\")\n    assert len(observer.emitters) == 1\n\n\ndef test_schedule_failure_should_not_prevent_future_schedules(observer):\n    observer.start()\n\n    # Make the emitter fail on start(), and subsequently the observer to fail on schedule()\n    def bad_start(_):\n        raise OSError(\"Mock'ed!\")\n\n    with patch.object(EventEmitter, \"start\", new=bad_start), pytest.raises(OSError, match=\"Mock'ed!\"):\n        observer.schedule(None, \"\")\n    # The emitter should not be in the list\n    assert not observer.emitters\n\n    # Re-scheduling the watch should work\n    observer.schedule(None, \"\")\n    assert len(observer.emitters) == 1\n\n\ndef test_context_manager_starts_observer():\n    \"\"\"Test that context manager starts observer if not already started.\"\"\"\n    obs = BaseObserver(EventEmitter)\n    obs.schedule(None, \"\")\n    (emitter,) = obs.emitters\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n    with obs:\n        assert obs.is_alive()\n        assert emitter.is_alive()\n\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n\ndef test_context_manager_with_already_started_observer():\n    \"\"\"Test that context manager doesn't try to start observer if already started.\"\"\"\n    obs = BaseObserver(EventEmitter)\n    obs.schedule(None, \"\")\n    obs.start()\n    (emitter,) = obs.emitters\n    assert obs.is_alive()\n    assert emitter.is_alive()\n\n    # Using context manager with already started observer should not raise\n    with obs:\n        assert obs.is_alive()\n        assert emitter.is_alive()\n\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n\ndef test_context_manager_stops_on_exit():\n    \"\"\"Test that context manager stops and joins observer on exit.\"\"\"\n    obs = BaseObserver(EventEmitter)\n    obs.schedule(None, \"\")\n    (emitter,) = obs.emitters\n\n    with obs:\n        assert obs.is_alive()\n        assert emitter.is_alive()\n        # Exit context\n\n    # After exiting context, observer should be stopped\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n\ndef test_context_manager_handles_exceptions():\n    \"\"\"Test that context manager properly cleans up even when exceptions occur.\"\"\"\n    obs = BaseObserver(EventEmitter)\n    obs.schedule(None, \"\")\n    (emitter,) = obs.emitters\n\n    def _raise_in_context() -> None:\n        with obs:\n            assert obs.is_alive()\n            assert emitter.is_alive()\n            raise ValueError\n\n    with pytest.raises(ValueError, match=\".*\"):\n        _raise_in_context()\n\n    # Observer should still be stopped even after exception\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n\ndef test_context_manager_with_scheduled_watches():\n    \"\"\"Test that context manager works correctly with scheduled watches.\"\"\"\n    obs = BaseObserver(EventEmitter)\n    handler = FileSystemEventHandler()\n    obs.schedule(handler, \"/foobar\", recursive=True)\n    assert len(obs.emitters) == 1\n    (emitter,) = obs.emitters\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n\n    with obs:\n        assert obs.is_alive()\n        assert emitter.is_alive()\n        assert len(obs.emitters) == 1\n\n    assert not obs.is_alive()\n    assert not emitter.is_alive()\n"
  },
  {
    "path": "tests/test_observers_api.py",
    "content": "from __future__ import annotations\n\nimport time\nfrom pathlib import Path\n\nimport pytest\n\nfrom watchdog.events import FileModifiedEvent, FileOpenedEvent, LoggingEventHandler\nfrom watchdog.observers.api import BaseObserver, EventDispatcher, EventEmitter, EventQueue, ObservedWatch\n\n\ndef test_observer_constructor():\n    ObservedWatch(Path(\"/foobar\"), recursive=True)\n\n\ndef test_observer__eq__():\n    watch1 = ObservedWatch(\"/foobar\", recursive=True)\n    watch2 = ObservedWatch(\"/foobar\", recursive=True)\n    watch_ne1 = ObservedWatch(\"/foo\", recursive=True)\n    watch_ne2 = ObservedWatch(\"/foobar\", recursive=False)\n\n    assert watch1 == watch2\n    assert watch1.__eq__(watch2)\n    assert not watch1.__eq__(watch_ne1)\n    assert not watch1.__eq__(watch_ne2)\n\n\ndef test_observer__ne__():\n    watch1 = ObservedWatch(\"/foobar\", recursive=True)\n    watch2 = ObservedWatch(\"/foobar\", recursive=True)\n    watch_ne1 = ObservedWatch(\"/foo\", recursive=True)\n    watch_ne2 = ObservedWatch(\"/foobar\", recursive=False)\n\n    assert not watch1.__ne__(watch2)\n    assert watch1.__ne__(watch_ne1)\n    assert watch1.__ne__(watch_ne2)\n\n\ndef test_observer__repr__():\n    observed_watch = ObservedWatch(\"/foobar\", recursive=True)\n    repr_str = \"<ObservedWatch: path='/foobar', is_recursive=True>\"\n    assert observed_watch.__repr__() == repr(observed_watch)\n    assert repr(observed_watch) == repr_str\n\n    observed_watch = ObservedWatch(\"/foobar\", recursive=False, event_filter=[FileOpenedEvent, FileModifiedEvent])\n    repr_str = \"<ObservedWatch: path='/foobar', is_recursive=False, event_filter=FileModifiedEvent|FileOpenedEvent>\"\n    assert observed_watch.__repr__() == repr(observed_watch)\n    assert repr(observed_watch) == repr_str\n\n\ndef test_event_emitter():\n    event_queue = EventQueue()\n    watch = ObservedWatch(\"/foobar\", recursive=True)\n    event_emitter = EventEmitter(event_queue, watch, timeout=1)\n    event_emitter.queue_event(FileModifiedEvent(\"/foobar/blah\"))\n\n\ndef test_event_dispatcher():\n    event = FileModifiedEvent(\"/foobar\")\n    watch = ObservedWatch(\"/path\", recursive=True)\n\n    class TestableEventDispatcher(EventDispatcher):\n        def dispatch_event(self, event, watch):\n            assert True\n\n    event_dispatcher = TestableEventDispatcher()\n    event_dispatcher.event_queue.put((event, watch))\n    event_dispatcher.start()\n    time.sleep(1)\n    event_dispatcher.stop()\n    event_dispatcher.join()\n\n\ndef test_observer_basic():\n    observer = BaseObserver(EventEmitter)\n    handler = LoggingEventHandler()\n\n    watch = observer.schedule(handler, \"/foobar\", recursive=True)\n    observer.add_handler_for_watch(handler, watch)\n    observer.add_handler_for_watch(handler, watch)\n    observer.remove_handler_for_watch(handler, watch)\n    with pytest.raises(KeyError):\n        observer.remove_handler_for_watch(handler, watch)\n    observer.unschedule(watch)\n    with pytest.raises(KeyError):\n        observer.unschedule(watch)\n\n    watch = observer.schedule(handler, \"/foobar\", recursive=True)\n    observer.event_queue.put((FileModifiedEvent(\"/foobar\"), watch))\n    observer.start()\n    time.sleep(1)\n    observer.unschedule_all()\n    observer.stop()\n    observer.join()\n"
  },
  {
    "path": "tests/test_observers_polling.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom queue import Empty, Queue\nfrom time import sleep\n\nimport pytest\n\nfrom watchdog.events import (\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n)\nfrom watchdog.observers.api import ObservedWatch\nfrom watchdog.observers.polling import PollingEmitter as Emitter\n\nfrom .shell import mkdir, mkdtemp, msize, mv, rm, touch\n\nSLEEP_TIME = 0.4\nTEMP_DIR = mkdtemp()\n\n\ndef p(*args):\n    \"\"\"\n    Convenience function to join the temporary directory path\n    with the provided arguments.\n    \"\"\"\n    return os.path.join(TEMP_DIR, *args)\n\n\n@pytest.fixture\ndef event_queue():\n    return Queue()\n\n\n@pytest.fixture\ndef emitter(event_queue):\n    watch = ObservedWatch(TEMP_DIR, recursive=True)\n    em = Emitter(event_queue, watch, timeout=0.2)\n    em.start()\n    yield em\n    em.stop()\n    em.join(5)\n\ndef test___init__(event_queue, emitter):\n    sleep(SLEEP_TIME)\n    mkdir(p(\"project\"))\n\n    sleep(SLEEP_TIME)\n    mkdir(p(\"project\", \"blah\"))\n\n    sleep(SLEEP_TIME)\n    touch(p(\"afile\"))\n\n    sleep(SLEEP_TIME)\n    touch(p(\"fromfile\"))\n\n    sleep(SLEEP_TIME)\n    mv(p(\"fromfile\"), p(\"project\", \"tofile\"))\n\n    sleep(SLEEP_TIME)\n    touch(p(\"afile\"))\n\n    sleep(SLEEP_TIME)\n    mv(p(\"project\", \"blah\"), p(\"project\", \"boo\"))\n\n    sleep(SLEEP_TIME)\n    rm(p(\"project\"), recursive=True)\n\n    sleep(SLEEP_TIME)\n    rm(p(\"afile\"))\n\n    sleep(SLEEP_TIME)\n    msize(p(\"bfile\"))\n\n    sleep(SLEEP_TIME)\n    rm(p(\"bfile\"))\n\n    sleep(SLEEP_TIME)\n    emitter.stop()\n\n    # What we need here for the tests to pass is a collection type\n    # that is:\n    #   * unordered\n    #   * non-unique\n    # A multiset! Python's collections.Counter class seems appropriate.\n    expected = {\n        DirModifiedEvent(p()),\n        DirCreatedEvent(p(\"project\")),\n        DirModifiedEvent(p(\"project\")),\n        DirCreatedEvent(p(\"project\", \"blah\")),\n        FileCreatedEvent(p(\"afile\")),\n        DirModifiedEvent(p()),\n        FileCreatedEvent(p(\"fromfile\")),\n        DirModifiedEvent(p()),\n        DirModifiedEvent(p()),\n        FileModifiedEvent(p(\"afile\")),\n        DirModifiedEvent(p(\"project\")),\n        DirModifiedEvent(p()),\n        FileDeletedEvent(p(\"project\", \"tofile\")),\n        DirDeletedEvent(p(\"project\", \"boo\")),\n        DirDeletedEvent(p(\"project\")),\n        DirModifiedEvent(p()),\n        FileDeletedEvent(p(\"afile\")),\n        DirModifiedEvent(p()),\n        FileCreatedEvent(p(\"bfile\")),\n        FileModifiedEvent(p(\"bfile\")),\n        DirModifiedEvent(p()),\n        FileDeletedEvent(p(\"bfile\")),\n    }\n\n    expected.add(FileMovedEvent(p(\"fromfile\"), p(\"project\", \"tofile\")))\n    expected.add(DirMovedEvent(p(\"project\", \"blah\"), p(\"project\", \"boo\")))\n\n    got = set()\n\n    while True:\n        try:\n            event, _ = event_queue.get_nowait()\n            got.add(event)\n        except Empty:\n            break\n\n    assert expected == got\n\n\ndef test_delete_watched_dir(event_queue, emitter):\n    rm(p(\"\"), recursive=True)\n\n    sleep(SLEEP_TIME)\n    emitter.stop()\n\n    # What we need here for the tests to pass is a collection type\n    # that is:\n    #   * unordered\n    #   * non-unique\n    # A multiset! Python's collections.Counter class seems appropriate.\n    expected = {\n        DirDeletedEvent(os.path.dirname(p(\"\"))),\n    }\n\n    got = set()\n\n    while True:\n        try:\n            event, _ = event_queue.get_nowait()\n            got.add(event)\n        except Empty:\n            break\n\n    assert expected == got\n"
  },
  {
    "path": "tests/test_observers_winapi.py",
    "content": "from __future__ import annotations\n\nimport os\nimport os.path\nfrom queue import Empty, Queue\nfrom time import sleep\n\nimport pytest\n\nfrom watchdog.events import DirCreatedEvent, DirMovedEvent\nfrom watchdog.observers.api import ObservedWatch\nfrom watchdog.utils import platform\n\nfrom .shell import mkdir, mkdtemp, mv, rm\n\n# make pytest aware this is windows only\nif not platform.is_windows():\n    pytest.skip(\"Windows only.\", allow_module_level=True)\n\nfrom watchdog.observers.read_directory_changes import WindowsApiEmitter\n\nSLEEP_TIME = 2\n\n# Path with non-ASCII\ntemp_dir = os.path.join(mkdtemp(), \"Strange \\N{SNOWMAN}\")\nos.makedirs(temp_dir)\n\n\ndef p(*args):\n    \"\"\"\n    Convenience function to join the temporary directory path\n    with the provided arguments.\n    \"\"\"\n    return os.path.join(temp_dir, *args)\n\n\n@pytest.fixture\ndef event_queue():\n    return Queue()\n\n\n@pytest.fixture\ndef emitter(event_queue):\n    watch = ObservedWatch(temp_dir, recursive=True)\n    em = WindowsApiEmitter(event_queue, watch, timeout=0.2)\n    yield em\n    em.stop()\n\n\ndef test___init__(event_queue, emitter):\n    emitter.start()\n    sleep(SLEEP_TIME)\n    mkdir(p(\"fromdir\"))\n\n    sleep(SLEEP_TIME)\n    mv(p(\"fromdir\"), p(\"todir\"))\n\n    sleep(SLEEP_TIME)\n    emitter.stop()\n    sleep(SLEEP_TIME)  # time for background thread to exit\n\n    # What we need here for the tests to pass is a collection type\n    # that is:\n    #   * unordered\n    #   * non-unique\n    # A multiset! Python's collections.Counter class seems appropriate.\n    expected = {\n        DirCreatedEvent(p(\"fromdir\")),\n        DirMovedEvent(p(\"fromdir\"), p(\"todir\")),\n    }\n\n    got = set()\n\n    while True:\n        try:\n            event, _ = event_queue.get_nowait()\n        except Empty:\n            break\n        else:\n            if event.event_type == \"modified\":\n                # On Windows can get one or two modified events.  Ignore\n                # them to make test more deterministic.\n                continue\n            got.add(event)\n\n    assert expected == got\n\n\ndef test_root_deleted(event_queue, emitter):\n    r\"\"\"Test the event got when removing the watched folder.\n    The regression to prevent is:\n\n        Exception in thread Thread-1:\n        Traceback (most recent call last):\n        File \"watchdog\\observers\\winapi.py\", line 333, in read_directory_changes\n            ctypes.byref(nbytes), None, None)\n        File \"watchdog\\observers\\winapi.py\", line 105, in _errcheck_bool\n            raise ctypes.WinError()\n        PermissionError: [WinError 5] Access refused.\n\n        During handling of the above exception, another exception occurred:\n\n        Traceback (most recent call last):\n        File \"C:\\Python37-32\\lib\\threading.py\", line 926, in _bootstrap_inner\n            self.run()\n        File \"watchdog\\observers\\api.py\", line 145, in run\n            self.queue_events(self.timeout)\n        File \"watchdog\\observers\\read_directory_changes.py\", line 76, in queue_events\n            winapi_events = self._read_events()\n        File \"watchdog\\observers\\read_directory_changes.py\", line 73, in _read_events\n            return read_events(self._whandle, self.watch.path, recursive=self.watch.is_recursive)\n        File \"watchdog\\observers\\winapi.py\", line 387, in read_events\n            buf, nbytes = read_directory_changes(handle, path, recursive=recursive)\n        File \"watchdog\\observers\\winapi.py\", line 340, in read_directory_changes\n            return _generate_observed_path_deleted_event()\n        File \"watchdog\\observers\\winapi.py\", line 298, in _generate_observed_path_deleted_event\n            event = FileNotifyInformation(0, FILE_ACTION_DELETED_SELF, len(path), path.value)\n        TypeError: expected bytes, str found\n    \"\"\"\n\n    emitter.start()\n    sleep(SLEEP_TIME)\n\n    # This should not fail\n    rm(p(), recursive=True)\n    sleep(SLEEP_TIME)\n\n    # The emitter is automatically stopped, with no error\n    assert not emitter.should_keep_running()\n"
  },
  {
    "path": "tests/test_pattern_matching_event_handler.py",
    "content": "from __future__ import annotations\n\nfrom watchdog.events import (\n    EVENT_TYPE_CREATED,\n    EVENT_TYPE_DELETED,\n    EVENT_TYPE_MODIFIED,\n    EVENT_TYPE_MOVED,\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    PatternMatchingEventHandler,\n)\nfrom watchdog.utils.patterns import filter_paths\n\npath_1 = \"/path/xyz\"\npath_2 = \"/path/abc\"\ng_allowed_patterns = [\"**/*.py\", \"**/*.txt\"]\ng_ignore_patterns = [\"**/*.foo\"]\n\n\ndef assert_patterns(event):\n    paths = [event.src_path, event.dest_path] if hasattr(event, \"dest_path\") else [event.src_path]\n    filtered_paths = filter_paths(\n        paths,\n        included_patterns=[\"**/*.py\", \"**/*.txt\"],\n        excluded_patterns=[\"**/*.pyc\"],\n        case_sensitive=False,\n    )\n    assert filtered_paths\n\n\ndef test_dispatch():\n    # Utilities.\n    patterns = [\"**/*.py\", \"**/*.txt\"]\n    ignore_patterns = [\"**/*.pyc\"]\n\n    dir_del_event_match = DirDeletedEvent(\"/path/blah.py\")\n    dir_del_event_not_match = DirDeletedEvent(\"/path/foobar\")\n    dir_del_event_ignored = DirDeletedEvent(\"/path/foobar.pyc\")\n    file_del_event_match = FileDeletedEvent(\"/path/blah.txt\")\n    file_del_event_not_match = FileDeletedEvent(\"/path/foobar\")\n    file_del_event_ignored = FileDeletedEvent(\"/path/blah.pyc\")\n\n    dir_cre_event_match = DirCreatedEvent(\"/path/blah.py\")\n    dir_cre_event_not_match = DirCreatedEvent(\"/path/foobar\")\n    dir_cre_event_ignored = DirCreatedEvent(\"/path/foobar.pyc\")\n    file_cre_event_match = FileCreatedEvent(\"/path/blah.txt\")\n    file_cre_event_not_match = FileCreatedEvent(\"/path/foobar\")\n    file_cre_event_ignored = FileCreatedEvent(\"/path/blah.pyc\")\n\n    dir_mod_event_match = DirModifiedEvent(\"/path/blah.py\")\n    dir_mod_event_not_match = DirModifiedEvent(\"/path/foobar\")\n    dir_mod_event_ignored = DirModifiedEvent(\"/path/foobar.pyc\")\n    file_mod_event_match = FileModifiedEvent(\"/path/blah.txt\")\n    file_mod_event_not_match = FileModifiedEvent(\"/path/foobar\")\n    file_mod_event_ignored = FileModifiedEvent(\"/path/blah.pyc\")\n\n    dir_mov_event_match = DirMovedEvent(\"/path/blah.py\", \"/path/blah\")\n    dir_mov_event_not_match = DirMovedEvent(\"/path/foobar\", \"/path/blah\")\n    dir_mov_event_ignored = DirMovedEvent(\"/path/foobar.pyc\", \"/path/blah\")\n    file_mov_event_match = FileMovedEvent(\"/path/blah.txt\", \"/path/blah\")\n    file_mov_event_not_match = FileMovedEvent(\"/path/foobar\", \"/path/blah\")\n    file_mov_event_ignored = FileMovedEvent(\"/path/blah.pyc\", \"/path/blah\")\n\n    all_dir_events = [\n        dir_mod_event_match,\n        dir_mod_event_not_match,\n        dir_mod_event_ignored,\n        dir_del_event_match,\n        dir_del_event_not_match,\n        dir_del_event_ignored,\n        dir_cre_event_match,\n        dir_cre_event_not_match,\n        dir_cre_event_ignored,\n        dir_mov_event_match,\n        dir_mov_event_not_match,\n        dir_mov_event_ignored,\n    ]\n    all_file_events = [\n        file_mod_event_match,\n        file_mod_event_not_match,\n        file_mod_event_ignored,\n        file_del_event_match,\n        file_del_event_not_match,\n        file_del_event_ignored,\n        file_cre_event_match,\n        file_cre_event_not_match,\n        file_cre_event_ignored,\n        file_mov_event_match,\n        file_mov_event_not_match,\n        file_mov_event_ignored,\n    ]\n    all_events = all_file_events + all_dir_events\n\n    def assert_check_directory(handler, event):\n        assert not (handler.ignore_directories and event.is_directory)\n\n    class TestableEventHandler(PatternMatchingEventHandler):\n        def on_any_event(self, event):\n            assert_check_directory(self, event)\n\n        def on_modified(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_MODIFIED\n            assert_patterns(event)\n\n        def on_deleted(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_DELETED\n            assert_patterns(event)\n\n        def on_moved(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_MOVED\n            assert_patterns(event)\n\n        def on_created(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_CREATED\n            assert_patterns(event)\n\n    no_dirs_handler = TestableEventHandler(patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=True)\n    handler = TestableEventHandler(patterns=patterns, ignore_patterns=ignore_patterns)\n\n    for event in all_events:\n        no_dirs_handler.dispatch(event)\n    for event in all_events:\n        handler.dispatch(event)\n\n\ndef test_handler():\n    handler1 = PatternMatchingEventHandler(\n        patterns=g_allowed_patterns,\n        ignore_patterns=g_ignore_patterns,\n        ignore_directories=True,\n    )\n    handler2 = PatternMatchingEventHandler(patterns=g_allowed_patterns, ignore_patterns=g_ignore_patterns)\n    assert handler1.patterns == g_allowed_patterns\n    assert handler1.ignore_patterns == g_ignore_patterns\n    assert handler1.ignore_directories\n    assert not handler2.ignore_directories\n\n\ndef test_ignore_directories():\n    handler1 = PatternMatchingEventHandler(\n        patterns=g_allowed_patterns,\n        ignore_patterns=g_ignore_patterns,\n        ignore_directories=True,\n    )\n    handler2 = PatternMatchingEventHandler(patterns=g_allowed_patterns, ignore_patterns=g_ignore_patterns)\n    assert handler1.ignore_directories\n    assert not handler2.ignore_directories\n\n\ndef test_ignore_patterns():\n    handler1 = PatternMatchingEventHandler(\n        patterns=g_allowed_patterns,\n        ignore_patterns=g_ignore_patterns,\n        ignore_directories=True,\n    )\n    assert handler1.ignore_patterns == g_ignore_patterns\n\n\ndef test_patterns():\n    handler1 = PatternMatchingEventHandler(\n        patterns=g_allowed_patterns,\n        ignore_patterns=g_ignore_patterns,\n        ignore_directories=True,\n    )\n    assert handler1.patterns == g_allowed_patterns\n"
  },
  {
    "path": "tests/test_patterns.py",
    "content": "from __future__ import annotations\n\nimport pytest\n\nfrom watchdog.utils.patterns import _match_path, filter_paths, match_any_paths\n\n\n@pytest.mark.parametrize(\n    (\"raw_path\", \"included_patterns\", \"excluded_patterns\", \"case_sensitive\", \"expected\"),\n    [\n        (\"/users/gorakhargosh/foobar.py\", {\"**/*.py\"}, {\"**/*.PY\"}, True, True),\n        (\"/users/gorakhargosh/foobar.py\", {\"*.py\"}, {\"*.PY\"}, True, False),\n        (\"/users/gorakhargosh/\", {\"*.py\"}, {\"*.txt\"}, False, False),\n        (\"/users/gorakhargosh/foobar.py\", {\"*.py\"}, {\"*.PY\"}, False, ValueError),\n    ],\n)\ndef test_match_path(raw_path, included_patterns, excluded_patterns, case_sensitive, expected):\n    if expected is ValueError:\n        with pytest.raises(expected):\n            _match_path(raw_path, included_patterns, excluded_patterns, case_sensitive=case_sensitive)\n    else:\n        assert _match_path(raw_path, included_patterns, excluded_patterns, case_sensitive=case_sensitive) is expected\n\n\n@pytest.mark.parametrize(\n    (\"included_patterns\", \"excluded_patterns\", \"case_sensitive\", \"expected\"),\n    [\n        (None, None, True, None),\n        (None, None, False, None),\n        (\n            [\"**/*.py\", \"**/*.conf\"],\n            [\"**/*.status\"],\n            True,\n            {\"/users/gorakhargosh/foobar.py\", \"/etc/pdnsd.conf\"},\n        ),\n    ],\n)\ndef test_filter_paths(included_patterns, excluded_patterns, case_sensitive, expected):\n    pathnames = {\n        \"/users/gorakhargosh/foobar.py\",\n        \"/var/cache/pdnsd.status\",\n        \"/etc/pdnsd.conf\",\n        \"/usr/local/bin/python\",\n    }\n    actual = set(\n        filter_paths(\n            pathnames,\n            included_patterns=included_patterns,\n            excluded_patterns=excluded_patterns,\n            case_sensitive=case_sensitive,\n        )\n    )\n    assert actual == expected if expected else pathnames\n\n\n@pytest.mark.parametrize(\n    (\"included_patterns\", \"excluded_patterns\", \"case_sensitive\", \"expected\"),\n    [\n        (None, None, True, True),\n        (None, None, False, True),\n        ([\"**/*py\", \"**/*.conf\"], [\"**/*.status\"], True, True),\n        ([\"**/*.txt\"], None, False, False),\n        ([\"**/*.txt\"], None, True, False),\n    ],\n)\ndef test_match_any_paths(included_patterns, excluded_patterns, case_sensitive, expected):\n    pathnames = {\n        \"/users/gorakhargosh/foobar.py\",\n        \"/var/cache/pdnsd.status\",\n        \"/etc/pdnsd.conf\",\n        \"/usr/local/bin/python\",\n    }\n    assert (\n        match_any_paths(\n            pathnames,\n            included_patterns=included_patterns,\n            excluded_patterns=excluded_patterns,\n            case_sensitive=case_sensitive,\n        )\n        == expected\n    )\n"
  },
  {
    "path": "tests/test_regex_matching_event_handler.py",
    "content": "from __future__ import annotations\n\nfrom watchdog.events import (\n    EVENT_TYPE_CREATED,\n    EVENT_TYPE_DELETED,\n    EVENT_TYPE_MODIFIED,\n    EVENT_TYPE_MOVED,\n    DirCreatedEvent,\n    DirDeletedEvent,\n    DirModifiedEvent,\n    DirMovedEvent,\n    FileCreatedEvent,\n    FileDeletedEvent,\n    FileModifiedEvent,\n    FileMovedEvent,\n    LoggingEventHandler,\n    RegexMatchingEventHandler,\n)\n\npath_1 = \"/path/xyz\"\npath_2 = \"/path/abc\"\ng_allowed_regexes = [r\".*\\.py\", r\".*\\.txt\"]\ng_allowed_str_regexes = r\".*\\.py\"\ng_ignore_regexes = [r\".*\\.pyc\"]\n\n\ndef test_dispatch():\n    # Utilities.\n    regexes = [r\".*\\.py\", r\".*\\.txt\"]\n    ignore_regexes = [r\".*\\.pyc\"]\n\n    def assert_regexes(handler, event):\n        paths = [event.src_path, event.dest_path] if hasattr(event, \"dest_path\") else [event.src_path]\n        filtered_paths = set()\n        for p in paths:\n            if any(r.match(p) for r in handler.regexes):\n                filtered_paths.add(p)\n        assert filtered_paths\n\n    dir_del_event_match = DirDeletedEvent(\"/path/blah.py\")\n    dir_del_event_not_match = DirDeletedEvent(\"/path/foobar\")\n    dir_del_event_ignored = DirDeletedEvent(\"/path/foobar.pyc\")\n    file_del_event_match = FileDeletedEvent(\"/path/blah.txt\")\n    file_del_event_not_match = FileDeletedEvent(\"/path/foobar\")\n    file_del_event_ignored = FileDeletedEvent(\"/path/blah.pyc\")\n\n    dir_cre_event_match = DirCreatedEvent(\"/path/blah.py\")\n    dir_cre_event_not_match = DirCreatedEvent(\"/path/foobar\")\n    dir_cre_event_ignored = DirCreatedEvent(\"/path/foobar.pyc\")\n    file_cre_event_match = FileCreatedEvent(\"/path/blah.txt\")\n    file_cre_event_not_match = FileCreatedEvent(\"/path/foobar\")\n    file_cre_event_ignored = FileCreatedEvent(\"/path/blah.pyc\")\n\n    dir_mod_event_match = DirModifiedEvent(\"/path/blah.py\")\n    dir_mod_event_not_match = DirModifiedEvent(\"/path/foobar\")\n    dir_mod_event_ignored = DirModifiedEvent(\"/path/foobar.pyc\")\n    file_mod_event_match = FileModifiedEvent(\"/path/blah.txt\")\n    file_mod_event_not_match = FileModifiedEvent(\"/path/foobar\")\n    file_mod_event_ignored = FileModifiedEvent(\"/path/blah.pyc\")\n\n    dir_mov_event_match = DirMovedEvent(\"/path/blah.py\", \"/path/blah\")\n    dir_mov_event_not_match = DirMovedEvent(\"/path/foobar\", \"/path/blah\")\n    dir_mov_event_ignored = DirMovedEvent(\"/path/foobar.pyc\", \"/path/blah\")\n    file_mov_event_match = FileMovedEvent(\"/path/blah.txt\", \"/path/blah\")\n    file_mov_event_not_match = FileMovedEvent(\"/path/foobar\", \"/path/blah\")\n    file_mov_event_ignored = FileMovedEvent(\"/path/blah.pyc\", \"/path/blah\")\n\n    all_dir_events = [\n        dir_mod_event_match,\n        dir_mod_event_not_match,\n        dir_mod_event_ignored,\n        dir_del_event_match,\n        dir_del_event_not_match,\n        dir_del_event_ignored,\n        dir_cre_event_match,\n        dir_cre_event_not_match,\n        dir_cre_event_ignored,\n        dir_mov_event_match,\n        dir_mov_event_not_match,\n        dir_mov_event_ignored,\n    ]\n    all_file_events = [\n        file_mod_event_match,\n        file_mod_event_not_match,\n        file_mod_event_ignored,\n        file_del_event_match,\n        file_del_event_not_match,\n        file_del_event_ignored,\n        file_cre_event_match,\n        file_cre_event_not_match,\n        file_cre_event_ignored,\n        file_mov_event_match,\n        file_mov_event_not_match,\n        file_mov_event_ignored,\n    ]\n    all_events = all_file_events + all_dir_events\n\n    def assert_check_directory(handler, event):\n        assert not (handler.ignore_directories and event.is_directory)\n\n    class TestableEventHandler(RegexMatchingEventHandler):\n        def on_any_event(self, event):\n            assert_check_directory(self, event)\n\n        def on_modified(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_MODIFIED\n            assert_regexes(self, event)\n\n        def on_deleted(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_DELETED\n            assert_regexes(self, event)\n\n        def on_moved(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_MOVED\n            assert_regexes(self, event)\n\n        def on_created(self, event):\n            assert_check_directory(self, event)\n            assert event.event_type == EVENT_TYPE_CREATED\n            assert_regexes(self, event)\n\n    no_dirs_handler = TestableEventHandler(regexes=regexes, ignore_regexes=ignore_regexes, ignore_directories=True)\n    handler = TestableEventHandler(regexes=regexes, ignore_regexes=ignore_regexes)\n\n    for event in all_events:\n        no_dirs_handler.dispatch(event)\n    for event in all_events:\n        handler.dispatch(event)\n\n\ndef test_handler():\n    handler1 = RegexMatchingEventHandler(\n        regexes=g_allowed_regexes,\n        ignore_regexes=g_ignore_regexes,\n        ignore_directories=True,\n    )\n    handler2 = RegexMatchingEventHandler(regexes=g_allowed_regexes, ignore_regexes=g_ignore_regexes)\n    assert [r.pattern for r in handler1.regexes] == g_allowed_regexes\n    assert [r.pattern for r in handler1.ignore_regexes] == g_ignore_regexes\n    assert handler1.ignore_directories\n    assert not handler2.ignore_directories\n\n\ndef test_ignore_directories():\n    handler1 = RegexMatchingEventHandler(\n        regexes=g_allowed_regexes,\n        ignore_regexes=g_ignore_regexes,\n        ignore_directories=True,\n    )\n    handler2 = RegexMatchingEventHandler(regexes=g_allowed_regexes, ignore_regexes=g_ignore_regexes)\n    assert handler1.ignore_directories\n    assert not handler2.ignore_directories\n\n\ndef test_ignore_regexes():\n    handler1 = RegexMatchingEventHandler(\n        regexes=g_allowed_regexes,\n        ignore_regexes=g_ignore_regexes,\n        ignore_directories=True,\n    )\n    assert [r.pattern for r in handler1.ignore_regexes] == g_ignore_regexes\n\n\ndef test_regexes():\n    handler1 = RegexMatchingEventHandler(\n        regexes=g_allowed_regexes,\n        ignore_regexes=g_ignore_regexes,\n        ignore_directories=True,\n    )\n    assert [r.pattern for r in handler1.regexes] == g_allowed_regexes\n\n\ndef test_str_regexes():\n    handler1 = RegexMatchingEventHandler(\n        regexes=g_allowed_str_regexes,\n        ignore_regexes=g_ignore_regexes,\n        case_sensitive=True,\n    )\n    assert [r.pattern for r in handler1.regexes] == [g_allowed_str_regexes]\n\n\ndef test_logging_event_handler_dispatch():\n    class _TestableEventHandler(LoggingEventHandler):\n        def on_any_event(self, event):\n            pass\n\n        def on_modified(self, event):\n            super().on_modified(event)\n            assert event.event_type == EVENT_TYPE_MODIFIED\n\n        def on_deleted(self, event):\n            super().on_deleted(event)\n            assert event.event_type == EVENT_TYPE_DELETED\n\n        def on_moved(self, event):\n            super().on_moved(event)\n            assert event.event_type == EVENT_TYPE_MOVED\n\n        def on_created(self, event):\n            super().on_created(event)\n            assert event.event_type == EVENT_TYPE_CREATED\n\n    # Utilities.\n    dir_del_event = DirDeletedEvent(\"/path/blah.py\")\n    file_del_event = FileDeletedEvent(\"/path/blah.txt\")\n    dir_cre_event = DirCreatedEvent(\"/path/blah.py\")\n    file_cre_event = FileCreatedEvent(\"/path/blah.txt\")\n    dir_mod_event = DirModifiedEvent(\"/path/blah.py\")\n    file_mod_event = FileModifiedEvent(\"/path/blah.txt\")\n    dir_mov_event = DirMovedEvent(\"/path/blah.py\", \"/path/blah\")\n    file_mov_event = FileMovedEvent(\"/path/blah.txt\", \"/path/blah\")\n\n    all_events = [\n        dir_mod_event,\n        dir_del_event,\n        dir_cre_event,\n        dir_mov_event,\n        file_mod_event,\n        file_del_event,\n        file_cre_event,\n        file_mov_event,\n    ]\n\n    handler = _TestableEventHandler()\n    for event in all_events:\n        handler.dispatch(event)\n"
  },
  {
    "path": "tests/test_skip_repeats_queue.py",
    "content": "from __future__ import annotations\n\nfrom watchdog import events\nfrom watchdog.utils.bricks import SkipRepeatsQueue\n\n\ndef test_basic_queue():\n    q = SkipRepeatsQueue()\n\n    e1 = (2, \"fred\")\n    e2 = (2, \"george\")\n    e3 = (4, \"sally\")\n\n    q.put(e1)\n    q.put(e2)\n    q.put(e3)\n\n    assert e1 == q.get()\n    assert e2 == q.get()\n    assert e3 == q.get()\n    assert q.empty()\n\n\ndef test_allow_nonconsecutive():\n    q = SkipRepeatsQueue()\n\n    e1 = (2, \"fred\")\n    e2 = (2, \"george\")\n\n    q.put(e1)\n    q.put(e2)\n    q.put(e1)  # repeat the first entry\n\n    assert e1 == q.get()\n    assert e2 == q.get()\n    assert e1 == q.get()\n    assert q.empty()\n\n\ndef test_put_with_watchdog_events():\n    # FileSystemEvent.__ne__() uses the key property without\n    # doing any type checking. Since _last_item is set to\n    # None in __init__(), an AttributeError is raised when\n    # FileSystemEvent.__ne__() tries to use None.key\n    queue = SkipRepeatsQueue()\n    dummy_file = \"dummy.txt\"\n    event = events.FileCreatedEvent(dummy_file)\n    queue.put(event)\n    assert queue.get() is event\n\n\ndef test_prevent_consecutive():\n    q = SkipRepeatsQueue()\n\n    e1 = (2, \"fred\")\n    e2 = (2, \"george\")\n\n    q.put(e1)\n    q.put(e1)  # repeat the first entry (this shouldn't get added)\n    q.put(e2)\n\n    assert e1 == q.get()\n    assert e2 == q.get()\n    assert q.empty()\n\n\ndef test_consecutives_allowed_across_empties():\n    q = SkipRepeatsQueue()\n\n    e1 = (2, \"fred\")\n\n    q.put(e1)\n    q.put(e1)  # repeat the first entry (this shouldn't get added)\n\n    assert e1 == q.get()\n    assert q.empty()\n\n    q.put(e1)  # this repeat is allowed because 'last' added is now gone from queue\n    assert e1 == q.get()\n    assert q.empty()\n"
  },
  {
    "path": "tests/test_snapshot_diff.py",
    "content": "from __future__ import annotations\n\nimport errno\nimport os\nimport pickle\nimport time\nfrom unittest.mock import patch\n\nfrom watchdog.utils import platform\nfrom watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff, EmptyDirectorySnapshot\n\nfrom .shell import mkdir, mv, rm, touch\n\n\ndef wait():\n    \"\"\"\n    Wait long enough for file/folder mtime to change. This is needed\n    to be able to detected modifications.\n    \"\"\"\n    if platform.is_darwin() or platform.is_windows():\n        # on macOS resolution of stat.mtime is only 1 second\n        time.sleep(1.5)\n    else:\n        time.sleep(0.5)\n\n\ndef test_pickle(p):\n    \"\"\"It should be possible to pickle a snapshot.\"\"\"\n    mkdir(p(\"dir1\"))\n    snasphot = DirectorySnapshot(p(\"dir1\"))\n    pickle.dumps(snasphot)\n\n\ndef test_move_to(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n    ref = DirectorySnapshot(p(\"dir2\"))\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"dir2\")))\n    assert diff.files_created == [p(\"dir2\", \"b\")]\n\n\ndef test_move_to_with_context_manager(p):\n    mkdir(p(\"dir1\"))\n    touch(p(\"dir1\", \"a\"))\n    mkdir(p(\"dir2\"))\n\n    dir1_cm = DirectorySnapshotDiff.ContextManager(p(\"dir1\"))\n    dir2_cm = DirectorySnapshotDiff.ContextManager(p(\"dir2\"))\n    with dir1_cm, dir2_cm:\n        mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n\n    assert dir1_cm.diff.files_deleted == [p(\"dir1\", \"a\")]\n    assert dir2_cm.diff.files_created == [p(\"dir2\", \"b\")]\n\n\ndef test_move_from(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n    ref = DirectorySnapshot(p(\"dir1\"))\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"dir1\")))\n    assert diff.files_deleted == [p(\"dir1\", \"a\")]\n\n\ndef test_move_internal(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n    ref = DirectorySnapshot(p(\"\"))\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"\")))\n    assert diff.files_moved == [(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))]\n    assert diff.files_created == []\n    assert diff.files_deleted == []\n\n\ndef test_move_replace(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n    touch(p(\"dir2\", \"b\"))\n    ref = DirectorySnapshot(p(\"\"))\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"\")))\n    assert diff.files_moved == [(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))]\n    assert diff.files_deleted == [p(\"dir2\", \"b\")]\n    assert diff.files_created == []\n\n\ndef test_dir_modify_on_create(p):\n    ref = DirectorySnapshot(p(\"\"))\n    wait()\n    touch(p(\"a\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"\")))\n    assert diff.dirs_modified == [p(\"\")]\n\n\ndef test_dir_modify_on_move(p):\n    mkdir(p(\"dir1\"))\n    mkdir(p(\"dir2\"))\n    touch(p(\"dir1\", \"a\"))\n    ref = DirectorySnapshot(p(\"\"))\n    wait()\n    mv(p(\"dir1\", \"a\"), p(\"dir2\", \"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"\")))\n    assert set(diff.dirs_modified) == {p(\"dir1\"), p(\"dir2\")}\n\n\ndef test_detect_modify_for_moved_files(p):\n    touch(p(\"a\"))\n    ref = DirectorySnapshot(p(\"\"))\n    wait()\n    touch(p(\"a\"))\n    mv(p(\"a\"), p(\"b\"))\n    diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(\"\")))\n    assert diff.files_moved == [(p(\"a\"), p(\"b\"))]\n    assert diff.files_modified == [p(\"a\")]\n\n\ndef test_replace_dir_with_file(p):\n    # Replace a dir with a file of the same name just before the normal listdir\n    # call and ensure it doesn't cause an exception\n\n    def listdir_fcn(path):\n        if path == p(\"root\", \"dir\"):\n            rm(path, recursive=True)\n            touch(path)\n        return os.scandir(path)\n\n    mkdir(p(\"root\"))\n    mkdir(p(\"root\", \"dir\"))\n\n    # Should NOT raise an OSError (ENOTDIR)\n    DirectorySnapshot(p(\"root\"), listdir=listdir_fcn)\n\n\ndef test_permission_error(p):\n    # Test that unreadable folders are not raising exceptions\n    mkdir(p(\"a\", \"b\", \"c\"), parents=True)\n\n    ref = DirectorySnapshot(p(\"\"))\n    walk_orig = DirectorySnapshot.walk\n\n    def walk(self, root):\n        \"\"\"Generate a permission error on folder \"a/b\".\"\"\"\n        # Generate the permission error\n        if root.startswith(p(\"a\", \"b\")):\n            raise OSError(errno.EACCES, os.strerror(errno.EACCES))\n\n        # Mimic the original method\n        yield from walk_orig(self, root)\n\n    with patch.object(DirectorySnapshot, \"walk\", new=walk):\n        # Should NOT raise an OSError (EACCES)\n        new_snapshot = DirectorySnapshot(p(\"\"))\n\n    diff = DirectorySnapshotDiff(ref, new_snapshot)\n    assert repr(diff)\n    assert len(diff) == 1\n\n    # Children of a/b/ are no more accessible and so removed in the new snapshot\n    assert diff.dirs_deleted == [(p(\"a\", \"b\", \"c\"))]\n\n\ndef test_ignore_device(p):\n    # Create a file and take a snapshot.\n    touch(p(\"file\"))\n    ref = DirectorySnapshot(p(\"\"))\n    wait()\n\n    inode_orig = DirectorySnapshot.inode\n\n    inode_times = 0\n\n    def inode(self, path):\n        # This function will always return a different device_id,\n        # even for the same file.\n        nonlocal inode_times\n        result = inode_orig(self, path)\n        inode_times += 1\n        return result[0], result[1] + inode_times\n\n    # Set the custom inode function.\n    with patch.object(DirectorySnapshot, \"inode\", new=inode):\n        # If we make the diff of the same directory, since by default the\n        # DirectorySnapshotDiff compares the snapshots using the device_id (and it will\n        # be different), it thinks that the same file has been deleted and created again.\n        snapshot = DirectorySnapshot(p(\"\"))\n        diff_with_device = DirectorySnapshotDiff(ref, snapshot)\n        assert diff_with_device.files_deleted == [(p(\"file\"))]\n        assert diff_with_device.files_created == [(p(\"file\"))]\n\n        # Otherwise, if we choose to ignore the device, the file will not be detected as\n        # deleted and re-created.\n        snapshot = DirectorySnapshot(p(\"\"))\n        diff_without_device = DirectorySnapshotDiff(ref, snapshot, ignore_device=True)\n        assert not len(diff_without_device)\n        assert diff_without_device.files_deleted == []\n        assert diff_without_device.files_created == []\n\n\ndef test_empty_snapshot(p):\n    # Create a file and declare a DirectorySnapshot and a EmptyDirectorySnapshot.\n    # When we make the diff, although both objects were declared with the same items on\n    # the directory, the file and directories created BEFORE the DirectorySnapshot will\n    # be detected as newly created.\n\n    touch(p(\"a\"))\n    mkdir(p(\"b\", \"c\"), parents=True)\n    ref = DirectorySnapshot(p(\"\"))\n    empty = EmptyDirectorySnapshot()\n    assert repr(empty) == \"{}\"\n\n    diff = DirectorySnapshotDiff(empty, ref)\n    assert len(diff) == 4\n    assert diff.files_created == [p(\"a\")]\n    assert sorted(diff.dirs_created) == sorted([p(\"\"), p(\"b\"), p(\"b\", \"c\")])\n"
  },
  {
    "path": "tests/utils.py",
    "content": "from __future__ import annotations\n\nimport dataclasses\nimport os\nimport subprocess\nimport sys\nfrom queue import Empty, Queue\nfrom typing import Any, Protocol\n\nfrom watchdog.events import FileSystemEvent\nfrom watchdog.observers.api import EventEmitter, ObservedWatch\nfrom watchdog.utils import platform\n\nEmitter: type[EventEmitter]\n\nif platform.is_linux():\n    from watchdog.observers.inotify import InotifyEmitter as Emitter\n    from watchdog.observers.inotify import InotifyFullEmitter\nelif platform.is_darwin():\n    from watchdog.observers.fsevents import FSEventsEmitter as Emitter\nelif platform.is_windows():\n    from watchdog.observers.read_directory_changes import WindowsApiEmitter as Emitter\nelif platform.is_bsd():\n    from watchdog.observers.kqueue import KqueueEmitter as Emitter\n\n\nclass P(Protocol):\n    def __call__(self, *args: str) -> str: ...\n\n\nclass StartWatching(Protocol):\n    def __call__(\n        self,\n        *,\n        path: bytes | str | None = ...,\n        use_full_emitter: bool = ...,\n        recursive: bool = ...,\n    ) -> EventEmitter: ...\n\n\nclass ExpectEvent(Protocol):\n    def __call__(self, expected_event: FileSystemEvent, *, timeout: float = ...) -> None: ...\n\n\nTestEventQueue = Queue[tuple[FileSystemEvent, ObservedWatch]]\n\n\n@dataclasses.dataclass()\nclass Helper:\n    tmp: str\n    emitters: list[EventEmitter] = dataclasses.field(default_factory=list)\n    event_queue: TestEventQueue = dataclasses.field(default_factory=Queue)\n\n    def joinpath(self, *args: str) -> str:\n        return os.path.join(self.tmp, *args)\n\n    def start_watching(\n        self,\n        *,\n        path: bytes | str | None = None,\n        use_full_emitter: bool = False,\n        recursive: bool = True,\n    ) -> EventEmitter:\n        # TODO: check if other platforms expect the trailing slash (e.g. `p('')`)\n        path = self.tmp if path is None else path\n\n        watcher = ObservedWatch(path, recursive=recursive)\n        emitter_cls = InotifyFullEmitter if platform.is_linux() and use_full_emitter else Emitter\n        emitter = emitter_cls(self.event_queue, watcher)\n\n        if platform.is_darwin():\n            # TODO: I think this could be better...  .suppress_history should maybe\n            #       become a common attribute.\n            from watchdog.observers.fsevents import FSEventsEmitter\n\n            assert isinstance(emitter, FSEventsEmitter)\n            emitter.suppress_history = True\n\n        self.emitters.append(emitter)\n        emitter.start()\n\n        return emitter\n\n    def events_checker(self, *, verbose: bool = False) -> _EventsChecker:\n        \"\"\"Utility function to create a new event checker instance.  Use as a context\n        manager and call add() to add events to check for.  The events in the queue\n        will automatically be checked when the \"with\" block is exited.\n\n        If `verbose` is true, print details of expected and found events to stdout (to assist with\n        developing or debugging tests).\n        \"\"\"\n        return _EventsChecker(self, verbose=verbose)\n\n    def expect_event(self, expected_event: FileSystemEvent, timeout: float = 2) -> None:\n        \"\"\"Utility function to wait up to `timeout` seconds for an `event_type` for `path` to show up in the queue.\n\n        Provides some robustness for the otherwise flaky nature of asynchronous notifications.\n        \"\"\"\n        assert self.event_queue.get(timeout=timeout)[0] == expected_event\n\n    def close(self) -> None:\n        for emitter in self.emitters:\n            emitter.stop()\n\n        for emitter in self.emitters:\n            if emitter.is_alive():\n                emitter.join(5)\n\n        alive = [emitter.is_alive() for emitter in self.emitters]\n        self.emitters = []\n        assert alive == [False] * len(alive)\n\n\ndef run_isolated_test(path):\n    isolated_test_prefix = os.path.join(\"tests\", \"isolated\")\n    path = os.path.abspath(os.path.join(isolated_test_prefix, path))\n\n    src_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"src\")\n    new_env = os.environ.copy()\n    new_env[\"PYTHONPATH\"] = os.pathsep.join([*sys.path, src_dir])\n\n    new_argv = [sys.executable, path]\n\n    p = subprocess.Popen(\n        new_argv,\n        env=new_env,\n    )\n\n    # in case test goes haywire, don't let it run forever\n    timeout = 10\n    try:\n        p.communicate(timeout=timeout)\n    except subprocess.TimeoutExpired:\n        p.kill()\n        raise\n\n    assert p.returncode == 0\n\n\nclass EventsChecker(Protocol):\n    def __call__(self) -> _EventsChecker: ...\n\n\n@dataclasses.dataclass()\nclass _ExpectedEvent:\n    \"\"\"Specifies what kind of event we are expecting.  The `expected_class` is required,\n    everything else is optional (not checked if it is None).\n    \"\"\"\n\n    expected_class: type\n    src_path: str | None = None\n    dest_path: str | None = None\n\n\nclass _EventsChecker:\n\n    expected_events: list[_ExpectedEvent]\n\n    def __init__(self, helper: Helper, *, verbose: bool = False):\n        self.tmp = helper.tmp\n        self.event_queue = helper.event_queue\n        self.expected_events = []\n        self._verbose = verbose\n        # If true, check that we receive exactly the expected events in the\n        # specified order.\n        self._validate_order = True\n        # If true, allow extra events in addition to expected ones\n        self._allow_extra = False\n        if platform.is_darwin():\n            # The fsevents API gives back events in a non-specific order.\n            self._validate_order = False\n            # Sometimes there are addtional DirModifiedEvents returned as well.\n            self._allow_extra = True\n\n    def allow_extra_events(self) -> None:\n        \"\"\"Allow events not specified by add().\"\"\"\n        self._allow_extra = True\n\n    def _debug(self, *args: Any) -> None:\n        if self._verbose:\n            print(\" == events checker == \", *args)  # noqa: T201\n\n    def _make_path(self, path: str | None) -> str | None:\n        if path is None:\n            return None\n        if path == \"\":\n            # an empty path is kept as-is\n            return \"\"\n        # convert to platform specific path and normalize\n        parts = path.split(\"/\")\n        path = os.path.join(self.tmp, *parts)\n        return os.path.normpath(path)\n\n    def add(self, expected_class: type, src_path: str | None = None, dest_path: str | None = None) -> None:\n        \"\"\"Add details for an expected event.  The `expected_class` argument\n        is required, everything else is optional.  The order that events are\n        received does not matter but adding the same kind of event more than\n        once will require that it appears more than once.\n\n        Note that paths are provided as relative to `tmp` and using the forward\n        slash separator. They will be converted to absolute paths using `tmp`\n        and normalized.  An empty path, i.e. \"\", is kept as-is.\n        \"\"\"\n        self.expected_events.append(_ExpectedEvent(expected_class, src_path, dest_path))\n\n    def _match_event(self, event: FileSystemEvent, expected_event: _ExpectedEvent) -> bool:\n        if not isinstance(event, expected_event.expected_class):\n            return False  # wrong class\n        src_path = self._make_path(expected_event.src_path)\n        if src_path is not None and src_path != event.src_path:\n            return False  # wrong src_path\n        dest_path = self._make_path(expected_event.dest_path)\n        if dest_path is not None and dest_path != event.dest_path:\n            return False  # wrong dest_path\n        self._debug(\"matched:\", expected_event)\n        return True\n\n    def _check_events_without_order( self, found_events: list[FileSystemEvent]) -> None:\n        # Check that events received contain the expected events.  This is\n        # an inefficient way to do it, O(n*m) but since the list of expected\n        # events should be fairly short, this is okay.  Using a list allows\n        # the expected events to contain the same kind of event more than\n        # once.\n        expected_events = list(self.expected_events)\n        matched_events = []\n        for event in found_events:\n            for i, expected_event in enumerate(expected_events):\n                if self._match_event(event, expected_event):\n                    del expected_events[i]\n                    matched_events.append(event)\n                    break\n        if expected_events:\n            # Fail, we did not find some of the expected events.\n            if self._verbose:\n                self._debug(\"missing:\")\n                for e in expected_events:\n                    self._debug(\"  \", e.expected_class.__name__, e.src_path)\n            assert not expected_events, \"some expected events not found\"\n        if not self._allow_extra:  # noqa: SIM102\n            # Check that we did not receive extra events\n            if len(matched_events) < len(found_events):\n                for event in found_events:\n                    if event not in matched_events:\n                        self._debug(\"unexpected\", event)\n                msg = \"received unexpected events\"\n                raise AssertionError(msg)\n\n    def _check_events_with_order( self, found_events: list[FileSystemEvent]) -> None:\n        if not self._allow_extra:\n            # we need exactly the number of expected events\n            assert len(found_events) == len(self.expected_events), \"wrong number of events\"\n        for event, expected_event in zip(found_events, self.expected_events):\n            assert self._match_event(event, expected_event), \"did not find expected event\"\n\n    def check_events(self, timeout: float = 2) -> None:\n        \"\"\"Read events from the events queue (waiting for up to `timeout` for\n        new events to appear).  Confirm that expected events, as specified by\n        calling add(), appear in the sequence of events receieved.\n        \"\"\"\n        if self._verbose:\n            self._debug(\"expecting:\")\n            for e in self.expected_events:\n                self._debug(\"  \", e.expected_class.__name__, repr(e.src_path))\n\n        found_events = []\n        while True:\n            # Read all the available events until we timeout.\n            try:\n                event = self.event_queue.get(timeout=timeout)[0]\n            except Empty:\n                self._debug(\"event queue timeout\")\n                break\n            self._debug(\"got:\", event.__class__.__name__, repr(event.src_path))\n            found_events.append(event)\n\n        if self._validate_order:\n            self._check_events_with_order(found_events)\n        else:\n            self._check_events_without_order(found_events)\n\n    def __enter__(self) -> _EventsChecker:  # noqa: PYI034\n        # This could be annotated to return `Self` but that type is available\n        # only in Python 3.11+.\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback) -> None:\n        if exc_value is None:\n            # do check only if there is no error\n            self.check_events()\n"
  },
  {
    "path": "tools/watchmedo.bat",
    "content": "@REM Copyright 2018-2025 Mickaël Schoentgen & contributors\n@REM Copyright 2012-2018 Google, Inc.\n@REM Copyright 2011-2012 Yesudeep Mangalapilly\n@REM Copyright 2001-2010 The SCons Foundation\n@REM watchmedo.bat - Wrapper .bat file for the watchmedo Python script.\n\n@echo off\nset SCRIPT_ERRORLEVEL=\nif \"%OS%\" == \"Windows_NT\" goto WinNT\n\n@REM Windows 9x/Me you better not have more than 9 arguments.\npython -c \"from watchdog import watchmedo; watchmedo.main()\" %1 %2 %3 %4 %5 %6 %7 %8 %9\n@REM No way to set exit status of this script for 9x/Me\ngoto endscript\n\n@REM Windows NT+\n:WinNT\nsetlocal\nset path=%~dp0;%~dp0..;%path%\npython -c \"from watchdog import watchmedo; watchmedo.main()\" %*\nendlocal & set SCRIPT_ERRORLEVEL=%ERRORLEVEL%\n\nif not \"%COMSPEC%\" == \"%SystemRoot%\\system32\\cmd.exe\" goto returncode\nif errorlevel 9009 echo You do not have python in your PATH environment variable.\ngoto endscript\n\n:returncode\nexit /B %SCRIPT_ERRORLEVEL%\n\n:endscript\ncall :returncode %SCRIPT_ERRORLEVEL%\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist =\n    py3{9,10,11,12,13}\n    pypy3\n    docs\n    types\n    lint\nskip_missing_interpreters = True\n\n[testenv]\nusedevelop = true\ndeps =\n    -r requirements-tests.txt\nextras =\n    watchmedo\ncommands =\n    python -m pytest {posargs}\n\n[testenv:docs]\nusedevelop = true\ndeps =\n    -r requirements-tests.txt\nextras =\n    watchmedo\ncommands =\n    sphinx-build -aEWb html docs/source docs/build/html\n\n[testenv:lint]\nusedevelop = true\ndeps =\n    -r requirements-tests.txt\nextras =\n    watchmedo\ncommands =\n    python -m ruff format docs/source/examples src tests\n    python -m ruff check --fix --unsafe-fixes src docs/source/examples tests\n\n[testenv:types]\nusedevelop = true\ndeps =\n    -r requirements-tests.txt\ncommands =\n    # General\n    python -m mypy docs/source/examples\n    python -m mypy src\n\n    # OS specific\n    python -m mypy --platform darwin --disable-error-code unused-ignore \\\n        src/watchdog/observers/fsevents.py \\\n        src/watchdog/observers/fsevents2.py\n    python -m mypy --platform freebsd --disable-error-code unused-ignore \\\n        src/watchdog/observers/kqueue.py\n    python -m mypy --platform linux --disable-error-code unused-ignore \\\n        src/watchdog/observers/inotify_c.py \\\n        src/watchdog/observers/inotify_move_event_grouper.py \\\n        src/watchdog/observers/inotify.py\n    python -m mypy --platform win32 --disable-error-code unused-ignore \\\n        src/watchdog/observers/read_directory_changes.py \\\n        src/watchdog/observers/winapi.py\n"
  }
]