├── .github ├── dependabot.yaml ├── semantic_release │ ├── package-lock.json │ └── package.json └── workflows │ ├── lint.yaml │ ├── matchers │ ├── flake8.json │ ├── mypy.json │ └── python.json │ ├── publish.yaml │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .releaserc.json ├── LICENSE ├── README.md ├── docs ├── contributing.md ├── gen_pages.py └── screenshots │ └── test_github_screenshot.svg ├── mkdocs.yaml ├── pyproject.toml ├── requirements.txt ├── requirements ├── requirements-all.py3.10.txt ├── requirements-all.py3.11.txt ├── requirements-all.py3.12.txt ├── requirements-all.py3.8.txt ├── requirements-all.py3.9.txt ├── requirements-docs.txt ├── requirements-lint.txt └── requirements-test.txt ├── tests ├── __init__.py ├── cassettes │ └── test_github_screenshot.yaml ├── conftest.py ├── helpers.py ├── test_screenshots.py └── test_utils.py └── textual_universal_directorytree ├── __about__.py ├── __init__.py ├── __main__.py ├── alternate_paths.py ├── app.py ├── py.typed ├── universal_directory_tree.py └── utils.py /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | - package-ecosystem: pip 8 | directory: /.github/workflows 9 | schedule: 10 | interval: monthly 11 | - package-ecosystem: pip 12 | directory: /docs 13 | schedule: 14 | interval: monthly 15 | - package-ecosystem: pip 16 | directory: / 17 | schedule: 18 | interval: monthly 19 | versioning-strategy: lockfile-only 20 | allow: 21 | - dependency-type: all 22 | -------------------------------------------------------------------------------- /.github/semantic_release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@semantic-release/exec": "^6.0.3", 4 | "@semantic-release/git": "^10.0.1", 5 | "@semantic-release/github": "^8.0.7", 6 | "semantic-release": "^21.0.1", 7 | "semantic-release-gitmoji": "^1.6.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | branches: ["**"] 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | steps: 12 | - name: Set up Github Workspace 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Python Environment 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.11" 20 | - name: Install Hatch 21 | run: | 22 | python -m pip install --upgrade pip wheel 23 | python -m pip install -q hatch pre-commit 24 | hatch --version 25 | - name: Create Environment 26 | run: hatch env create lint 27 | - name: Lint 28 | id: lint 29 | continue-on-error: true 30 | run: | 31 | echo "::add-matcher::.github/workflows/matchers/flake8.json" 32 | hatch run lint:style 33 | echo "::remove-matcher owner=flake8::" 34 | - name: Type Checking 35 | id: check 36 | continue-on-error: true 37 | run: | 38 | echo "::add-matcher::.github/workflows/matchers/mypy.json" 39 | hatch run lint:typing 40 | echo "::remove-matcher owner=mypy::" 41 | - name: Raise Errors For Failures 42 | if: | 43 | steps.lint.outcome != 'success' || 44 | steps.check.outcome != 'success' 45 | run: | 46 | echo "Lint: ${{ steps.lint.outcome }}" 47 | echo "Check: ${{ steps.check.outcome }}" 48 | exit 1 49 | -------------------------------------------------------------------------------- /.github/workflows/matchers/flake8.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "flake8", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.*?):(\\d+):(\\d+): (.*)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "message": 4 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/matchers/mypy.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "mypy", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+):(\\d+):\\s(error|warning|note):\\s(.+)$", 8 | "file": 1, 9 | "line": 2, 10 | "severity": 3, 11 | "message": 4 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/matchers/python.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "python", 5 | "pattern": [ 6 | { 7 | "regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$", 8 | "file": 1, 9 | "line": 2 10 | }, 11 | { 12 | "regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$", 13 | "message": 2 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publishing 2 | on: 3 | release: 4 | types: 5 | - published 6 | jobs: 7 | pypi-publish: 8 | name: PyPI 9 | if: github.repository_owner == 'juftin' 10 | runs-on: ubuntu-latest 11 | environment: 12 | name: pypi 13 | url: https://pypi.org/p/textual-universal-directorytree 14 | permissions: 15 | id-token: write 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.job }} 18 | cancel-in-progress: false 19 | steps: 20 | - name: Check out the repository 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 2 24 | - name: Set up Python 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.11" 28 | - name: Install Hatch 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install -q hatch pre-commit 32 | hatch --version 33 | - name: Build package 34 | run: | 35 | hatch build 36 | - name: Publish package on PyPI 37 | uses: pypa/gh-action-pypi-publish@release/v1 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - next 7 | - beta 8 | - alpha 9 | - "*.x" 10 | jobs: 11 | release: 12 | name: github-release 13 | if: github.repository_owner == 'juftin' 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | issues: write 18 | pull-requests: write 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.job }} 21 | cancel-in-progress: false 22 | steps: 23 | - name: Check out the repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 2 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v4 29 | - name: Set up Python 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: "3.11" 33 | - name: Install Hatch 34 | run: | 35 | python -m pip install --upgrade pip 36 | python -m pip install -q hatch pre-commit 37 | hatch -v env create 38 | hatch --version 39 | - name: Release 40 | run: hatch run gen:release 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 43 | GIT_AUTHOR_NAME: github-actions[bot] 44 | GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com 45 | GIT_COMMITTER_NAME: github-actions[bot] 46 | GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com 47 | github-pages-publish: 48 | runs-on: ubuntu-latest 49 | needs: release 50 | if: github.ref == 'refs/heads/main' && github.repository_owner == 'juftin' 51 | permissions: 52 | pages: write 53 | id-token: write 54 | environment: 55 | name: github-pages 56 | url: ${{ steps.deployment.outputs.page_url }} 57 | concurrency: 58 | group: ${{ github.workflow }}-${{ github.job }} 59 | cancel-in-progress: false 60 | steps: 61 | - name: Checkout Latest Changes 62 | uses: actions/checkout@v4 63 | - name: Set up Python Environment 64 | uses: actions/setup-python@v5 65 | with: 66 | python-version: "3.11" 67 | - name: Install Hatch 68 | run: | 69 | python -m pip install --upgrade pip wheel 70 | python -m pip install -q hatch pre-commit 71 | hatch --version 72 | - name: Create Virtual Environment 73 | run: hatch env create docs 74 | - name: Build Site 75 | run: hatch run docs:build 76 | - name: Setup GitHub Pages 77 | uses: actions/configure-pages@v4 78 | - name: Upload Artifact 79 | uses: actions/upload-pages-artifact@v3 80 | with: 81 | path: site/ 82 | - name: Deploy to GitHub Pages 83 | id: deployment 84 | uses: actions/deploy-pages@v4 85 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - textual_universal_directorytree/** 8 | - tests/** 9 | - pyproject.toml 10 | - .github/workflows/tests.yaml 11 | pull_request: 12 | branches: ["**"] 13 | paths: 14 | - textual_universal_directorytree/** 15 | - tests/** 16 | - pyproject.toml 17 | - .github/workflows/tests.yaml 18 | schedule: 19 | - cron: 0 12 1 * * 20 | jobs: 21 | test-suite: 22 | runs-on: ubuntu-latest 23 | strategy: 24 | fail-fast: true 25 | matrix: 26 | include: 27 | - { name: Python 3.12, python: "3.12" } 28 | - { name: Python 3.11, python: "3.11" } 29 | - { name: Python 3.10, python: "3.10" } 30 | - { name: Python 3.9, python: "3.9" } 31 | - { name: Python 3.8, python: "3.8" } 32 | concurrency: 33 | group: ${{ github.workflow }}-${{ matrix.python }}-${{ github.ref }} 34 | cancel-in-progress: true 35 | steps: 36 | - name: Set up Github Workspace 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 0 40 | - name: Set up Python Environment ${{ matrix.python }} 41 | uses: actions/setup-python@v5 42 | with: 43 | python-version: ${{ matrix.python }} 44 | - name: Install Hatch 45 | run: | 46 | python -m pip install -q --upgrade pip wheel 47 | python -m pip install -q hatch pre-commit 48 | hatch --version 49 | - name: Create Environment 50 | run: hatch --env all run +py=${{ matrix.python }} -- python --version 51 | - name: Test Suite 52 | run: | 53 | echo "::add-matcher::.github/workflows/matchers/python.json" 54 | hatch run +py=${{ matrix.python }} all:cov 55 | echo "::remove-matcher owner=python::" 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | docs/source/api 74 | docs/source/README.md 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # documentation 123 | /site 124 | docs/source/_autosummary 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Mac Files 132 | .DS_Store 133 | 134 | # IDE Files 135 | .idea/ 136 | 137 | # Node.js 138 | node_modules/ 139 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_stages: [commit] 2 | fail_fast: false 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.5.0 7 | hooks: 8 | - id: trailing-whitespace 9 | exclude: '\.svg$' 10 | - id: end-of-file-fixer 11 | - id: check-yaml 12 | exclude: mkdocs.yaml 13 | - id: check-ast 14 | - id: check-docstring-first 15 | - id: check-merge-conflict 16 | - id: mixed-line-ending 17 | 18 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 19 | rev: v2.11.0 20 | hooks: 21 | - id: pretty-format-toml 22 | args: [--autofix] 23 | 24 | - repo: https://github.com/pre-commit/mirrors-prettier 25 | rev: v3.0.3 26 | hooks: 27 | - id: prettier 28 | args: [--print-width=88, --tab-width=4] 29 | exclude: | 30 | (?x)( 31 | .github/semantic_release/release_notes.hbs 32 | ) 33 | additional_dependencies: 34 | - prettier 35 | 36 | - repo: local 37 | hooks: 38 | - id: format 39 | name: format 40 | description: Runs Code Auto-Formatters 41 | entry: hatch run lint:fmt 42 | language: system 43 | pass_filenames: false 44 | - id: lint 45 | name: lint 46 | description: Runs Code Linters 47 | entry: hatch run lint:all 48 | language: system 49 | pass_filenames: false 50 | require_serial: false 51 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main", 4 | "master", 5 | "next", 6 | "next-major", 7 | "+([0-9])?(.{+([0-9]),x}).x", 8 | { 9 | "name": "beta", 10 | "prerelease": true 11 | }, 12 | { 13 | "name": "alpha", 14 | "prerelease": true 15 | } 16 | ], 17 | "plugins": [ 18 | "semantic-release-gitmoji", 19 | [ 20 | "@semantic-release/exec", 21 | { 22 | "prepareCmd": "hatch version ${nextRelease.version} && hatch build" 23 | } 24 | ], 25 | [ 26 | "@semantic-release/git", 27 | { 28 | "assets": ["pyproject.toml", "*/__about__.py"], 29 | "message": "🔖 textual-universal-directorytree ${nextRelease.version}\n\n${nextRelease.notes}\n[skip ci]" 30 | } 31 | ], 32 | [ 33 | "@semantic-release/github", 34 | { 35 | "assets": [ 36 | { 37 | "path": "dist/*.whl" 38 | }, 39 | { 40 | "path": "dist/*.tar.gz" 41 | } 42 | ] 43 | } 44 | ] 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Justin Flannery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

textual-universal-directorytree

2 | 3 |
4 | 5 | textual-universal-directorytree 6 | 7 |
8 | 9 |

10 | DirectoryTree widget for textual, compatible with all filesystems 11 |

12 | 13 |

14 | PyPI 15 | PyPI - Python Version 16 | GitHub License 17 | docs 18 | Testing Status 19 | Hatch project 20 | Ruff 21 | pre-commit 22 | semantic-release 23 | Gitmoji 24 |

25 | 26 | ## Installation 27 | 28 | ```shell 29 | pip install textual-universal-directorytree 30 | ``` 31 | 32 | ### Extra Dependencies 33 | 34 | Some filesystems require additional dependencies to be installed. 35 | The `remote` extra includes all the known optional dependencies. See the 36 | [Filesystems Supported](#filesystems-supported) section for more information. 37 | 38 | ```shell 39 | pip install "textual-universal-directorytree[remote]" 40 | ``` 41 | 42 | ## Usage 43 | 44 | The below example shows how to use `textual-universal-directorytree` in a Textual app. 45 | It uses the GitHub filesystem to display the contents of the textual GitHub repository. 46 | It requires the `requests` library to be installed (or the `remote` extra). 47 | 48 | ```python 49 | from __future__ import annotations 50 | 51 | from typing import Any, ClassVar 52 | 53 | from rich.syntax import Syntax 54 | from textual import on 55 | from textual.app import App, ComposeResult 56 | from textual.binding import BindingType 57 | from textual.containers import Horizontal, VerticalScroll 58 | from textual.widgets import DirectoryTree, Footer, Header, Static 59 | 60 | from textual_universal_directorytree import UniversalDirectoryTree, UPath 61 | 62 | 63 | class UniversalDirectoryTreeApp(App): 64 | """ 65 | The power of upath and fsspec in a Textual app 66 | """ 67 | 68 | TITLE = "UniversalDirectoryTree" 69 | 70 | CSS = """ 71 | UniversalDirectoryTree { 72 | max-width: 50%; 73 | width: auto; 74 | height: 100%; 75 | dock: left; 76 | } 77 | """ 78 | 79 | BINDINGS: ClassVar[list[BindingType]] = [ 80 | ("q", "quit", "Quit"), 81 | ] 82 | 83 | def __init__(self, path: str | UPath, *args: Any, **kwargs: Any): 84 | super().__init__(*args, **kwargs) 85 | self.universal_path = UPath(path).resolve() 86 | self.directory_tree = UniversalDirectoryTree(path=self.universal_path) 87 | self.file_content = Static(expand=True) 88 | 89 | def compose(self) -> ComposeResult: 90 | yield Header() 91 | yield Horizontal(self.directory_tree, VerticalScroll(self.file_content)) 92 | yield Footer() 93 | 94 | @on(DirectoryTree.FileSelected) 95 | def handle_file_selected(self, message: DirectoryTree.FileSelected) -> None: 96 | """ 97 | Do something with the selected file. 98 | 99 | Objects returned by the FileSelected event are upath.UPath objects and 100 | they are compatible with the familiar pathlib.Path API built into Python. 101 | """ 102 | self.sub_title = str(message.path) 103 | try: 104 | file_content = message.path.read_text() 105 | except UnicodeDecodeError: 106 | self.file_content.update("") 107 | return None 108 | lexer = Syntax.guess_lexer(path=message.path.name, code=file_content) 109 | code = Syntax(code=file_content, lexer=lexer) 110 | self.file_content.update(code) 111 | ``` 112 | 113 | Run the above app in your terminal: 114 | 115 | ```python 116 | python -m textual_universal_directorytree github://juftin:textual-universal-directorytree@main/ 117 | ``` 118 | 119 | ## Filesystems Supported 120 | 121 | `textual-universal-directorytree` leverages [fsspec](https://github.com/fsspec/filesystem_spec) and 122 | [universal_pathlib](https://github.com/fsspec/universal_pathlib) to enable compatibility with 123 | local and remote filesystems. 124 | 125 | In some cases you need to install a filesystem-specific library 126 | to enable compatibility with that filesystem. For example, to enable compatibility with AWS S3 you must 127 | install [s3fs](https://github.com/fsspec/s3fs) which is an `fsspec` implementation for S3. 128 | 129 | The following filesystems are known to be supported by `textual-universal-directorytree`, but it's possible 130 | that [others filesystems](https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations) 131 | are supported as well and just haven't been tested. If you find a filesystem that works, please open an issue. 132 | 133 | | File System | Format | Optional Dependencies | 134 | | --------------- | --------------------------------- | ------------------------------------------------ | 135 | | Local | `path/to/file` | None | 136 | | Local | `file://path/to/file` | None | 137 | | AWS S3 | `s3://bucket/path` | [s3fs](https://github.com/fsspec/s3fs) | 138 | | AWS S3 | `s3a://bucket/path` | [s3fs](https://github.com/fsspec/s3fs) | 139 | | Google GCS | `gs://bucket/path` | [gcsfs](https://github.com/fsspec/gcsfs) | 140 | | Azure Data Lake | `adl://bucket/path` | [adlfs](https://github.com/fsspec/adlfs) | 141 | | Azure Blob | `abfs://bucket/path` | [adlfs](https://github.com/fsspec/adlfs) | 142 | | Azure Blob | `az://bucket/path` | [adlfs](https://github.com/fsspec/adlfs) | 143 | | GitHub | `github://owner:repo@branch` | [requests](https://github.com/requests/requests) | 144 | | GitHub | `github://owner:repo@branch/path` | [requests](https://github.com/requests/requests) | 145 | | SSH | `ssh://user@host:port/path` | [paramiko](https://github.com/paramiko/paramiko) | 146 | | SFTP | `sftp://user@host:port/path` | [paramiko](https://github.com/paramiko/paramiko) | 147 | 148 | ## License 149 | 150 | `textual-universal-directorytree` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) 151 | license. 152 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Environment Setup 4 | 5 | > TIP: **pipx** 6 | > 7 | > This documentaion uses [pipx] to 8 | > install and manage non-project command line tools like `hatch` and 9 | > `pre-commit`. If you don't already have `pipx` installed, make sure to 10 | > see their [documentation](https://pypa.github.io/pipx/installation/). 11 | > If you prefer not to use `pipx`, you can use `pip` instead. 12 | 13 | 1. Install [hatch](https://hatch.pypa.io/latest/) 14 | 15 | ```shell 16 | pipx install hatch 17 | ``` 18 | 19 | > NOTE: **pre-commit** 20 | > 21 | > Hatch will attempt to set up pre-commit hooks for you using 22 | > [pre-commit]. If you don't already, 23 | > make sure to install pre-commit as well: `pipx install pre-commit` 24 | 25 | 2. Build the Virtual Environment 26 | 27 | ```shell 28 | hatch env create 29 | ``` 30 | 31 | 3. If you need to, you can link a hatch virtual environment to your IDE. 32 | They can be located by name with the `env find` command: 33 | 34 | ```shell 35 | hatch env find default 36 | ``` 37 | 38 | 4. Activate the Virtual Environment 39 | 40 | ```shell 41 | hatch shell 42 | ``` 43 | 44 | ## Using Hatch 45 | 46 | ### Hatch Cheat Sheet 47 | 48 | | Command Description | Command | Notes | 49 | | -------------------------- | -------------------------- | ---------------------------------------------- | 50 | | Run Tests | `hatch run cov` | Runs tests with `pytest` and `coverage` | 51 | | Run Formatting | `hatch run lint:fmt` | Runs `ruff` code formatter | 52 | | Run Linting | `hatch run lint:style` | Runs `ruff` code linter | 53 | | Run Type Checking | `hatch run lint:typing` | Runs `mypy` type checker | 54 | | Run All Static Analysis | `hatch run lint:all` | Runs `ruff` and `mypy` linters / type checkers | 55 | | Serve the Documentation | `hatch run docs:serve` | Serve the documentation using MkDocs | 56 | | Run the `pre-commit` Hooks | `hatch run lint:precommit` | Runs the `pre-commit` hooks on all files | 57 | 58 | ### Hatch Explanation 59 | 60 | Hatch is a Python package manager. Its most basic use is as a standardized build-system. 61 | However, hatch also has some extra features which this project takes advantage of. 62 | These features include virtual environment management and the organization of common 63 | scripts like linting and testing. All the operations in hatch take place in one 64 | of its managed virtual environments. 65 | 66 | Hatch has a variety of environments, to see them simply ask hatch: 67 | 68 | ```bash exec="on" result="markdown" source="tabbed-left" tabs="hatch CLI|Output" 69 | hatch env show 70 | ``` 71 | 72 | That above command will tell you that there are five environments that 73 | you can use: 74 | 75 | - `default` 76 | - `docs` 77 | - `gen` 78 | - `lint` 79 | - `test` 80 | 81 | Each of these environments has a set of commands that you can run. 82 | To see the commands for a specific environment, run: 83 | 84 | ```bash exec="on" result="markdown" source="tabbed-left" tabs="hatch CLI|Output" 85 | hatch env show default 86 | ``` 87 | 88 | Here we can see that the `default` environment has the following commands: 89 | 90 | - `cov` 91 | - `test` 92 | 93 | The one that we're interested in is `cov`, which will run the tests 94 | for the project. 95 | 96 | ```bash 97 | hatch run cov 98 | ``` 99 | 100 | Since `cov` is in the default environment, we can run it without 101 | specifying the environment. However, to run the `serve` command in the 102 | `docs` environment, we need to specify the environment: 103 | 104 | ```bash 105 | hatch run docs:serve 106 | ``` 107 | 108 | You can see what scripts are available using the `env show` command 109 | 110 | ```bash exec="on" result="markdown" source="tabbed-left" tabs="hatch CLI|Output" 111 | hatch env show docs 112 | ``` 113 | 114 | ## Committing Code 115 | 116 | This project uses [pre-commit] to run a set of 117 | checks on the code before it is committed. The pre-commit hooks are 118 | installed by hatch automatically when you run it for the first time. 119 | 120 | This project uses [semantic-versioning] standards, managed by [semantic-release]. 121 | Releases for this project are handled entirely by CI/CD via pull requests being 122 | merged into the `main` branch. Contributions follow the [gitmoji] standards 123 | with [conventional commits]. 124 | 125 | While you can denote other changes on your commit messages with [gitmoji], the following 126 | commit message emoji prefixes are the only ones to trigger new releases: 127 | 128 | | Emoji | Shortcode | Description | Semver | 129 | | ----- | ------------- | --------------------------- | ------ | 130 | | 💥 | \:boom\: | Introduce breaking changes. | Major | 131 | | ✨ | \:sparkles\: | Introduce new features. | Minor | 132 | | 🐛 | \:bug\: | Fix a bug. | Patch | 133 | | 🚑 | \:ambulance\: | Critical hotfix. | Patch | 134 | | 🔒 | \:lock\: | Fix security issues. | Patch | 135 | 136 | Most features can be squash merged into a single commit on a pull-request. 137 | When merging multiple commits, they will be summarized into a single release. 138 | 139 | If you're working on a new feature, your commit message might look like: 140 | 141 | ```text 142 | ✨ New Feature Description 143 | ``` 144 | 145 | Bug fix commits would look like this: 146 | 147 | ```text 148 | 🐛 Bug Fix Description 149 | ``` 150 | 151 | If you're working on a feature that introduces breaking changes, your 152 | commit message might look like: 153 | 154 | ```text 155 | 💥 Breaking Change Description 156 | ``` 157 | 158 | Other commits that don't trigger a release might look like this: 159 | 160 | ```text 161 | 📝 Documentation Update Description 162 | 👷 CI/CD Update Description 163 | 🧪 Testing Changes Description 164 | 🚚 Moving/Renaming Description 165 | ⬆️ Dependency Upgrade Description 166 | ``` 167 | 168 | ### Pre-Releases 169 | 170 | [semantic-release] supports pre-releases. To trigger a pre-release, you 171 | would merge your pull request into an `alpha` or `beta` branch. 172 | 173 | ### Specific Release Versions 174 | 175 | In some cases you need more advanced control around what kind of release you 176 | need to create. If you need to release a specific version, you can do so by creating a 177 | new branch with the version number as the branch name. For example, if the 178 | current version is `2.3.2`, but you need to release a fix as `1.2.5`, you 179 | would create a branch named `1.2.x` and merge your changes into that branch. 180 | 181 | See the [semantic-release documentation] for more information about 182 | branch based releases and other advanced release cases. 183 | 184 | [pipx]: https://pypa.github.io/pipx/ 185 | [pre-commit]: https://pre-commit.com/ 186 | [gitmoji]: https://gitmoji.dev/ 187 | [conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/ 188 | [semantic-release]: https://github.com/semantic-release/semantic-release 189 | [semantic-versioning]: https://semver.org/ 190 | [semantic-release documentation]: https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches 191 | -------------------------------------------------------------------------------- /docs/gen_pages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generate the code reference pages and navigation. 3 | """ 4 | 5 | import logging 6 | from pathlib import Path 7 | 8 | import mkdocs_gen_files 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | project_dir = Path(__file__).resolve().parent.parent 13 | source_code = project_dir.joinpath("textual_universal_directorytree") 14 | 15 | for path in sorted(source_code.rglob("*.py")): 16 | module_path = path.relative_to(project_dir).with_suffix("") 17 | doc_path = path.relative_to(source_code).with_suffix(".md") 18 | full_doc_path = Path("reference", doc_path) 19 | 20 | parts = tuple(module_path.parts) 21 | if parts[-1] == "__init__": 22 | parts = parts[:-1] 23 | doc_path = doc_path.with_name("index.md") 24 | full_doc_path = full_doc_path.with_name("index.md") 25 | elif parts[-1] == "__main__": 26 | continue 27 | with mkdocs_gen_files.open(full_doc_path, "w") as fd: 28 | fd.write(f"# `{parts[-1]}`\n\n::: {'.'.join(parts)}") 29 | 30 | mkdocs_gen_files.set_edit_path(full_doc_path, path) 31 | 32 | # Exclude parts that are between two exact `` lines 33 | readme_content = Path("README.md").read_text() 34 | readme_content = readme_content.replace("docs/", "") 35 | readme_content = "\n".join(readme_content.split("\n\n")[::2]) 36 | with mkdocs_gen_files.open("index.md", "w") as index_file: 37 | index_file.write(readme_content) 38 | -------------------------------------------------------------------------------- /docs/screenshots/test_github_screenshot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | UniversalDirectoryTree 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | UniversalDirectoryTree — github://juftin:textual-universal-directorytree@v1.0.0/.pre-commit-config.ya… 174 | 📂 textual-universal-directorytreedefault_stages:[commit]                                                       175 | ├── 📁 .githubfail_fast:false                                                               176 | ├── 📁 docs 177 | ├── 📁 requirementsrepos: 178 | ├── 📁 tests-repo:https://github.com/pre-commit/pre-commit-hooks                     179 | ├── 📁 textual_universal_directorytreerev:v4.4.0                                                              180 | ├── 📄 .gitignorehooks: 181 | ├── 📄 .pre-commit-config.yaml-id:trailing-whitespace                                            182 | ├── 📄 .releaserc.jsexclude:'\.svg$' 183 | ├── 📄 LICENSE.txt-id:end-of-file-fixer                                              184 | ├── 📄 mkdocs.yaml-id:check-yaml                                                     185 | ├── 📄 pyproject.toml-id:check-ast                                                      186 | └── 📄 README.md-id:check-docstring-first                                          187 | -id:check-merge-conflict                                           188 | -id:mixed-line-ending                                              189 | 190 | -repo:https://github.com/macisamuele/language-formatters-pre-commit-hook 191 | rev:v2.8.0                                                              192 | hooks: 193 | -id:pretty-format-toml                                             194 | args:[--autofix]                                                  195 | ▁▁ 196 | -repo:https://github.com/pre-commit/mirrors-prettier                     197 | rev:v3.0.0-alpha.6                                                      198 | hooks: 199 | -id:prettier                                                       200 | args:[--print-width=88,--tab-width=4]                            201 | exclude:|                                                         202 | (?x)( 203 | .github/semantic_release/release_notes.hbs 204 | ) 205 | additional_dependencies: 206 | -prettier                                                     207 |  Q  Quit  208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /mkdocs.yaml: -------------------------------------------------------------------------------- 1 | # schema: https://squidfunk.github.io/mkdocs-material/schema.json 2 | 3 | site_name: textual-universal-directorytree 4 | nav: 5 | - index.md 6 | - API Documentation 🤖: reference/ 7 | - Contributing 🤝: contributing.md 8 | theme: 9 | favicon: https://raw.githubusercontent.com/juftin/juftin/main/static/juftin.png 10 | logo: https://raw.githubusercontent.com/juftin/juftin/main/static/juftin.png 11 | name: material 12 | features: 13 | - navigation.tracking 14 | - content.code.annotate 15 | - content.code.copy 16 | - navigation.indexes 17 | palette: 18 | - media: "(prefers-color-scheme: light)" 19 | scheme: default 20 | accent: purple 21 | toggle: 22 | icon: material/weather-sunny 23 | name: Switch to dark mode 24 | - media: "(prefers-color-scheme: dark)" 25 | scheme: slate 26 | primary: black 27 | toggle: 28 | icon: material/weather-night 29 | name: Switch to light mode 30 | repo_url: https://github.com/juftin/textual-universal-directorytree 31 | repo_name: textual-universal-directorytree 32 | edit_uri: blob/main/docs/ 33 | site_author: Justin Flannery 34 | remote_branch: gh-pages 35 | copyright: Copyright © 2023 Justin Flannery 36 | extra: 37 | generator: false 38 | exclude_docs: | 39 | gen_pages.py 40 | markdown_extensions: 41 | - toc: 42 | permalink: "#" 43 | - pymdownx.snippets 44 | - pymdownx.magiclink 45 | - attr_list 46 | - md_in_html 47 | - pymdownx.highlight: 48 | anchor_linenums: true 49 | - pymdownx.inlinehilite 50 | - pymdownx.superfences 51 | - markdown.extensions.attr_list 52 | - pymdownx.keys 53 | - pymdownx.tasklist: 54 | custom_checkbox: true 55 | - pymdownx.tilde 56 | - admonition 57 | - callouts 58 | - pymdownx.details 59 | - pymdownx.emoji 60 | - pymdownx.tabbed: 61 | alternate_style: true 62 | - mkdocs-click 63 | plugins: 64 | - search 65 | - markdown-exec 66 | - section-index 67 | - autorefs 68 | - mkdocstrings: 69 | handlers: 70 | python: 71 | import: 72 | - https://docs.python.org/3/objects.inv 73 | options: 74 | docstring_style: numpy 75 | - gen-files: 76 | scripts: 77 | - docs/gen_pages.py 78 | - literate-nav: 79 | nav_file: SUMMARY.md 80 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "hatchling.build" 3 | requires = ["hatchling"] 4 | 5 | [project] 6 | authors = [ 7 | {name = "Justin Flannery", email = "juftin@juftin.com"} 8 | ] 9 | classifiers = [ 10 | "Development Status :: 4 - Beta", 11 | "Operating System :: OS Independent", 12 | "Programming Language :: Python", 13 | "Programming Language :: Python :: 3.8", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "Programming Language :: Python :: 3.12", 18 | "Programming Language :: Python :: Implementation :: CPython", 19 | "Programming Language :: Python :: Implementation :: PyPy" 20 | ] 21 | dependencies = [ 22 | "textual>=0.27.0", 23 | "universal-pathlib>=0.2.2" 24 | ] 25 | description = "A Textual Directory Tree for all File Systems" 26 | dynamic = ["version"] 27 | keywords = ["textual", "tui", "plugin", "fsspec"] 28 | license = "MIT" 29 | name = "textual-universal-directorytree" 30 | readme = "README.md" 31 | requires-python = ">=3.8" 32 | 33 | [project.optional-dependencies] 34 | remote = [ 35 | "s3fs", 36 | "requests>=2", 37 | "gcsfs", 38 | "adlfs", 39 | "aiohttp", 40 | "paramiko" 41 | ] 42 | 43 | [project.urls] 44 | Documentation = "https://github.com/juftin/textual-universal-directorytree#readme" 45 | Issues = "https://github.com/juftin/textual-universal-directorytree/issues" 46 | Source = "https://github.com/juftin/textual-universal-directorytree" 47 | 48 | [tool.coverage.paths] 49 | tests = ["tests", "*/textual-universal-directorytree/tests"] 50 | textual_universal_directorytree = ["textual_universal_directorytree", "*/textual-universal-directorytree/textual_universal_directorytree"] 51 | 52 | [tool.coverage.report] 53 | exclude_lines = [ 54 | "no cov", 55 | "if __name__ == .__main__.:", 56 | "if TYPE_CHECKING:" 57 | ] 58 | show_missing = true 59 | 60 | [tool.coverage.run] 61 | branch = true 62 | omit = [ 63 | "textual_universal_directorytree/__about__.py" 64 | ] 65 | parallel = true 66 | source_pkgs = ["textual_universal_directorytree", "tests"] 67 | 68 | [tool.cruft] 69 | skip = [ 70 | "textual_universal_directorytree/__about__.py", 71 | "textual_universal_directorytree/__init__.py", 72 | "tests/" 73 | ] 74 | 75 | [tool.hatch.env] 76 | requires = ["hatch-pip-compile", "hatch-mkdocs"] 77 | 78 | [tool.hatch.env.collectors.mkdocs.docs] 79 | path = "mkdocs.yaml" 80 | 81 | [tool.hatch.envs.all] 82 | pip-compile-constraint = "" 83 | template = "test" 84 | 85 | [[tool.hatch.envs.all.matrix]] 86 | python = ["3.8", "3.9", "3.10", "3.11", "3.12"] 87 | 88 | [tool.hatch.envs.default] 89 | features = ["remote"] 90 | pip-compile-constraint = "default" 91 | post-install-commands = [ 92 | "- pre-commit install" 93 | ] 94 | type = "pip-compile" 95 | 96 | [tool.hatch.envs.default.scripts] 97 | cov = "hatch run test:cov" 98 | test = "hatch run test:test" 99 | 100 | [tool.hatch.envs.docs] 101 | detached = false 102 | pip-compile-constraint = "default" 103 | template = "docs" 104 | type = "pip-compile" 105 | 106 | [tool.hatch.envs.gen] 107 | detached = true 108 | 109 | [tool.hatch.envs.gen.scripts] 110 | release = [ 111 | "npm install --prefix .github/semantic_release/", 112 | "npx --prefix .github/semantic_release/ semantic-release {args:}" 113 | ] 114 | 115 | [tool.hatch.envs.lint] 116 | dependencies = [ 117 | "mypy>=1.6.1", 118 | "ruff~=0.1.7" 119 | ] 120 | detached = true 121 | type = "pip-compile" 122 | 123 | [tool.hatch.envs.lint.scripts] 124 | all = [ 125 | "style", 126 | "typing" 127 | ] 128 | fmt = [ 129 | "ruff format {args:.}", 130 | "ruff --fix {args:.}", 131 | "style" 132 | ] 133 | precommit = [ 134 | "pre-commit run --all-files" 135 | ] 136 | style = [ 137 | "ruff {args:.}", 138 | "ruff format --check --diff {args:.}" 139 | ] 140 | typing = "mypy --install-types --non-interactive {args:textual_universal_directorytree tests}" 141 | 142 | [tool.hatch.envs.test] 143 | dependencies = [ 144 | "pytest", 145 | "pytest-cov" 146 | ] 147 | 148 | [tool.hatch.envs.test.scripts] 149 | cov = "pytest --cov --cov-config=pyproject.toml {args:tests}" 150 | test = "pytest {args:tests}" 151 | 152 | [tool.hatch.version] 153 | path = "textual_universal_directorytree/__about__.py" 154 | 155 | [tool.mypy] 156 | check_untyped_defs = true 157 | disallow_any_generics = true 158 | disallow_untyped_defs = true 159 | follow_imports = "silent" 160 | ignore_missing_imports = true 161 | no_implicit_reexport = true 162 | warn_redundant_casts = true 163 | warn_unused_ignores = true 164 | 165 | [tool.ruff] 166 | ignore = [ 167 | # Ignore checks for possible passwords 168 | "S105", 169 | "S106", 170 | "S107", 171 | # Ignore complexity 172 | "C901", 173 | "PLR0911", 174 | "PLR0912", 175 | "PLR0913", 176 | "PLR0915", 177 | # Boolean-typed positional argument in function definition 178 | "FBT001", 179 | # Boolean default positional argument in function definition 180 | "FBT002", 181 | # Allow boolean positional values in function calls, like `dict.get(... True)` 182 | "FBT003", 183 | # Exception must not use a string literal, assign to variable first 184 | "EM101" 185 | ] 186 | line-length = 88 187 | select = [ 188 | "A", # flake8-builtins 189 | "ARG", # flake8-unused-arguments 190 | "B", # flake8-bugbear 191 | "C", # mccabe 192 | "DTZ", # flake8-datetimez 193 | "E", # pycodestyle (Error) 194 | "EM", # flake8-errmsg 195 | "F", # Pyflakes 196 | "FBT", # flake8-boolean-trap 197 | "I", # isort 198 | "ICN", # flake8-import-conventions 199 | "ISC", # flake8-implicit-str-concat 200 | "N", # pep8-naming 201 | "PLC", # Pylint (Convention message) 202 | "PLE", # Pylint (Error message) 203 | "PLR", # Pylint (Refactor message) 204 | "PLW", # Pylint (Warning message) 205 | "Q", # flake8-quotes 206 | "RUF", # Ruff-specific rules 207 | "S", # flake8-bandit 208 | "T", # flake8-debugger (T10) and flake8-print (T20) 209 | "TID", # flake8-tidy-imports 210 | "UP", # pyupgrade 211 | "W", # pycodestyle (Warning) 212 | "YTT" # flake8-2020 213 | ] 214 | target-version = "py38" 215 | 216 | [tool.ruff.flake8-tidy-imports] 217 | ban-relative-imports = "all" 218 | 219 | [tool.ruff.isort] 220 | known-first-party = ["textual_universal_directorytree"] 221 | 222 | [tool.ruff.per-file-ignores] 223 | # Tests can use magic values, assertions, and relative imports 224 | "tests/**/*" = ["PLR2004", "S101", "TID252"] 225 | 226 | [tool.ruff.pydocstyle] 227 | convention = "numpy" 228 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.11 3 | # 4 | # - textual>=0.27.0 5 | # - universal-pathlib>=0.2.2 6 | # - adlfs 7 | # - aiohttp 8 | # - gcsfs 9 | # - paramiko 10 | # - requests>=2 11 | # - s3fs 12 | # 13 | 14 | adlfs==2024.2.0 15 | # via hatch.envs.default 16 | aiobotocore==2.12.1 17 | # via s3fs 18 | aiohttp==3.9.3 19 | # via 20 | # hatch.envs.default 21 | # adlfs 22 | # aiobotocore 23 | # gcsfs 24 | # s3fs 25 | aioitertools==0.11.0 26 | # via aiobotocore 27 | aiosignal==1.3.1 28 | # via aiohttp 29 | attrs==23.2.0 30 | # via aiohttp 31 | azure-core==1.30.1 32 | # via 33 | # adlfs 34 | # azure-identity 35 | # azure-storage-blob 36 | azure-datalake-store==0.0.53 37 | # via adlfs 38 | azure-identity==1.15.0 39 | # via adlfs 40 | azure-storage-blob==12.19.1 41 | # via adlfs 42 | bcrypt==4.1.2 43 | # via paramiko 44 | botocore==1.34.51 45 | # via aiobotocore 46 | cachetools==5.3.3 47 | # via google-auth 48 | certifi==2024.2.2 49 | # via requests 50 | cffi==1.16.0 51 | # via 52 | # azure-datalake-store 53 | # cryptography 54 | # pynacl 55 | charset-normalizer==3.3.2 56 | # via requests 57 | cryptography==42.0.5 58 | # via 59 | # azure-identity 60 | # azure-storage-blob 61 | # msal 62 | # paramiko 63 | # pyjwt 64 | decorator==5.1.1 65 | # via gcsfs 66 | frozenlist==1.4.1 67 | # via 68 | # aiohttp 69 | # aiosignal 70 | fsspec==2024.3.1 71 | # via 72 | # adlfs 73 | # gcsfs 74 | # s3fs 75 | # universal-pathlib 76 | gcsfs==2024.3.1 77 | # via hatch.envs.default 78 | google-api-core==2.17.1 79 | # via 80 | # google-cloud-core 81 | # google-cloud-storage 82 | google-auth==2.29.0 83 | # via 84 | # gcsfs 85 | # google-api-core 86 | # google-auth-oauthlib 87 | # google-cloud-core 88 | # google-cloud-storage 89 | google-auth-oauthlib==1.2.0 90 | # via gcsfs 91 | google-cloud-core==2.4.1 92 | # via google-cloud-storage 93 | google-cloud-storage==2.16.0 94 | # via gcsfs 95 | google-crc32c==1.5.0 96 | # via 97 | # google-cloud-storage 98 | # google-resumable-media 99 | google-resumable-media==2.7.0 100 | # via google-cloud-storage 101 | googleapis-common-protos==1.63.0 102 | # via google-api-core 103 | idna==3.6 104 | # via 105 | # requests 106 | # yarl 107 | isodate==0.6.1 108 | # via azure-storage-blob 109 | jmespath==1.0.1 110 | # via botocore 111 | linkify-it-py==2.0.3 112 | # via markdown-it-py 113 | markdown-it-py==3.0.0 114 | # via 115 | # mdit-py-plugins 116 | # rich 117 | # textual 118 | mdit-py-plugins==0.4.0 119 | # via markdown-it-py 120 | mdurl==0.1.2 121 | # via markdown-it-py 122 | msal==1.28.0 123 | # via 124 | # azure-datalake-store 125 | # azure-identity 126 | # msal-extensions 127 | msal-extensions==1.1.0 128 | # via azure-identity 129 | multidict==6.0.5 130 | # via 131 | # aiohttp 132 | # yarl 133 | oauthlib==3.2.2 134 | # via requests-oauthlib 135 | packaging==24.0 136 | # via msal-extensions 137 | paramiko==3.4.0 138 | # via hatch.envs.default 139 | portalocker==2.8.2 140 | # via msal-extensions 141 | protobuf==4.25.3 142 | # via 143 | # google-api-core 144 | # googleapis-common-protos 145 | pyasn1==0.5.1 146 | # via 147 | # pyasn1-modules 148 | # rsa 149 | pyasn1-modules==0.3.0 150 | # via google-auth 151 | pycparser==2.21 152 | # via cffi 153 | pygments==2.17.2 154 | # via rich 155 | pyjwt==2.8.0 156 | # via 157 | # msal 158 | # pyjwt 159 | pynacl==1.5.0 160 | # via paramiko 161 | python-dateutil==2.9.0.post0 162 | # via botocore 163 | requests==2.31.0 164 | # via 165 | # hatch.envs.default 166 | # azure-core 167 | # azure-datalake-store 168 | # gcsfs 169 | # google-api-core 170 | # google-cloud-storage 171 | # msal 172 | # requests-oauthlib 173 | requests-oauthlib==1.4.0 174 | # via google-auth-oauthlib 175 | rich==13.7.1 176 | # via textual 177 | rsa==4.9 178 | # via google-auth 179 | s3fs==2024.3.1 180 | # via hatch.envs.default 181 | six==1.16.0 182 | # via 183 | # azure-core 184 | # isodate 185 | # python-dateutil 186 | textual==0.53.1 187 | # via hatch.envs.default 188 | typing-extensions==4.10.0 189 | # via 190 | # azure-core 191 | # azure-storage-blob 192 | # textual 193 | uc-micro-py==1.0.3 194 | # via linkify-it-py 195 | universal-pathlib==0.2.2 196 | # via hatch.envs.default 197 | urllib3==2.0.7 198 | # via 199 | # botocore 200 | # requests 201 | wrapt==1.16.0 202 | # via aiobotocore 203 | yarl==1.9.4 204 | # via aiohttp 205 | -------------------------------------------------------------------------------- /requirements/requirements-all.py3.10.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.10 3 | # 4 | # - pytest 5 | # - pytest-cov 6 | # - textual>=0.27.0 7 | # - universal-pathlib>=0.2.2 8 | # - adlfs 9 | # - aiohttp 10 | # - gcsfs 11 | # - paramiko 12 | # - requests>=2 13 | # - s3fs 14 | # 15 | 16 | adlfs==2024.2.0 17 | # via hatch.envs.all.py3.10 18 | aiobotocore==2.12.1 19 | # via s3fs 20 | aiohttp==3.9.3 21 | # via 22 | # hatch.envs.all.py3.10 23 | # adlfs 24 | # aiobotocore 25 | # gcsfs 26 | # s3fs 27 | aioitertools==0.11.0 28 | # via aiobotocore 29 | aiosignal==1.3.1 30 | # via aiohttp 31 | async-timeout==4.0.3 32 | # via aiohttp 33 | attrs==23.2.0 34 | # via aiohttp 35 | azure-core==1.30.1 36 | # via 37 | # adlfs 38 | # azure-identity 39 | # azure-storage-blob 40 | azure-datalake-store==0.0.53 41 | # via adlfs 42 | azure-identity==1.15.0 43 | # via adlfs 44 | azure-storage-blob==12.19.1 45 | # via adlfs 46 | bcrypt==4.1.2 47 | # via paramiko 48 | botocore==1.34.51 49 | # via aiobotocore 50 | cachetools==5.3.3 51 | # via google-auth 52 | certifi==2024.2.2 53 | # via requests 54 | cffi==1.16.0 55 | # via 56 | # azure-datalake-store 57 | # cryptography 58 | # pynacl 59 | charset-normalizer==3.3.2 60 | # via requests 61 | coverage==7.4.4 62 | # via pytest-cov 63 | cryptography==42.0.5 64 | # via 65 | # azure-identity 66 | # azure-storage-blob 67 | # msal 68 | # paramiko 69 | # pyjwt 70 | decorator==5.1.1 71 | # via gcsfs 72 | exceptiongroup==1.2.0 73 | # via pytest 74 | frozenlist==1.4.1 75 | # via 76 | # aiohttp 77 | # aiosignal 78 | fsspec==2024.3.1 79 | # via 80 | # adlfs 81 | # gcsfs 82 | # s3fs 83 | # universal-pathlib 84 | gcsfs==2024.3.1 85 | # via hatch.envs.all.py3.10 86 | google-api-core==2.17.1 87 | # via 88 | # google-cloud-core 89 | # google-cloud-storage 90 | google-auth==2.29.0 91 | # via 92 | # gcsfs 93 | # google-api-core 94 | # google-auth-oauthlib 95 | # google-cloud-core 96 | # google-cloud-storage 97 | google-auth-oauthlib==1.2.0 98 | # via gcsfs 99 | google-cloud-core==2.4.1 100 | # via google-cloud-storage 101 | google-cloud-storage==2.16.0 102 | # via gcsfs 103 | google-crc32c==1.5.0 104 | # via 105 | # google-cloud-storage 106 | # google-resumable-media 107 | google-resumable-media==2.7.0 108 | # via google-cloud-storage 109 | googleapis-common-protos==1.63.0 110 | # via google-api-core 111 | idna==3.6 112 | # via 113 | # requests 114 | # yarl 115 | iniconfig==2.0.0 116 | # via pytest 117 | isodate==0.6.1 118 | # via azure-storage-blob 119 | jmespath==1.0.1 120 | # via botocore 121 | linkify-it-py==2.0.3 122 | # via markdown-it-py 123 | markdown-it-py==3.0.0 124 | # via 125 | # mdit-py-plugins 126 | # rich 127 | # textual 128 | mdit-py-plugins==0.4.0 129 | # via markdown-it-py 130 | mdurl==0.1.2 131 | # via markdown-it-py 132 | msal==1.28.0 133 | # via 134 | # azure-datalake-store 135 | # azure-identity 136 | # msal-extensions 137 | msal-extensions==1.1.0 138 | # via azure-identity 139 | multidict==6.0.5 140 | # via 141 | # aiohttp 142 | # yarl 143 | oauthlib==3.2.2 144 | # via requests-oauthlib 145 | packaging==24.0 146 | # via 147 | # msal-extensions 148 | # pytest 149 | paramiko==3.4.0 150 | # via hatch.envs.all.py3.10 151 | pluggy==1.4.0 152 | # via pytest 153 | portalocker==2.8.2 154 | # via msal-extensions 155 | protobuf==4.25.3 156 | # via 157 | # google-api-core 158 | # googleapis-common-protos 159 | pyasn1==0.5.1 160 | # via 161 | # pyasn1-modules 162 | # rsa 163 | pyasn1-modules==0.3.0 164 | # via google-auth 165 | pycparser==2.21 166 | # via cffi 167 | pygments==2.17.2 168 | # via rich 169 | pyjwt==2.8.0 170 | # via 171 | # msal 172 | # pyjwt 173 | pynacl==1.5.0 174 | # via paramiko 175 | pytest==8.1.1 176 | # via 177 | # hatch.envs.all.py3.10 178 | # pytest-cov 179 | pytest-cov==4.1.0 180 | # via hatch.envs.all.py3.10 181 | python-dateutil==2.9.0.post0 182 | # via botocore 183 | requests==2.31.0 184 | # via 185 | # hatch.envs.all.py3.10 186 | # azure-core 187 | # azure-datalake-store 188 | # gcsfs 189 | # google-api-core 190 | # google-cloud-storage 191 | # msal 192 | # requests-oauthlib 193 | requests-oauthlib==1.4.0 194 | # via google-auth-oauthlib 195 | rich==13.7.1 196 | # via textual 197 | rsa==4.9 198 | # via google-auth 199 | s3fs==2024.3.1 200 | # via hatch.envs.all.py3.10 201 | six==1.16.0 202 | # via 203 | # azure-core 204 | # isodate 205 | # python-dateutil 206 | textual==0.53.1 207 | # via hatch.envs.all.py3.10 208 | tomli==2.0.1 209 | # via 210 | # coverage 211 | # pytest 212 | typing-extensions==4.10.0 213 | # via 214 | # azure-core 215 | # azure-storage-blob 216 | # textual 217 | uc-micro-py==1.0.3 218 | # via linkify-it-py 219 | universal-pathlib==0.2.2 220 | # via hatch.envs.all.py3.10 221 | urllib3==2.0.7 222 | # via 223 | # botocore 224 | # requests 225 | wrapt==1.16.0 226 | # via aiobotocore 227 | yarl==1.9.4 228 | # via aiohttp 229 | -------------------------------------------------------------------------------- /requirements/requirements-all.py3.11.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.11 3 | # 4 | # - pytest 5 | # - pytest-cov 6 | # - textual>=0.27.0 7 | # - universal-pathlib>=0.2.2 8 | # - adlfs 9 | # - aiohttp 10 | # - gcsfs 11 | # - paramiko 12 | # - requests>=2 13 | # - s3fs 14 | # 15 | 16 | adlfs==2024.2.0 17 | # via hatch.envs.all.py3.11 18 | aiobotocore==2.12.1 19 | # via s3fs 20 | aiohttp==3.9.3 21 | # via 22 | # hatch.envs.all.py3.11 23 | # adlfs 24 | # aiobotocore 25 | # gcsfs 26 | # s3fs 27 | aioitertools==0.11.0 28 | # via aiobotocore 29 | aiosignal==1.3.1 30 | # via aiohttp 31 | attrs==23.2.0 32 | # via aiohttp 33 | azure-core==1.30.1 34 | # via 35 | # adlfs 36 | # azure-identity 37 | # azure-storage-blob 38 | azure-datalake-store==0.0.53 39 | # via adlfs 40 | azure-identity==1.15.0 41 | # via adlfs 42 | azure-storage-blob==12.19.1 43 | # via adlfs 44 | bcrypt==4.1.2 45 | # via paramiko 46 | botocore==1.34.51 47 | # via aiobotocore 48 | cachetools==5.3.3 49 | # via google-auth 50 | certifi==2024.2.2 51 | # via requests 52 | cffi==1.16.0 53 | # via 54 | # azure-datalake-store 55 | # cryptography 56 | # pynacl 57 | charset-normalizer==3.3.2 58 | # via requests 59 | coverage==7.4.4 60 | # via pytest-cov 61 | cryptography==42.0.5 62 | # via 63 | # azure-identity 64 | # azure-storage-blob 65 | # msal 66 | # paramiko 67 | # pyjwt 68 | decorator==5.1.1 69 | # via gcsfs 70 | frozenlist==1.4.1 71 | # via 72 | # aiohttp 73 | # aiosignal 74 | fsspec==2024.3.1 75 | # via 76 | # adlfs 77 | # gcsfs 78 | # s3fs 79 | # universal-pathlib 80 | gcsfs==2024.3.1 81 | # via hatch.envs.all.py3.11 82 | google-api-core==2.17.1 83 | # via 84 | # google-cloud-core 85 | # google-cloud-storage 86 | google-auth==2.29.0 87 | # via 88 | # gcsfs 89 | # google-api-core 90 | # google-auth-oauthlib 91 | # google-cloud-core 92 | # google-cloud-storage 93 | google-auth-oauthlib==1.2.0 94 | # via gcsfs 95 | google-cloud-core==2.4.1 96 | # via google-cloud-storage 97 | google-cloud-storage==2.16.0 98 | # via gcsfs 99 | google-crc32c==1.5.0 100 | # via 101 | # google-cloud-storage 102 | # google-resumable-media 103 | google-resumable-media==2.7.0 104 | # via google-cloud-storage 105 | googleapis-common-protos==1.63.0 106 | # via google-api-core 107 | idna==3.6 108 | # via 109 | # requests 110 | # yarl 111 | iniconfig==2.0.0 112 | # via pytest 113 | isodate==0.6.1 114 | # via azure-storage-blob 115 | jmespath==1.0.1 116 | # via botocore 117 | linkify-it-py==2.0.3 118 | # via markdown-it-py 119 | markdown-it-py==3.0.0 120 | # via 121 | # mdit-py-plugins 122 | # rich 123 | # textual 124 | mdit-py-plugins==0.4.0 125 | # via markdown-it-py 126 | mdurl==0.1.2 127 | # via markdown-it-py 128 | msal==1.28.0 129 | # via 130 | # azure-datalake-store 131 | # azure-identity 132 | # msal-extensions 133 | msal-extensions==1.1.0 134 | # via azure-identity 135 | multidict==6.0.5 136 | # via 137 | # aiohttp 138 | # yarl 139 | oauthlib==3.2.2 140 | # via requests-oauthlib 141 | packaging==24.0 142 | # via 143 | # msal-extensions 144 | # pytest 145 | paramiko==3.4.0 146 | # via hatch.envs.all.py3.11 147 | pluggy==1.4.0 148 | # via pytest 149 | portalocker==2.8.2 150 | # via msal-extensions 151 | protobuf==4.25.3 152 | # via 153 | # google-api-core 154 | # googleapis-common-protos 155 | pyasn1==0.5.1 156 | # via 157 | # pyasn1-modules 158 | # rsa 159 | pyasn1-modules==0.3.0 160 | # via google-auth 161 | pycparser==2.21 162 | # via cffi 163 | pygments==2.17.2 164 | # via rich 165 | pyjwt==2.8.0 166 | # via 167 | # msal 168 | # pyjwt 169 | pynacl==1.5.0 170 | # via paramiko 171 | pytest==8.1.1 172 | # via 173 | # hatch.envs.all.py3.11 174 | # pytest-cov 175 | pytest-cov==4.1.0 176 | # via hatch.envs.all.py3.11 177 | python-dateutil==2.9.0.post0 178 | # via botocore 179 | requests==2.31.0 180 | # via 181 | # hatch.envs.all.py3.11 182 | # azure-core 183 | # azure-datalake-store 184 | # gcsfs 185 | # google-api-core 186 | # google-cloud-storage 187 | # msal 188 | # requests-oauthlib 189 | requests-oauthlib==1.4.0 190 | # via google-auth-oauthlib 191 | rich==13.7.1 192 | # via textual 193 | rsa==4.9 194 | # via google-auth 195 | s3fs==2024.3.1 196 | # via hatch.envs.all.py3.11 197 | six==1.16.0 198 | # via 199 | # azure-core 200 | # isodate 201 | # python-dateutil 202 | textual==0.53.1 203 | # via hatch.envs.all.py3.11 204 | typing-extensions==4.10.0 205 | # via 206 | # azure-core 207 | # azure-storage-blob 208 | # textual 209 | uc-micro-py==1.0.3 210 | # via linkify-it-py 211 | universal-pathlib==0.2.2 212 | # via hatch.envs.all.py3.11 213 | urllib3==2.0.7 214 | # via 215 | # botocore 216 | # requests 217 | wrapt==1.16.0 218 | # via aiobotocore 219 | yarl==1.9.4 220 | # via aiohttp 221 | -------------------------------------------------------------------------------- /requirements/requirements-all.py3.12.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.12 3 | # 4 | # - pytest 5 | # - pytest-cov 6 | # - textual>=0.27.0 7 | # - universal-pathlib>=0.2.2 8 | # - adlfs 9 | # - aiohttp 10 | # - gcsfs 11 | # - paramiko 12 | # - requests>=2 13 | # - s3fs 14 | # 15 | 16 | adlfs==2024.2.0 17 | # via hatch.envs.all.py3.12 18 | aiobotocore==2.12.1 19 | # via s3fs 20 | aiohttp==3.9.3 21 | # via 22 | # hatch.envs.all.py3.12 23 | # adlfs 24 | # aiobotocore 25 | # gcsfs 26 | # s3fs 27 | aioitertools==0.11.0 28 | # via aiobotocore 29 | aiosignal==1.3.1 30 | # via aiohttp 31 | attrs==23.2.0 32 | # via aiohttp 33 | azure-core==1.30.1 34 | # via 35 | # adlfs 36 | # azure-identity 37 | # azure-storage-blob 38 | azure-datalake-store==0.0.53 39 | # via adlfs 40 | azure-identity==1.15.0 41 | # via adlfs 42 | azure-storage-blob==12.19.1 43 | # via adlfs 44 | bcrypt==4.1.2 45 | # via paramiko 46 | botocore==1.34.51 47 | # via aiobotocore 48 | cachetools==5.3.3 49 | # via google-auth 50 | certifi==2024.2.2 51 | # via requests 52 | cffi==1.16.0 53 | # via 54 | # azure-datalake-store 55 | # cryptography 56 | # pynacl 57 | charset-normalizer==3.3.2 58 | # via requests 59 | coverage==7.4.4 60 | # via pytest-cov 61 | cryptography==42.0.5 62 | # via 63 | # azure-identity 64 | # azure-storage-blob 65 | # msal 66 | # paramiko 67 | # pyjwt 68 | decorator==5.1.1 69 | # via gcsfs 70 | frozenlist==1.4.1 71 | # via 72 | # aiohttp 73 | # aiosignal 74 | fsspec==2024.3.1 75 | # via 76 | # adlfs 77 | # gcsfs 78 | # s3fs 79 | # universal-pathlib 80 | gcsfs==2024.3.1 81 | # via hatch.envs.all.py3.12 82 | google-api-core==2.17.1 83 | # via 84 | # google-cloud-core 85 | # google-cloud-storage 86 | google-auth==2.29.0 87 | # via 88 | # gcsfs 89 | # google-api-core 90 | # google-auth-oauthlib 91 | # google-cloud-core 92 | # google-cloud-storage 93 | google-auth-oauthlib==1.2.0 94 | # via gcsfs 95 | google-cloud-core==2.4.1 96 | # via google-cloud-storage 97 | google-cloud-storage==2.16.0 98 | # via gcsfs 99 | google-crc32c==1.5.0 100 | # via 101 | # google-cloud-storage 102 | # google-resumable-media 103 | google-resumable-media==2.7.0 104 | # via google-cloud-storage 105 | googleapis-common-protos==1.63.0 106 | # via google-api-core 107 | idna==3.6 108 | # via 109 | # requests 110 | # yarl 111 | iniconfig==2.0.0 112 | # via pytest 113 | isodate==0.6.1 114 | # via azure-storage-blob 115 | jmespath==1.0.1 116 | # via botocore 117 | linkify-it-py==2.0.3 118 | # via markdown-it-py 119 | markdown-it-py==3.0.0 120 | # via 121 | # mdit-py-plugins 122 | # rich 123 | # textual 124 | mdit-py-plugins==0.4.0 125 | # via markdown-it-py 126 | mdurl==0.1.2 127 | # via markdown-it-py 128 | msal==1.28.0 129 | # via 130 | # azure-datalake-store 131 | # azure-identity 132 | # msal-extensions 133 | msal-extensions==1.1.0 134 | # via azure-identity 135 | multidict==6.0.5 136 | # via 137 | # aiohttp 138 | # yarl 139 | oauthlib==3.2.2 140 | # via requests-oauthlib 141 | packaging==24.0 142 | # via 143 | # msal-extensions 144 | # pytest 145 | paramiko==3.4.0 146 | # via hatch.envs.all.py3.12 147 | pluggy==1.4.0 148 | # via pytest 149 | portalocker==2.8.2 150 | # via msal-extensions 151 | protobuf==4.25.3 152 | # via 153 | # google-api-core 154 | # googleapis-common-protos 155 | pyasn1==0.5.1 156 | # via 157 | # pyasn1-modules 158 | # rsa 159 | pyasn1-modules==0.3.0 160 | # via google-auth 161 | pycparser==2.21 162 | # via cffi 163 | pygments==2.17.2 164 | # via rich 165 | pyjwt==2.8.0 166 | # via 167 | # msal 168 | # pyjwt 169 | pynacl==1.5.0 170 | # via paramiko 171 | pytest==8.1.1 172 | # via 173 | # hatch.envs.all.py3.12 174 | # pytest-cov 175 | pytest-cov==4.1.0 176 | # via hatch.envs.all.py3.12 177 | python-dateutil==2.9.0.post0 178 | # via botocore 179 | requests==2.31.0 180 | # via 181 | # hatch.envs.all.py3.12 182 | # azure-core 183 | # azure-datalake-store 184 | # gcsfs 185 | # google-api-core 186 | # google-cloud-storage 187 | # msal 188 | # requests-oauthlib 189 | requests-oauthlib==1.4.0 190 | # via google-auth-oauthlib 191 | rich==13.7.1 192 | # via textual 193 | rsa==4.9 194 | # via google-auth 195 | s3fs==2024.3.1 196 | # via hatch.envs.all.py3.12 197 | six==1.16.0 198 | # via 199 | # azure-core 200 | # isodate 201 | # python-dateutil 202 | textual==0.53.1 203 | # via hatch.envs.all.py3.12 204 | typing-extensions==4.10.0 205 | # via 206 | # azure-core 207 | # azure-storage-blob 208 | # textual 209 | uc-micro-py==1.0.3 210 | # via linkify-it-py 211 | universal-pathlib==0.2.2 212 | # via hatch.envs.all.py3.12 213 | urllib3==2.0.7 214 | # via 215 | # botocore 216 | # requests 217 | wrapt==1.16.0 218 | # via aiobotocore 219 | yarl==1.9.4 220 | # via aiohttp 221 | -------------------------------------------------------------------------------- /requirements/requirements-all.py3.8.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.8 3 | # 4 | # - pytest 5 | # - pytest-cov 6 | # - textual>=0.27.0 7 | # - universal-pathlib>=0.2.2 8 | # - adlfs 9 | # - aiohttp 10 | # - gcsfs 11 | # - paramiko 12 | # - requests>=2 13 | # - s3fs 14 | # 15 | 16 | adlfs==2024.2.0 17 | # via hatch.envs.all.py3.8 18 | aiobotocore==2.12.1 19 | # via s3fs 20 | aiohttp==3.9.3 21 | # via 22 | # hatch.envs.all.py3.8 23 | # adlfs 24 | # aiobotocore 25 | # gcsfs 26 | # s3fs 27 | aioitertools==0.11.0 28 | # via aiobotocore 29 | aiosignal==1.3.1 30 | # via aiohttp 31 | async-timeout==4.0.3 32 | # via aiohttp 33 | attrs==23.2.0 34 | # via aiohttp 35 | azure-core==1.30.1 36 | # via 37 | # adlfs 38 | # azure-identity 39 | # azure-storage-blob 40 | azure-datalake-store==0.0.53 41 | # via adlfs 42 | azure-identity==1.15.0 43 | # via adlfs 44 | azure-storage-blob==12.19.1 45 | # via adlfs 46 | bcrypt==4.1.2 47 | # via paramiko 48 | botocore==1.34.51 49 | # via aiobotocore 50 | cachetools==5.3.3 51 | # via google-auth 52 | certifi==2024.2.2 53 | # via requests 54 | cffi==1.16.0 55 | # via 56 | # azure-datalake-store 57 | # cryptography 58 | # pynacl 59 | charset-normalizer==3.3.2 60 | # via requests 61 | coverage==7.4.4 62 | # via pytest-cov 63 | cryptography==42.0.5 64 | # via 65 | # azure-identity 66 | # azure-storage-blob 67 | # msal 68 | # paramiko 69 | # pyjwt 70 | decorator==5.1.1 71 | # via gcsfs 72 | exceptiongroup==1.2.0 73 | # via pytest 74 | frozenlist==1.4.1 75 | # via 76 | # aiohttp 77 | # aiosignal 78 | fsspec==2024.3.1 79 | # via 80 | # adlfs 81 | # gcsfs 82 | # s3fs 83 | # universal-pathlib 84 | gcsfs==2024.3.1 85 | # via hatch.envs.all.py3.8 86 | google-api-core==2.17.1 87 | # via 88 | # google-cloud-core 89 | # google-cloud-storage 90 | google-auth==2.29.0 91 | # via 92 | # gcsfs 93 | # google-api-core 94 | # google-auth-oauthlib 95 | # google-cloud-core 96 | # google-cloud-storage 97 | google-auth-oauthlib==1.2.0 98 | # via gcsfs 99 | google-cloud-core==2.4.1 100 | # via google-cloud-storage 101 | google-cloud-storage==2.16.0 102 | # via gcsfs 103 | google-crc32c==1.5.0 104 | # via 105 | # google-cloud-storage 106 | # google-resumable-media 107 | google-resumable-media==2.7.0 108 | # via google-cloud-storage 109 | googleapis-common-protos==1.63.0 110 | # via google-api-core 111 | idna==3.6 112 | # via 113 | # requests 114 | # yarl 115 | iniconfig==2.0.0 116 | # via pytest 117 | isodate==0.6.1 118 | # via azure-storage-blob 119 | jmespath==1.0.1 120 | # via botocore 121 | linkify-it-py==2.0.3 122 | # via markdown-it-py 123 | markdown-it-py==3.0.0 124 | # via 125 | # mdit-py-plugins 126 | # rich 127 | # textual 128 | mdit-py-plugins==0.4.0 129 | # via markdown-it-py 130 | mdurl==0.1.2 131 | # via markdown-it-py 132 | msal==1.28.0 133 | # via 134 | # azure-datalake-store 135 | # azure-identity 136 | # msal-extensions 137 | msal-extensions==1.1.0 138 | # via azure-identity 139 | multidict==6.0.5 140 | # via 141 | # aiohttp 142 | # yarl 143 | oauthlib==3.2.2 144 | # via requests-oauthlib 145 | packaging==24.0 146 | # via 147 | # msal-extensions 148 | # pytest 149 | paramiko==3.4.0 150 | # via hatch.envs.all.py3.8 151 | pluggy==1.4.0 152 | # via pytest 153 | portalocker==2.8.2 154 | # via msal-extensions 155 | protobuf==4.25.3 156 | # via 157 | # google-api-core 158 | # googleapis-common-protos 159 | pyasn1==0.5.1 160 | # via 161 | # pyasn1-modules 162 | # rsa 163 | pyasn1-modules==0.3.0 164 | # via google-auth 165 | pycparser==2.21 166 | # via cffi 167 | pygments==2.17.2 168 | # via rich 169 | pyjwt==2.8.0 170 | # via 171 | # msal 172 | # pyjwt 173 | pynacl==1.5.0 174 | # via paramiko 175 | pytest==8.1.1 176 | # via 177 | # hatch.envs.all.py3.8 178 | # pytest-cov 179 | pytest-cov==4.1.0 180 | # via hatch.envs.all.py3.8 181 | python-dateutil==2.9.0.post0 182 | # via botocore 183 | requests==2.31.0 184 | # via 185 | # hatch.envs.all.py3.8 186 | # azure-core 187 | # azure-datalake-store 188 | # gcsfs 189 | # google-api-core 190 | # google-cloud-storage 191 | # msal 192 | # requests-oauthlib 193 | requests-oauthlib==1.4.0 194 | # via google-auth-oauthlib 195 | rich==13.7.1 196 | # via textual 197 | rsa==4.9 198 | # via google-auth 199 | s3fs==2024.3.1 200 | # via hatch.envs.all.py3.8 201 | six==1.16.0 202 | # via 203 | # azure-core 204 | # isodate 205 | # python-dateutil 206 | textual==0.53.1 207 | # via hatch.envs.all.py3.8 208 | tomli==2.0.1 209 | # via 210 | # coverage 211 | # pytest 212 | typing-extensions==4.10.0 213 | # via 214 | # aioitertools 215 | # azure-core 216 | # azure-storage-blob 217 | # rich 218 | # textual 219 | uc-micro-py==1.0.3 220 | # via linkify-it-py 221 | universal-pathlib==0.2.2 222 | # via hatch.envs.all.py3.8 223 | urllib3==1.26.18 224 | # via 225 | # botocore 226 | # requests 227 | wrapt==1.16.0 228 | # via aiobotocore 229 | yarl==1.9.4 230 | # via aiohttp 231 | -------------------------------------------------------------------------------- /requirements/requirements-all.py3.9.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.9 3 | # 4 | # - pytest 5 | # - pytest-cov 6 | # - textual>=0.27.0 7 | # - universal-pathlib>=0.2.2 8 | # - adlfs 9 | # - aiohttp 10 | # - gcsfs 11 | # - paramiko 12 | # - requests>=2 13 | # - s3fs 14 | # 15 | 16 | adlfs==2024.2.0 17 | # via hatch.envs.all.py3.9 18 | aiobotocore==2.12.1 19 | # via s3fs 20 | aiohttp==3.9.3 21 | # via 22 | # hatch.envs.all.py3.9 23 | # adlfs 24 | # aiobotocore 25 | # gcsfs 26 | # s3fs 27 | aioitertools==0.11.0 28 | # via aiobotocore 29 | aiosignal==1.3.1 30 | # via aiohttp 31 | async-timeout==4.0.3 32 | # via aiohttp 33 | attrs==23.2.0 34 | # via aiohttp 35 | azure-core==1.30.1 36 | # via 37 | # adlfs 38 | # azure-identity 39 | # azure-storage-blob 40 | azure-datalake-store==0.0.53 41 | # via adlfs 42 | azure-identity==1.15.0 43 | # via adlfs 44 | azure-storage-blob==12.19.1 45 | # via adlfs 46 | bcrypt==4.1.2 47 | # via paramiko 48 | botocore==1.34.51 49 | # via aiobotocore 50 | cachetools==5.3.3 51 | # via google-auth 52 | certifi==2024.2.2 53 | # via requests 54 | cffi==1.16.0 55 | # via 56 | # azure-datalake-store 57 | # cryptography 58 | # pynacl 59 | charset-normalizer==3.3.2 60 | # via requests 61 | coverage==7.4.4 62 | # via pytest-cov 63 | cryptography==42.0.5 64 | # via 65 | # azure-identity 66 | # azure-storage-blob 67 | # msal 68 | # paramiko 69 | # pyjwt 70 | decorator==5.1.1 71 | # via gcsfs 72 | exceptiongroup==1.2.0 73 | # via pytest 74 | frozenlist==1.4.1 75 | # via 76 | # aiohttp 77 | # aiosignal 78 | fsspec==2024.3.1 79 | # via 80 | # adlfs 81 | # gcsfs 82 | # s3fs 83 | # universal-pathlib 84 | gcsfs==2024.3.1 85 | # via hatch.envs.all.py3.9 86 | google-api-core==2.17.1 87 | # via 88 | # google-cloud-core 89 | # google-cloud-storage 90 | google-auth==2.29.0 91 | # via 92 | # gcsfs 93 | # google-api-core 94 | # google-auth-oauthlib 95 | # google-cloud-core 96 | # google-cloud-storage 97 | google-auth-oauthlib==1.2.0 98 | # via gcsfs 99 | google-cloud-core==2.4.1 100 | # via google-cloud-storage 101 | google-cloud-storage==2.16.0 102 | # via gcsfs 103 | google-crc32c==1.5.0 104 | # via 105 | # google-cloud-storage 106 | # google-resumable-media 107 | google-resumable-media==2.7.0 108 | # via google-cloud-storage 109 | googleapis-common-protos==1.63.0 110 | # via google-api-core 111 | idna==3.6 112 | # via 113 | # requests 114 | # yarl 115 | iniconfig==2.0.0 116 | # via pytest 117 | isodate==0.6.1 118 | # via azure-storage-blob 119 | jmespath==1.0.1 120 | # via botocore 121 | linkify-it-py==2.0.3 122 | # via markdown-it-py 123 | markdown-it-py==3.0.0 124 | # via 125 | # mdit-py-plugins 126 | # rich 127 | # textual 128 | mdit-py-plugins==0.4.0 129 | # via markdown-it-py 130 | mdurl==0.1.2 131 | # via markdown-it-py 132 | msal==1.28.0 133 | # via 134 | # azure-datalake-store 135 | # azure-identity 136 | # msal-extensions 137 | msal-extensions==1.1.0 138 | # via azure-identity 139 | multidict==6.0.5 140 | # via 141 | # aiohttp 142 | # yarl 143 | oauthlib==3.2.2 144 | # via requests-oauthlib 145 | packaging==24.0 146 | # via 147 | # msal-extensions 148 | # pytest 149 | paramiko==3.4.0 150 | # via hatch.envs.all.py3.9 151 | pluggy==1.4.0 152 | # via pytest 153 | portalocker==2.8.2 154 | # via msal-extensions 155 | protobuf==4.25.3 156 | # via 157 | # google-api-core 158 | # googleapis-common-protos 159 | pyasn1==0.5.1 160 | # via 161 | # pyasn1-modules 162 | # rsa 163 | pyasn1-modules==0.3.0 164 | # via google-auth 165 | pycparser==2.21 166 | # via cffi 167 | pygments==2.17.2 168 | # via rich 169 | pyjwt==2.8.0 170 | # via msal 171 | pynacl==1.5.0 172 | # via paramiko 173 | pytest==8.1.1 174 | # via 175 | # hatch.envs.all.py3.9 176 | # pytest-cov 177 | pytest-cov==4.1.0 178 | # via hatch.envs.all.py3.9 179 | python-dateutil==2.9.0.post0 180 | # via botocore 181 | requests==2.31.0 182 | # via 183 | # hatch.envs.all.py3.9 184 | # azure-core 185 | # azure-datalake-store 186 | # gcsfs 187 | # google-api-core 188 | # google-cloud-storage 189 | # msal 190 | # requests-oauthlib 191 | requests-oauthlib==1.4.0 192 | # via google-auth-oauthlib 193 | rich==13.7.1 194 | # via textual 195 | rsa==4.9 196 | # via google-auth 197 | s3fs==2024.3.1 198 | # via hatch.envs.all.py3.9 199 | six==1.16.0 200 | # via 201 | # azure-core 202 | # isodate 203 | # python-dateutil 204 | textual==0.53.1 205 | # via hatch.envs.all.py3.9 206 | tomli==2.0.1 207 | # via 208 | # coverage 209 | # pytest 210 | typing-extensions==4.10.0 211 | # via 212 | # aioitertools 213 | # azure-core 214 | # azure-storage-blob 215 | # textual 216 | uc-micro-py==1.0.3 217 | # via linkify-it-py 218 | universal-pathlib==0.2.2 219 | # via hatch.envs.all.py3.9 220 | urllib3==1.26.18 221 | # via 222 | # botocore 223 | # requests 224 | wrapt==1.16.0 225 | # via aiobotocore 226 | yarl==1.9.4 227 | # via aiohttp 228 | -------------------------------------------------------------------------------- /requirements/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.11 3 | # 4 | # [constraints] requirements.txt (SHA256: 532cb51b5469f2619129b6af918139783b9c46119122341f666ba8d4fe41fcfd) 5 | # 6 | # - markdown-callouts 7 | # - markdown-exec 8 | # - mkdocs 9 | # - mkdocs-autorefs 10 | # - mkdocs-click 11 | # - mkdocs-gen-files 12 | # - mkdocs-literate-nav 13 | # - mkdocs-material 14 | # - mkdocs-section-index 15 | # - mkdocstrings 16 | # - mkdocstrings-python 17 | # - pymdown-extensions 18 | # - textual>=0.27.0 19 | # - universal-pathlib>=0.2.2 20 | # 21 | 22 | babel==2.14.0 23 | # via mkdocs-material 24 | certifi==2024.2.2 25 | # via 26 | # -c requirements.txt 27 | # requests 28 | charset-normalizer==3.3.2 29 | # via 30 | # -c requirements.txt 31 | # requests 32 | click==8.1.7 33 | # via 34 | # mkdocs 35 | # mkdocs-click 36 | # mkdocstrings 37 | colorama==0.4.6 38 | # via 39 | # griffe 40 | # mkdocs-material 41 | fsspec==2024.3.1 42 | # via 43 | # -c requirements.txt 44 | # universal-pathlib 45 | ghp-import==2.1.0 46 | # via mkdocs 47 | griffe==0.42.1 48 | # via mkdocstrings-python 49 | idna==3.6 50 | # via 51 | # -c requirements.txt 52 | # requests 53 | jinja2==3.1.3 54 | # via 55 | # mkdocs 56 | # mkdocs-material 57 | # mkdocstrings 58 | linkify-it-py==2.0.3 59 | # via 60 | # -c requirements.txt 61 | # markdown-it-py 62 | markdown==3.5.2 63 | # via 64 | # markdown-callouts 65 | # mkdocs 66 | # mkdocs-autorefs 67 | # mkdocs-click 68 | # mkdocs-material 69 | # mkdocstrings 70 | # mkdocstrings-python 71 | # pymdown-extensions 72 | markdown-callouts==0.4.0 73 | # via hatch.envs.docs 74 | markdown-exec==1.8.0 75 | # via hatch.envs.docs 76 | markdown-it-py==3.0.0 77 | # via 78 | # -c requirements.txt 79 | # mdit-py-plugins 80 | # rich 81 | # textual 82 | markupsafe==2.1.5 83 | # via 84 | # jinja2 85 | # mkdocs 86 | # mkdocs-autorefs 87 | # mkdocstrings 88 | mdit-py-plugins==0.4.0 89 | # via 90 | # -c requirements.txt 91 | # markdown-it-py 92 | mdurl==0.1.2 93 | # via 94 | # -c requirements.txt 95 | # markdown-it-py 96 | mergedeep==1.3.4 97 | # via mkdocs 98 | mkdocs==1.5.3 99 | # via 100 | # hatch.envs.docs 101 | # mkdocs-autorefs 102 | # mkdocs-gen-files 103 | # mkdocs-literate-nav 104 | # mkdocs-material 105 | # mkdocs-section-index 106 | # mkdocstrings 107 | mkdocs-autorefs==1.0.1 108 | # via 109 | # hatch.envs.docs 110 | # mkdocstrings 111 | mkdocs-click==0.8.1 112 | # via hatch.envs.docs 113 | mkdocs-gen-files==0.5.0 114 | # via hatch.envs.docs 115 | mkdocs-literate-nav==0.6.1 116 | # via hatch.envs.docs 117 | mkdocs-material==9.5.14 118 | # via hatch.envs.docs 119 | mkdocs-material-extensions==1.3.1 120 | # via mkdocs-material 121 | mkdocs-section-index==0.3.8 122 | # via hatch.envs.docs 123 | mkdocstrings==0.24.1 124 | # via 125 | # hatch.envs.docs 126 | # mkdocstrings-python 127 | mkdocstrings-python==1.9.0 128 | # via hatch.envs.docs 129 | packaging==24.0 130 | # via 131 | # -c requirements.txt 132 | # mkdocs 133 | paginate==0.5.6 134 | # via mkdocs-material 135 | pathspec==0.12.1 136 | # via mkdocs 137 | platformdirs==4.2.0 138 | # via 139 | # mkdocs 140 | # mkdocstrings 141 | pygments==2.17.2 142 | # via 143 | # -c requirements.txt 144 | # mkdocs-material 145 | # rich 146 | pymdown-extensions==10.7.1 147 | # via 148 | # hatch.envs.docs 149 | # markdown-exec 150 | # mkdocs-material 151 | # mkdocstrings 152 | python-dateutil==2.9.0.post0 153 | # via 154 | # -c requirements.txt 155 | # ghp-import 156 | pyyaml==6.0.1 157 | # via 158 | # mkdocs 159 | # pymdown-extensions 160 | # pyyaml-env-tag 161 | pyyaml-env-tag==0.1 162 | # via mkdocs 163 | regex==2023.12.25 164 | # via mkdocs-material 165 | requests==2.31.0 166 | # via 167 | # -c requirements.txt 168 | # mkdocs-material 169 | rich==13.7.1 170 | # via 171 | # -c requirements.txt 172 | # textual 173 | six==1.16.0 174 | # via 175 | # -c requirements.txt 176 | # python-dateutil 177 | textual==0.53.1 178 | # via 179 | # -c requirements.txt 180 | # hatch.envs.docs 181 | typing-extensions==4.10.0 182 | # via 183 | # -c requirements.txt 184 | # textual 185 | uc-micro-py==1.0.3 186 | # via 187 | # -c requirements.txt 188 | # linkify-it-py 189 | universal-pathlib==0.2.2 190 | # via 191 | # -c requirements.txt 192 | # hatch.envs.docs 193 | urllib3==2.0.7 194 | # via 195 | # -c requirements.txt 196 | # requests 197 | watchdog==4.0.0 198 | # via mkdocs 199 | -------------------------------------------------------------------------------- /requirements/requirements-lint.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.11 3 | # 4 | # - mypy>=1.6.1 5 | # - ruff~=0.1.7 6 | # 7 | 8 | mypy==1.9.0 9 | # via hatch.envs.lint 10 | mypy-extensions==1.0.0 11 | # via mypy 12 | ruff==0.1.15 13 | # via hatch.envs.lint 14 | typing-extensions==4.10.0 15 | # via mypy 16 | -------------------------------------------------------------------------------- /requirements/requirements-test.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by hatch-pip-compile with Python 3.11 3 | # 4 | # [constraints] requirements.txt (SHA256: 532cb51b5469f2619129b6af918139783b9c46119122341f666ba8d4fe41fcfd) 5 | # 6 | # - pytest 7 | # - pytest-cov 8 | # - textual>=0.27.0 9 | # - universal-pathlib>=0.2.2 10 | # - adlfs 11 | # - aiohttp 12 | # - gcsfs 13 | # - paramiko 14 | # - requests>=2 15 | # - s3fs 16 | # 17 | 18 | adlfs==2024.2.0 19 | # via 20 | # -c requirements.txt 21 | # hatch.envs.test 22 | aiobotocore==2.12.1 23 | # via 24 | # -c requirements.txt 25 | # s3fs 26 | aiohttp==3.9.3 27 | # via 28 | # -c requirements.txt 29 | # hatch.envs.test 30 | # adlfs 31 | # aiobotocore 32 | # gcsfs 33 | # s3fs 34 | aioitertools==0.11.0 35 | # via 36 | # -c requirements.txt 37 | # aiobotocore 38 | aiosignal==1.3.1 39 | # via 40 | # -c requirements.txt 41 | # aiohttp 42 | attrs==23.2.0 43 | # via 44 | # -c requirements.txt 45 | # aiohttp 46 | azure-core==1.30.1 47 | # via 48 | # -c requirements.txt 49 | # adlfs 50 | # azure-identity 51 | # azure-storage-blob 52 | azure-datalake-store==0.0.53 53 | # via 54 | # -c requirements.txt 55 | # adlfs 56 | azure-identity==1.15.0 57 | # via 58 | # -c requirements.txt 59 | # adlfs 60 | azure-storage-blob==12.19.1 61 | # via 62 | # -c requirements.txt 63 | # adlfs 64 | bcrypt==4.1.2 65 | # via 66 | # -c requirements.txt 67 | # paramiko 68 | botocore==1.34.51 69 | # via 70 | # -c requirements.txt 71 | # aiobotocore 72 | cachetools==5.3.3 73 | # via 74 | # -c requirements.txt 75 | # google-auth 76 | certifi==2024.2.2 77 | # via 78 | # -c requirements.txt 79 | # requests 80 | cffi==1.16.0 81 | # via 82 | # -c requirements.txt 83 | # azure-datalake-store 84 | # cryptography 85 | # pynacl 86 | charset-normalizer==3.3.2 87 | # via 88 | # -c requirements.txt 89 | # requests 90 | coverage==7.4.4 91 | # via pytest-cov 92 | cryptography==42.0.5 93 | # via 94 | # -c requirements.txt 95 | # azure-identity 96 | # azure-storage-blob 97 | # msal 98 | # paramiko 99 | # pyjwt 100 | decorator==5.1.1 101 | # via 102 | # -c requirements.txt 103 | # gcsfs 104 | frozenlist==1.4.1 105 | # via 106 | # -c requirements.txt 107 | # aiohttp 108 | # aiosignal 109 | fsspec==2024.3.1 110 | # via 111 | # -c requirements.txt 112 | # adlfs 113 | # gcsfs 114 | # s3fs 115 | # universal-pathlib 116 | gcsfs==2024.3.1 117 | # via 118 | # -c requirements.txt 119 | # hatch.envs.test 120 | google-api-core==2.17.1 121 | # via 122 | # -c requirements.txt 123 | # google-cloud-core 124 | # google-cloud-storage 125 | google-auth==2.29.0 126 | # via 127 | # -c requirements.txt 128 | # gcsfs 129 | # google-api-core 130 | # google-auth-oauthlib 131 | # google-cloud-core 132 | # google-cloud-storage 133 | google-auth-oauthlib==1.2.0 134 | # via 135 | # -c requirements.txt 136 | # gcsfs 137 | google-cloud-core==2.4.1 138 | # via 139 | # -c requirements.txt 140 | # google-cloud-storage 141 | google-cloud-storage==2.16.0 142 | # via 143 | # -c requirements.txt 144 | # gcsfs 145 | google-crc32c==1.5.0 146 | # via 147 | # -c requirements.txt 148 | # google-cloud-storage 149 | # google-resumable-media 150 | google-resumable-media==2.7.0 151 | # via 152 | # -c requirements.txt 153 | # google-cloud-storage 154 | googleapis-common-protos==1.63.0 155 | # via 156 | # -c requirements.txt 157 | # google-api-core 158 | idna==3.6 159 | # via 160 | # -c requirements.txt 161 | # requests 162 | # yarl 163 | iniconfig==2.0.0 164 | # via pytest 165 | isodate==0.6.1 166 | # via 167 | # -c requirements.txt 168 | # azure-storage-blob 169 | jmespath==1.0.1 170 | # via 171 | # -c requirements.txt 172 | # botocore 173 | linkify-it-py==2.0.3 174 | # via 175 | # -c requirements.txt 176 | # markdown-it-py 177 | markdown-it-py==3.0.0 178 | # via 179 | # -c requirements.txt 180 | # mdit-py-plugins 181 | # rich 182 | # textual 183 | mdit-py-plugins==0.4.0 184 | # via 185 | # -c requirements.txt 186 | # markdown-it-py 187 | mdurl==0.1.2 188 | # via 189 | # -c requirements.txt 190 | # markdown-it-py 191 | msal==1.28.0 192 | # via 193 | # -c requirements.txt 194 | # azure-datalake-store 195 | # azure-identity 196 | # msal-extensions 197 | msal-extensions==1.1.0 198 | # via 199 | # -c requirements.txt 200 | # azure-identity 201 | multidict==6.0.5 202 | # via 203 | # -c requirements.txt 204 | # aiohttp 205 | # yarl 206 | oauthlib==3.2.2 207 | # via 208 | # -c requirements.txt 209 | # requests-oauthlib 210 | packaging==24.0 211 | # via 212 | # -c requirements.txt 213 | # msal-extensions 214 | # pytest 215 | paramiko==3.4.0 216 | # via 217 | # -c requirements.txt 218 | # hatch.envs.test 219 | pluggy==1.4.0 220 | # via pytest 221 | portalocker==2.8.2 222 | # via 223 | # -c requirements.txt 224 | # msal-extensions 225 | protobuf==4.25.3 226 | # via 227 | # -c requirements.txt 228 | # google-api-core 229 | # googleapis-common-protos 230 | pyasn1==0.5.1 231 | # via 232 | # -c requirements.txt 233 | # pyasn1-modules 234 | # rsa 235 | pyasn1-modules==0.3.0 236 | # via 237 | # -c requirements.txt 238 | # google-auth 239 | pycparser==2.21 240 | # via 241 | # -c requirements.txt 242 | # cffi 243 | pygments==2.17.2 244 | # via 245 | # -c requirements.txt 246 | # rich 247 | pyjwt==2.8.0 248 | # via 249 | # -c requirements.txt 250 | # msal 251 | # pyjwt 252 | pynacl==1.5.0 253 | # via 254 | # -c requirements.txt 255 | # paramiko 256 | pytest==8.1.1 257 | # via 258 | # hatch.envs.test 259 | # pytest-cov 260 | pytest-cov==4.1.0 261 | # via hatch.envs.test 262 | python-dateutil==2.9.0.post0 263 | # via 264 | # -c requirements.txt 265 | # botocore 266 | requests==2.31.0 267 | # via 268 | # -c requirements.txt 269 | # hatch.envs.test 270 | # azure-core 271 | # azure-datalake-store 272 | # gcsfs 273 | # google-api-core 274 | # google-cloud-storage 275 | # msal 276 | # requests-oauthlib 277 | requests-oauthlib==1.4.0 278 | # via 279 | # -c requirements.txt 280 | # google-auth-oauthlib 281 | rich==13.7.1 282 | # via 283 | # -c requirements.txt 284 | # textual 285 | rsa==4.9 286 | # via 287 | # -c requirements.txt 288 | # google-auth 289 | s3fs==2024.3.1 290 | # via 291 | # -c requirements.txt 292 | # hatch.envs.test 293 | six==1.16.0 294 | # via 295 | # -c requirements.txt 296 | # azure-core 297 | # isodate 298 | # python-dateutil 299 | textual==0.53.1 300 | # via 301 | # -c requirements.txt 302 | # hatch.envs.test 303 | typing-extensions==4.10.0 304 | # via 305 | # -c requirements.txt 306 | # azure-core 307 | # azure-storage-blob 308 | # textual 309 | uc-micro-py==1.0.3 310 | # via 311 | # -c requirements.txt 312 | # linkify-it-py 313 | universal-pathlib==0.2.2 314 | # via 315 | # -c requirements.txt 316 | # hatch.envs.test 317 | urllib3==2.0.7 318 | # via 319 | # -c requirements.txt 320 | # botocore 321 | # requests 322 | wrapt==1.16.0 323 | # via 324 | # -c requirements.txt 325 | # aiobotocore 326 | yarl==1.9.4 327 | # via 328 | # -c requirements.txt 329 | # aiohttp 330 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juftin/textual-universal-directorytree/9cbf049dac89a0d2d3bd9743975ee306e12092d6/tests/__init__.py -------------------------------------------------------------------------------- /tests/cassettes/test_github_screenshot.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - "*/*" 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.30.0 13 | authorization: 14 | - XXXXXXXXXX 15 | method: GET 16 | uri: https://api.github.com/repos/juftin/textual-universal-directorytree/git/trees/v1.0.0 17 | response: 18 | body: 19 | string: !!binary | 20 | H4sIAAAAAAAAA7WWTW/jRgyG/4vOWZvzyZncijaHAm0P7bEoFvNBJkpty5XkYtNF/nupbBArl8Us 21 | sNLB0kAY833Ed0h+7qaH1N12YKIvCozyGQGztiqh8sGXBJac4YRZeZVTd9NdxoNseJjn83S736dz 22 | v7vv54dL3pXhuB/pPEz7xwvP/Wk/06f5kg4fLqf+Xxonear9SGUexqd5JNrLvv3yMO2/Ifyyobv9 23 | 83N3TvODCHmNLsKOQ5U3HViQS9bz03lZv2y4eeX0VemiBcy4pEFR8GRD8CqgdjGGTN4gV3IbcTaH 24 | f755B9jfn4ZxoXhlVADe2itjPgz5jREJqq3WQRLIiIaqk7UiymijsDJnH6jwsqH/Tz6RDmC/d14X 25 | RdO+Wcqa9zzSB/HSsZ/lduL+fveUjodG+Joyeu1YkzFOgUAbEFyHKnMNUauSKtj84ogXeGWi2Qa+ 26 | WcoafqQDpYnGsnucGpG1NtaAA7JFYYyawIRiYqwGvDcKEF200ccrslh9G+RmKSvkX37+8e63P+52 27 | 86e5EViRZdYWC6dgfNIFmLItHoBcyOAZpQYg4xVYzss2wM1SVsC/3/3w0693u2NtxPXEbDMXJT/V 28 | OG+FEZUL4EhrdL5YCBj18ndfLO3AbmTpZikr3DqUlZO/Wp0JqzcadIpRxRxSYs2YotUeAhskZ2OJ 29 | ti6J3aILNYdf0R3/Xvi+pUItXOCMdsajKsFjkBSSd1gkpQDZUdKR8ZpO5V343sBfynOzlBXw+ek8 30 | Do/SxXfz0FyVwbEJFEtS6CWfqQQMNUUX0ZGJkm4ISvuq3yxsot6oJTVLWTGP9M9FJpcjneZWK0ur 31 | iRSqCY45KJ+KllFDPkBO2lpXjUXnQIrURlZuDr+inGlqxvMMiaGgBzmdCoOcV9IoMwajZmEsxDaJ 32 | wTfCaw7/Du9lGv34No1+fDeNXovx10uUNBrNQTpMiQwmQ2XtisbE3qUSycld67hMY5uUqNbwz3/J 33 | ADxeTiXNVLtbToeJnv8Hoc5SXe4LAAA= 34 | headers: 35 | Access-Control-Allow-Origin: 36 | - "*" 37 | Access-Control-Expose-Headers: 38 | - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, 39 | X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, 40 | X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, 41 | X-GitHub-Request-Id, Deprecation, Sunset 42 | Cache-Control: 43 | - private, max-age=60, s-maxage=60 44 | Content-Encoding: 45 | - gzip 46 | Content-Security-Policy: 47 | - default-src 'none' 48 | Content-Type: 49 | - application/json; charset=utf-8 50 | Date: 51 | - Thu, 01 Jun 2023 14:56:34 GMT 52 | ETag: 53 | - W/"35e7aa89a360c9055c24444336351b1da46238a88403e3479371471550efe3be" 54 | Last-Modified: 55 | - Sat, 20 May 2023 00:00:46 GMT 56 | Referrer-Policy: 57 | - origin-when-cross-origin, strict-origin-when-cross-origin 58 | Server: 59 | - GitHub.com 60 | Strict-Transport-Security: 61 | - max-age=31536000; includeSubdomains; preload 62 | Transfer-Encoding: 63 | - chunked 64 | Vary: 65 | - Accept, Authorization, Cookie, X-GitHub-OTP 66 | - Accept-Encoding, Accept, X-Requested-With 67 | X-Accepted-OAuth-Scopes: 68 | - "" 69 | X-Content-Type-Options: 70 | - nosniff 71 | X-Frame-Options: 72 | - deny 73 | X-GitHub-Media-Type: 74 | - github.v3; format=json 75 | X-GitHub-Request-Id: 76 | - DA4F:4B24:1946:35DE:6478B1A2 77 | X-OAuth-Scopes: 78 | - admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, 79 | admin:ssh_signing_key, audit_log, codespace, delete:packages, gist, notifications, 80 | project, read:enterprise, repo, user, workflow, write:discussion, write:packages 81 | X-RateLimit-Limit: 82 | - "5000" 83 | X-RateLimit-Remaining: 84 | - "4964" 85 | X-RateLimit-Reset: 86 | - "1685632562" 87 | X-RateLimit-Resource: 88 | - core 89 | X-RateLimit-Used: 90 | - "36" 91 | X-XSS-Protection: 92 | - "0" 93 | x-github-api-version-selected: 94 | - "2022-11-28" 95 | status: 96 | code: 200 97 | message: OK 98 | - request: 99 | body: null 100 | headers: 101 | Accept: 102 | - "*/*" 103 | Accept-Encoding: 104 | - gzip, deflate 105 | Connection: 106 | - keep-alive 107 | User-Agent: 108 | - python-requests/2.30.0 109 | authorization: 110 | - XXXXXXXXXX 111 | method: GET 112 | uri: https://raw.githubusercontent.com/juftin/textual-universal-directorytree/v1.0.0/.pre-commit-config.yaml 113 | response: 114 | body: 115 | string: !!binary | 116 | H4sIAAAAAAAAA71US4vbMBC+51foUNhdqOzQhhIEpZRCTz312gYzK41tsXq4mnE2gf74yuskxHWy 117 | 6ak62JbmwfcY2WANveOKGBokJX7o6L3lzaIG66oaiJWowREuFgm7SGoh8pJi2CjRMnekyrKx3PaP 118 | Ra4tu4Ry7HH2KdsYn+ilVOTSrRLbVbEqloeTl6g6bMb+1ijBKWOwoZHPrWWkDjSe5QiBO+16g0rc 119 | /Sxo27y5m3XAYGSsZW0d5scO0yxDt6if5B68uxLKClyJmKiJ04CvtulqlsfUDDKE2lk9T/IZlZGZ 120 | JcoMNjdb3BLYg7YEvkeHpYPQ9Nk4WcfkgRkTyVdVf1esb6me65n3h46S40QZISA1w5hICT3HrOnm 121 | JuCzifA2pThiZLYnO0Zs74tlsZTguhaKD/+A0U7sPEPWZVNYPlvD7cf1+q2QkuHxsF9tLo/Q78nx 122 | sO4/7R7uZ6dCFCO1ktBDYKurlJ0AwvLwrkLMw1q0jzQrfpjiNcayjQFcZbDL7mPQFidsj5xPfCda 123 | u6jBvS7U6OKkYwCPFwMGSSfbDZCU+N4HEl+iQfE5+yy/nuZrKl/gtM+mA+tWpD5canucUSVoT4x+ 124 | EuyAqBqu54CKjr+av1nk63GJw+z4CoNvOe+/Ix+m+ldvE1aEyYI7ZvwBJGYTsHEFAAA= 125 | headers: 126 | Accept-Ranges: 127 | - bytes 128 | Access-Control-Allow-Origin: 129 | - "*" 130 | Cache-Control: 131 | - max-age=300 132 | Connection: 133 | - keep-alive 134 | Content-Encoding: 135 | - gzip 136 | Content-Length: 137 | - "506" 138 | Content-Security-Policy: 139 | - default-src 'none'; style-src 'unsafe-inline'; sandbox 140 | Content-Type: 141 | - text/plain; charset=utf-8 142 | Date: 143 | - Thu, 01 Jun 2023 14:56:37 GMT 144 | ETag: 145 | - W/"c746f825a03c6a7644af41844edbce9492e6742f2226f210f7d42cb9db8c0f8d" 146 | Expires: 147 | - Thu, 01 Jun 2023 15:01:37 GMT 148 | Source-Age: 149 | - "0" 150 | Strict-Transport-Security: 151 | - max-age=31536000 152 | Vary: 153 | - Authorization,Accept-Encoding,Origin 154 | Via: 155 | - 1.1 varnish 156 | X-Cache: 157 | - MISS 158 | X-Cache-Hits: 159 | - "0" 160 | X-Content-Type-Options: 161 | - nosniff 162 | X-Fastly-Request-ID: 163 | - 6a0772fc7acc217f55e64a34a3b43838aa523343 164 | X-Frame-Options: 165 | - deny 166 | X-GitHub-Request-Id: 167 | - 40E0:5FF8:29BA42:30D8A5:6478B1A5 168 | X-Served-By: 169 | - cache-den8250-DEN 170 | X-Timer: 171 | - S1685631398.720750,VS0,VE154 172 | X-XSS-Protection: 173 | - 1; mode=block 174 | status: 175 | code: 200 176 | message: OK 177 | version: 1 178 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pytest Fixtures Shared Across all Unit Tests 3 | """ 4 | 5 | import pathlib 6 | from typing import Any, Dict, List 7 | 8 | import pytest 9 | from upath import UPath 10 | 11 | from tests.helpers import Screenshotter 12 | 13 | 14 | @pytest.fixture 15 | def repo_dir() -> pathlib.Path: 16 | """ 17 | Return the path to the repository root 18 | """ 19 | return pathlib.Path(__file__).parent.parent.resolve() 20 | 21 | 22 | @pytest.fixture 23 | def screenshot_dir(repo_dir: pathlib.Path) -> pathlib.Path: 24 | """ 25 | Return the path to the screenshot directory 26 | """ 27 | return repo_dir / "tests" / "screenshots" 28 | 29 | 30 | @pytest.fixture 31 | def github_release_path() -> UPath: 32 | """ 33 | Return the path to the GitHub Release 34 | """ 35 | release = "v1.0.0" 36 | uri = f"github://juftin:textual-universal-directorytree@{release}" 37 | return UPath(uri) 38 | 39 | 40 | @pytest.fixture(scope="module") 41 | def vcr_config() -> Dict[str, List[Any]]: 42 | """ 43 | VCR Cassette Privacy Enforcer 44 | 45 | This fixture ensures the API Credentials are obfuscated 46 | 47 | Returns 48 | ------- 49 | Dict[str, List[Any]]: 50 | The VCR Config 51 | """ 52 | return { 53 | "filter_headers": [("authorization", "XXXXXXXXXX")], 54 | "filter_query_parameters": [("user", "XXXXXXXXXX"), ("token", "XXXXXXXXXX")], 55 | } 56 | 57 | 58 | @pytest.fixture 59 | def screenshotter(github_release_path: UPath) -> Screenshotter: 60 | """ 61 | Return a Screenshotter 62 | 63 | Parameters 64 | ---------- 65 | github_release_path : UPath 66 | The path to the GitHub Release 67 | 68 | Returns 69 | ------- 70 | Screenshotter: 71 | The Screenshotter 72 | """ 73 | return Screenshotter(file_path=github_release_path) 74 | 75 | 76 | cassette = pytest.mark.vcr(scope="module") 77 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers for the tests 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import pathlib 8 | from os import environ, getenv 9 | 10 | from textual._doc import take_svg_screenshot 11 | 12 | from textual_universal_directorytree.app import UniversalDirectoryTreeApp 13 | 14 | 15 | class Screenshotter: 16 | """ 17 | App Screenshotter 18 | """ 19 | 20 | def __init__(self, file_path: str | pathlib.Path) -> None: 21 | """ 22 | Initialize the Screenshotter 23 | """ 24 | self.app = UniversalDirectoryTreeApp(path=str(file_path)) 25 | 26 | def take_screenshot( 27 | self, press: list[str] | None = None 28 | ) -> tuple[str, pathlib.Path]: 29 | """ 30 | Take a Screenshot 31 | """ 32 | screenshot = take_svg_screenshot( 33 | app=self.app, 34 | terminal_size=(120, 35), 35 | press=press or [], 36 | title=None, 37 | ) 38 | screenshot_path = self._get_screenshot_path() 39 | if getenv("REGENERATE_SCREENSHOTS", "0") != "0": 40 | screenshot_path.write_text(screenshot) 41 | return screenshot, screenshot_path 42 | 43 | @classmethod 44 | def _get_screenshot_path(cls) -> pathlib.Path: 45 | """ 46 | Get the Screenshot Path 47 | 48 | Screenshots are stored in the docs/screenshots directory 49 | so they can be included in the documentation 50 | """ 51 | test_dir = pathlib.Path(__file__).parent 52 | repo_dir = test_dir.parent 53 | docs_dir = repo_dir / "docs" 54 | screenshot_dir = docs_dir / "screenshots" 55 | current_test = environ["PYTEST_CURRENT_TEST"].split("::")[-1].split(" ")[0] 56 | screenshot_path = screenshot_dir / f"{current_test}.svg" 57 | return screenshot_path 58 | -------------------------------------------------------------------------------- /tests/test_screenshots.py: -------------------------------------------------------------------------------- 1 | """ 2 | Screenshot Testing 3 | """ 4 | 5 | from tests.conftest import cassette 6 | from tests.helpers import Screenshotter 7 | 8 | 9 | @cassette 10 | def test_github_screenshot(screenshotter: Screenshotter) -> None: 11 | """ 12 | Snapshot a release of this repo 13 | """ 14 | screenshot, screenshot_path = screenshotter.take_screenshot( 15 | press=[ 16 | "wait:3000", 17 | "down", 18 | "down", 19 | "down", 20 | "down", 21 | "down", 22 | "down", 23 | "down", 24 | "space", 25 | "enter", 26 | "_", 27 | ] 28 | ) 29 | assert screenshot_path.read_text() == screenshot 30 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test utils 3 | """ 4 | 5 | from textual_universal_directorytree import UPath 6 | from textual_universal_directorytree.alternate_paths import ( 7 | GitHubTextualPath, 8 | S3TextualPath, 9 | ) 10 | from textual_universal_directorytree.utils import is_local_path, is_remote_path 11 | 12 | 13 | def test_is_local_path() -> None: 14 | """ 15 | Test is_local_path + is_remote_path 16 | """ 17 | local_tests_dir = UPath("tests") 18 | assert is_local_path(local_tests_dir) is True 19 | assert is_remote_path(local_tests_dir) is False 20 | local_dot_dir = UPath(".") 21 | assert is_local_path(local_dot_dir) is True 22 | assert is_remote_path(local_dot_dir) is False 23 | github_path = UPath("github://juftin:textual-universal-directorytree@main") 24 | assert is_local_path(github_path) is False 25 | assert is_remote_path(github_path) is True 26 | assert isinstance(github_path, GitHubTextualPath) 27 | s3_path = UPath("s3://bucket/key") 28 | assert is_local_path(s3_path) is False 29 | assert is_remote_path(s3_path) is True 30 | assert isinstance(s3_path, S3TextualPath) 31 | -------------------------------------------------------------------------------- /textual_universal_directorytree/__about__.py: -------------------------------------------------------------------------------- 1 | """ 2 | textual-universal-directorytree info file 3 | """ 4 | 5 | __author__ = "Justin Flannery" 6 | __email__ = "juftin@juftin.com" 7 | __application__ = "textual-universal-directorytree" 8 | __version__ = "1.5.0" 9 | -------------------------------------------------------------------------------- /textual_universal_directorytree/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Textual Universal Directory Tree 3 | """ 4 | 5 | from upath import UPath, registry 6 | 7 | from textual_universal_directorytree.alternate_paths import ( 8 | GitHubTextualPath, 9 | S3TextualPath, 10 | SFTPTextualPath, 11 | ) 12 | from textual_universal_directorytree.universal_directory_tree import ( 13 | UniversalDirectoryTree, 14 | ) 15 | from textual_universal_directorytree.utils import is_local_path, is_remote_path 16 | 17 | registry.register_implementation(protocol="github", cls=GitHubTextualPath, clobber=True) 18 | registry.register_implementation(protocol="s3", cls=S3TextualPath, clobber=True) 19 | registry.register_implementation(protocol="s3a", cls=S3TextualPath, clobber=True) 20 | registry.register_implementation(protocol="ssh", cls=SFTPTextualPath, clobber=True) 21 | registry.register_implementation(protocol="sftp", cls=SFTPTextualPath, clobber=True) 22 | 23 | __all__ = [ 24 | "UniversalDirectoryTree", 25 | "is_local_path", 26 | "is_remote_path", 27 | "GitHubTextualPath", 28 | "S3TextualPath", 29 | "UPath", 30 | "SFTPTextualPath", 31 | ] 32 | -------------------------------------------------------------------------------- /textual_universal_directorytree/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | __main__ hook 3 | """ 4 | 5 | from textual_universal_directorytree.app import cli 6 | 7 | if __name__ == "__main__": 8 | cli() 9 | -------------------------------------------------------------------------------- /textual_universal_directorytree/alternate_paths.py: -------------------------------------------------------------------------------- 1 | """ 2 | Textual's Implementation of Pathlib, Powered by fsspec 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import os 8 | import re 9 | from typing import Any 10 | 11 | from upath import UPath 12 | from upath.implementations.cloud import S3Path 13 | from upath.implementations.github import GitHubPath 14 | 15 | 16 | class GitHubTextualPath(GitHubPath): 17 | """ 18 | GitHubPath 19 | 20 | UPath implementation for GitHub to be compatible with the Directory Tree 21 | """ 22 | 23 | @classmethod 24 | def _transform_init_args( 25 | cls, 26 | args: tuple[str | os.PathLike[Any], ...], 27 | protocol: str, 28 | storage_options: dict[str, Any], 29 | ) -> tuple[tuple[str | os.PathLike[Any], ...], str, dict[str, Any]]: 30 | """ 31 | Initialize the GitHubPath with GitHub Token Authentication 32 | """ 33 | if "token" not in storage_options: 34 | token = os.getenv("GITHUB_TOKEN") 35 | if token is not None: 36 | storage_options.update({"username": "Bearer", "token": token}) 37 | handled_args = args 38 | if "sha" not in storage_options: 39 | handled_url = cls.handle_github_url(args[0]) 40 | handled_args = (handled_url, *args[1:]) 41 | return handled_args, protocol, storage_options 42 | 43 | def __str__(self) -> str: 44 | """ 45 | String representation of the GitHubPath 46 | """ 47 | return ( 48 | f"{self.protocol}://{self.storage_options['org']}:" 49 | f"{self.storage_options['repo']}@{self.storage_options['sha']}/" 50 | f"{self.path}" 51 | ) 52 | 53 | @classmethod 54 | def handle_github_url(cls, url: str | GitHubPath) -> str: 55 | """ 56 | Handle GitHub URLs 57 | 58 | GitHub URLs are handled by converting them to the raw URL. 59 | """ 60 | try: 61 | import requests 62 | except ImportError as e: 63 | raise ImportError( 64 | "The requests library is required to browse GitHub files. " 65 | "Install `textual-universal-directorytree` with the `remote` " 66 | "extra to install requests." 67 | ) from e 68 | url = str(url) 69 | gitub_prefix = "github://" 70 | if gitub_prefix in url and "@" not in url: 71 | _, user_password = url.split("github://") 72 | org, repo_str = user_password.split(":") 73 | repo, *args = repo_str.split("/") 74 | elif gitub_prefix in url and "@" in url: 75 | return url 76 | else: 77 | msg = f"Invalid GitHub URL: {url}" 78 | raise ValueError(msg) 79 | token = os.getenv("GITHUB_TOKEN") 80 | auth = {"auth": ("Bearer", token)} if token is not None else {} 81 | resp = requests.get( 82 | f"https://api.github.com/repos/{org}/{repo}", 83 | headers={"Accept": "application/vnd.github.v3+json"}, 84 | timeout=30, 85 | **auth, # type: ignore[arg-type] 86 | ) 87 | resp.raise_for_status() 88 | default_branch = resp.json()["default_branch"] 89 | arg_str = "/".join(args) 90 | github_uri = f"{gitub_prefix}{org}:{repo}@{default_branch}/{arg_str}" 91 | return github_uri 92 | 93 | @property 94 | def name(self) -> str: 95 | """ 96 | Override the name for top level repo 97 | """ 98 | original_name = super().name 99 | if original_name == "": 100 | return self.storage_options["repo"] 101 | else: 102 | return original_name 103 | 104 | 105 | class S3TextualPath(S3Path): 106 | """ 107 | S3TextualPath 108 | """ 109 | 110 | def _is_top_level_bucket(self) -> bool: 111 | """ 112 | Check if the path is a top level bucket 113 | """ 114 | return len(self.parts) == 1 and self.parts[0] == "/" 115 | 116 | def __str__(self) -> str: 117 | """ 118 | String representation of the S3Path 119 | """ 120 | return re.sub(r"s3:/*", "s3://", super().__str__()) 121 | 122 | @property 123 | def name(self) -> str: 124 | """ 125 | Override the name for top level repo 126 | """ 127 | original_name = super().name 128 | if self._is_top_level_bucket(): 129 | return f"{self._url.scheme}://{self._url.netloc}" 130 | elif original_name == "": 131 | return self.parts[-1].rstrip("/") 132 | else: 133 | return original_name 134 | 135 | 136 | class SFTPTextualPath(UPath): 137 | """ 138 | SFTPTextualPath 139 | """ 140 | 141 | @property 142 | def path(self) -> str: 143 | """ 144 | Always return the path relative to the root 145 | """ 146 | pth = super().path 147 | if pth.startswith("."): 148 | return f"/{pth[1:]}" 149 | elif pth.startswith("/"): 150 | return pth 151 | else: 152 | return "/" + pth 153 | 154 | def __str__(self) -> str: 155 | """ 156 | Add the protocol prefix + extras to the string representation 157 | """ 158 | string_representation = f"{self.protocol}://" 159 | if "username" in self.storage_options: 160 | string_representation += f"{self.storage_options['username']}@" 161 | string_representation += f"{self.storage_options['host']}" 162 | if "port" in self.storage_options: 163 | string_representation += f":{self.storage_options['port']}" 164 | string_representation += self.path 165 | return string_representation 166 | 167 | @property 168 | def name(self) -> str: 169 | """ 170 | Override the name for top level repo 171 | """ 172 | original_name = super().name 173 | if original_name == "": 174 | return "/" 175 | else: 176 | return original_name 177 | -------------------------------------------------------------------------------- /textual_universal_directorytree/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example Universal Directory Tree App 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import argparse 8 | import os 9 | from typing import Any, ClassVar 10 | 11 | from rich.syntax import Syntax 12 | from textual import on 13 | from textual.app import App, ComposeResult 14 | from textual.binding import BindingType 15 | from textual.containers import Horizontal, VerticalScroll 16 | from textual.widgets import DirectoryTree, Footer, Header, Static 17 | from upath import UPath 18 | 19 | from textual_universal_directorytree import UniversalDirectoryTree 20 | 21 | 22 | class UniversalDirectoryTreeApp(App): 23 | """ 24 | The power of upath and fsspec in a Textual app 25 | """ 26 | 27 | TITLE = "UniversalDirectoryTree" 28 | 29 | CSS = """ 30 | UniversalDirectoryTree { 31 | max-width: 50%; 32 | width: auto; 33 | height: 100%; 34 | dock: left; 35 | } 36 | """ 37 | 38 | BINDINGS: ClassVar[list[BindingType]] = [ 39 | ("q", "quit", "Quit"), 40 | ] 41 | 42 | def __init__(self, path: str | UPath, *args: Any, **kwargs: Any): 43 | super().__init__(*args, **kwargs) 44 | self.universal_path = UPath(path).resolve() 45 | self.directory_tree = UniversalDirectoryTree(path=self.universal_path) 46 | self.file_content = Static(expand=True) 47 | 48 | def compose(self) -> ComposeResult: 49 | yield Header() 50 | yield Horizontal(self.directory_tree, VerticalScroll(self.file_content)) 51 | yield Footer() 52 | 53 | @on(DirectoryTree.FileSelected) 54 | def handle_file_selected(self, message: DirectoryTree.FileSelected) -> None: 55 | """ 56 | Do something with the selected file. 57 | 58 | Objects returned by the FileSelected event are upath.UPath objects and 59 | they are compatible with the familiar pathlib.Path API built into Python. 60 | """ 61 | self.sub_title = str(message.path) 62 | try: 63 | file_content = message.path.read_text() 64 | except UnicodeDecodeError: 65 | self.file_content.update("") 66 | return None 67 | lexer = Syntax.guess_lexer(path=message.path.name, code=file_content) 68 | code = Syntax(code=file_content, lexer=lexer) 69 | self.file_content.update(code) 70 | 71 | 72 | def cli() -> None: 73 | """ 74 | Command Line Interface for the example App 75 | """ 76 | parser = argparse.ArgumentParser(description="Universal Directory Tree") 77 | parser.add_argument("path", type=str, help="Path to open", default=".") 78 | args = parser.parse_args() 79 | cli_app = UniversalDirectoryTreeApp(path=args.path) 80 | cli_app.run() 81 | 82 | 83 | app = UniversalDirectoryTreeApp(path=os.getenv("UDT_PATH", os.getcwd())) 84 | 85 | if __name__ == "__main__": 86 | cli() 87 | -------------------------------------------------------------------------------- /textual_universal_directorytree/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juftin/textual-universal-directorytree/9cbf049dac89a0d2d3bd9743975ee306e12092d6/textual_universal_directorytree/py.typed -------------------------------------------------------------------------------- /textual_universal_directorytree/universal_directory_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Universal Directory Tree Widget 3 | """ 4 | 5 | from textual.widgets import DirectoryTree 6 | from upath import UPath 7 | 8 | 9 | class UniversalDirectoryTree(DirectoryTree): 10 | """ 11 | Universal Directory Tree Widget 12 | """ 13 | 14 | PATH = UPath 15 | -------------------------------------------------------------------------------- /textual_universal_directorytree/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Universal Directory Tree Utils 3 | """ 4 | 5 | import pathlib 6 | 7 | from upath import UPath 8 | from upath.implementations.local import LocalPath 9 | 10 | 11 | def is_local_path(path: pathlib.Path) -> bool: 12 | """ 13 | Check if the path is a local path 14 | """ 15 | return isinstance(path, LocalPath) 16 | 17 | 18 | def is_remote_path(path: UPath) -> bool: 19 | """ 20 | Check if the path is a remote path 21 | """ 22 | return not is_local_path(path) 23 | --------------------------------------------------------------------------------