diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..c810363 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,28 @@ +name: Checks (pylint,pytest) + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi + - name: Analysing the code with pylint + run: | + pylint --fail-under=6 $(git ls-files '*.py') + - name: Run Tests + run: | + pytest + diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..60864dc --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ "master", "stable" ] + pull_request: + branches: [ "master", "stable" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..b417be8 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,43 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi + - name: Build package + run: python -m build + - name: Publish package + run: | + python -m site +# uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 +# with: +# user: __token__ +# password: ${{ secrets.PYPI_API_TOKEN }} +# diff --git a/.gitignore b/.gitignore index 2a21361..e317ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ lib64/ parts/ sdist/ var/ +venv/ +.vscode/ wheels/ *.egg-info/ .installed.cfg diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..24b38cf --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,16 @@ +setupextras >= 0.1.5 +coloredlogs >= 10.0 +termcolor >= 1.1.0 + +colour-runner >= 0.0.5 +deepdiff >= 3.3.0 +pygments >= 2.2.0 +tox >= 3.0.0 +coverage >= 4.5.2 +codecov >= 2.0.15 + +flake8 +pytest +pylint +black +build diff --git a/requirements-ide.txt b/requirements-ide.txt new file mode 100644 index 0000000..0894e19 --- /dev/null +++ b/requirements-ide.txt @@ -0,0 +1,4 @@ +python-language-server[all] +pyls-black +pyls-isort +pyls-mypy diff --git a/requirements.txt b/requirements.txt index a5995f8..9b9fa5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1 @@ -setupextras >= 0.1.5 six >= 1.11.0 -coloredlogs >= 10.0 -termcolor >= 1.1.0 - -colour-runner >= 0.0.5 -deepdiff >= 3.3.0 -pygments >= 2.2.0 -tox >= 3.0.0 -coverage >= 4.5.2 -codecov >= 2.0.15 diff --git a/rootpath/append.py b/rootpath/append.py index 8a769db..8752bd0 100644 --- a/rootpath/append.py +++ b/rootpath/append.py @@ -1,15 +1,13 @@ - # ========================================= # DEPS # -------------------------------------- import sys -import os from os import path CURRENT_PATH = path.abspath(path.dirname(__file__)) -ROOT_PATH = path.abspath(path.join(CURRENT_PATH, '..')) +ROOT_PATH = path.abspath(path.join(CURRENT_PATH, "..")) try: try: @@ -24,13 +22,12 @@ import rootpath - # ========================================= # FUNCTIONS # -------------------------------------- -def append(current_path = None, pattern = None): +def append(current_path=None, pattern=None): """ Automatically adds current file's package root to Python load path (i.e. `sys.path`) unless already added. This makes it possible to always ensure module imports behave same no matter how the file is loaded. @@ -44,8 +41,11 @@ def append(current_path = None, pattern = None): """ project_root_path = rootpath.detect(current_path, pattern) + if project_root_path is None: + return False, project_root_path + try: - if project_root_path != current_path: + if current_path is not None and project_root_path != current_path: try: sys.path.remove(current_path) except: @@ -55,7 +55,7 @@ def append(current_path = None, pattern = None): return False, project_root_path - except ValueError as error: + except ValueError: sys.path.append(project_root_path) return True, project_root_path diff --git a/rootpath/detect.py b/rootpath/detect.py index e241451..8c39e26 100644 --- a/rootpath/detect.py +++ b/rootpath/detect.py @@ -1,4 +1,3 @@ - # ========================================= # IMPORTS # -------------------------------------- @@ -10,21 +9,19 @@ from os import path, listdir - # ========================================= # CONSTANTS # -------------------------------------- -DEFAULT_PATH = '.' -DEFAULT_ROOT_FILENAME_MATCH_PATTERN = '.git|requirements.txt' - +DEFAULT_PATH = "." +DEFAULT_ROOT_FILENAME_MATCH_PATTERN = ".git|requirements.txt|pyproject.toml" # ========================================= # FUNCTIONS # -------------------------------------- -def detect(current_path = None, pattern = None): +def detect(current_path=None, pattern=None): """ Find project root path from specified file/directory path, based on common project root file pattern. @@ -46,7 +43,7 @@ def detect(current_path = None, pattern = None): if not path.isdir(current_path): current_path = path.dirname(current_path) - def find_root_path(current_path, pattern = None): + def find_root_path(current_path, pattern): if isinstance(pattern, six.string_types): pattern = re.compile(pattern) @@ -59,9 +56,12 @@ def find_root_path(current_path, pattern = None): file_names = None root_file_names = None - while (detecting): - file_names = listdir(current_path) - found_more_files = bool(len(file_names) > 0) + while detecting: + try: + file_names = listdir(current_path) + found_more_files = bool(len(file_names) > 0) + except FileNotFoundError: + return None if not found_more_files: detecting = False @@ -84,13 +84,13 @@ def find_root_path(current_path, pattern = None): return None system_root = sys.executable - + while os.path.split(system_root)[1]: system_root = os.path.split(system_root)[0] if current_path == system_root: return None - current_path = path.abspath(path.join(current_path, '..')) + current_path = path.abspath(path.join(current_path, "..")) return find_root_path(current_path, pattern) diff --git a/rootpath/tests/test_append.py b/rootpath/tests/test_append.py index 5118192..16b7a5e 100644 --- a/rootpath/tests/test_append.py +++ b/rootpath/tests/test_append.py @@ -58,7 +58,8 @@ def test__import(self): def test_rootpath_append_base(self): try: - sys.path.remove(ROOT_PATH) + while sys.path.count(ROOT_PATH) > 0: + sys.path.remove(ROOT_PATH) except: pass diff --git a/rootpath/tests/test_detect.py b/rootpath/tests/test_detect.py index f4b9a90..1da7b50 100644 --- a/rootpath/tests/test_detect.py +++ b/rootpath/tests/test_detect.py @@ -174,7 +174,7 @@ def test_rootpath_detect_entry_nested_pattern(self): self.assertEqual(root_path, None) def test_system_root(self): - root_path = rootpath.detect(r"\Users\Public\Downloads") + root_path = rootpath.detect(r"/Users/Public/Downloads") self.assertEqual(root_path, None) diff --git a/setup.py b/setup.py index a700b0e..072b075 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ - # ========================================= # IMPORTS # -------------------------------------- import os import setuptools -import setupextras + +# import setupextras # DISABLED/BUG: this line fails when `pip install palmtree` but works `pip install .` # from palmtree import __version__ @@ -14,81 +14,90 @@ # MAIN # -------------------------------------- -name = 'rootpath' -version = '0.1.1' -description = 'Python project/package root path detection.' +name = "rootpath" +version = "0.2.1" +description = "Python project/package root path detection." keywords = [ - 'python', - 'utlity', - 'common', - 'root', - 'rootpath', - 'root-path', - 'detect', - 'autodetect', - 'auto-detect', - 'project-root', - 'project-root-path', - 'package-root', - 'package-root-path', + "python", + "utlity", + "common", + "root", + "rootpath", + "root-path", + "detect", + "autodetect", + "auto-detect", + "project-root", + "project-root-path", + "package-root", + "package-root-path", ] -packages = setupextras.get_packages() -data_files = setupextras.get_data_files(['*.*'], os.path.join(name, 'tests', '__fixtures__')) -requirements = setupextras.get_requirements() -readme = setupextras.get_readme() +## +# setupextras setup support +# +# packages = setupextras.get_packages() +# data_files = setupextras.get_data_files(['*.*'], os.path.join(name, 'tests', '__fixtures__')) +# requirements = setupextras.get_requirements() +# readme = setupextras.get_readme() -config = { - 'name': name, - 'version': version, - 'description': (description), - 'keywords': keywords, - 'author': 'Jonas Grimfelt', - 'author_email': 'grimen@gmail.com', - 'url': 'https://github.com/grimen/python-{name}'.format(name = name), - 'download_url': 'https://github.com/grimen/python-{name}'.format(name = name), - 'project_urls': { - 'repository': 'https://github.com/grimen/python-{name}'.format(name = name), - 'bugs': 'https://github.com/grimen/python-{name}/issues'.format(name = name), - }, - 'license': 'MIT', +packages = ["rootpath"] +data_files = [] +requirements = [ + "six >= 1.11.0", +] - 'long_description': readme, - 'long_description_content_type': 'text/markdown', +this_directory = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: + readme = f.read() - 'classifiers': [ - 'Topic :: Software Development :: Libraries', - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', +config = { + "name": name, + "version": version, + "description": (description), + "keywords": keywords, + "author": "Jonas Grimfelt", + "author_email": "grimen@gmail.com", + "url": "https://github.com/grimen/python-{name}".format(name=name), + "download_url": "https://github.com/grimen/python-{name}".format(name=name), + "project_urls": { + "repository": "https://github.com/grimen/python-{name}".format(name=name), + "bugs": "https://github.com/grimen/python-{name}/issues".format(name=name), + }, + "license": "MIT", + "long_description": readme, + "long_description_content_type": "text/markdown", + "classifiers": [ + "Topic :: Software Development :: Libraries", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", ], - - 'packages': packages, - 'package_dir': { + "packages": packages, + "package_dir": { name: name, }, - 'package_data': { - '': [ - 'MIT-LICENSE', - 'README.md', + "package_data": { + "": [ + "MIT-LICENSE", + "README.md", ], name: [ - '*.*', + "*.*", ], }, - 'data_files': data_files, - 'include_package_data': True, - 'zip_safe': True, - - 'install_requires': requirements, - 'setup_requires': [ - 'setuptools_git >= 1.2', + "data_files": data_files, + "include_package_data": True, + "zip_safe": True, + "install_requires": requirements, + "setup_requires": [ + "setuptools_git >= 1.2", ], }