diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37e187e..65fc116 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,8 +30,8 @@ jobs: - name: build run: make - - name: mypy - run: make mypy + - name: check-types + run: make check-types - name: tests run: env TOX_PARALLEL_NO_SPINNER=1 make test diff --git a/Dockerfile b/Dockerfile index 53c14ee..1d27a5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM andreyfedoseev/django-static-precompiler:20.04-1 +FROM andreyfedoseev/django-static-precompiler:22.04-1 ARG DEBIAN_FRONTEND=noninteractive ENV TZ=Etc/UTC RUN apt update && \ @@ -7,6 +7,7 @@ RUN apt update && \ apt install -y \ python3-venv \ python3.8-dev \ + python3.8-distutils \ python3.9-dev \ python3.9-distutils \ python3.10-dev \ @@ -25,6 +26,7 @@ RUN python3 -m venv $POETRY_HOME && \ RUN mkdir /app WORKDIR /app ADD poetry.lock pyproject.toml /app/ -RUN poetry install --all-extras --no-root --no-interaction +RUN poetry install --all-extras --no-root --no-interaction && \ + pyright --version # this is to force pyright to install its dependencies ADD . /app/ RUN poetry install --all-extras --no-interaction diff --git a/Dockerfile-base b/Dockerfile-base index d0ddd14..8f5e045 100644 --- a/Dockerfile-base +++ b/Dockerfile-base @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 MAINTAINER Andrey Fedoseev ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y autoconf libtool npm wget diff --git a/Makefile b/Makefile index c8a97aa..9189b91 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ shell: test: docker compose run --rm app tox -p auto -mypy: - docker compose run --rm app mypy --strict ./src +check-types: + docker compose run --rm app pyright package: rm -rf ./dist diff --git a/poetry.lock b/poetry.lock index c3b7510..68a099a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -163,7 +163,7 @@ files = [ django = "*" django-stubs-ext = ">=4.2.2" mypy = [ - {version = ">=1.0.0"}, + {version = ">=1.0.0", optional = true, markers = "extra != \"compatible-mypy\""}, {version = "==1.4.*", optional = true, markers = "extra == \"compatible-mypy\""}, ] tomli = {version = "*", markers = "python_version < \"3.11\""} @@ -300,6 +300,20 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "packaging" version = "23.1" @@ -371,6 +385,24 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} docs = ["furo (>=2023.7.26)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68)", "wheel (>=0.41.1)"] +[[package]] +name = "pyright" +version = "1.1.323" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.323-py3-none-any.whl", hash = "sha256:23ce9eca401fda311be273784ebf128850d43a17f9e87dc299ffcdc0ffe91f75"}, + {file = "pyright-1.1.323.tar.gz", hash = "sha256:f3029bfe96a3436a505464d28e3433fafe23ac5f86f52edab9a26cd66685825e"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" version = "7.4.0" @@ -440,6 +472,22 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] +[[package]] +name = "setuptools" +version = "68.1.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "sqlparse" version = "0.4.4" @@ -593,4 +641,4 @@ watchdog = ["watchdog"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d6d7a6579dce82c85a8476de7d61e5bd599a76125c2b26d6999f3588a2d06cfc" +content-hash = "af8baccfac691523d1a3ed6b2098028d88f35c58135ad125ed6fcece8d7fd647" diff --git a/pyproject.toml b/pyproject.toml index 33346b2..7a688f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,11 +27,11 @@ packages = [ {include = "static_precompiler", from = "src"} ] include = [ - "AUTHORS", - "README.md", - "CHANGES.md", - "LICENSE", - "py.typed", + {path = "AUTHORS"}, + {path = "README.md"}, + {path = "CHANGES.md"}, + {path = "LICENSE"}, + {path = "py.typed"}, { path = "tests", format = "sdist" } ] @@ -50,8 +50,8 @@ pytest-cov = "~4.0.0" watchdog = "~3.0.0" coverage = "~7.3.0" tox = "~4.9.0" -mypy = "~1.4.0" -django-stubs = {extras = ["compatible-mypy"], version = "~4.2.3"} +pyright = "^1.1.323" +django-stubs = "~4.2.3" [tool.poetry.extras] libsass = ["libsass"] @@ -73,13 +73,7 @@ target-version = "py38" [tool.ruff.isort] known-first-party = ["static_precompiler"] -[tool.mypy] -python_version = "3.8" -plugins = ["mypy_django_plugin.main"] - -[[tool.mypy.overrides]] -module = "pretend" -ignore_missing_imports = true - -[tool.django-stubs] -django_settings_module = "static_precompiler.mypy_django_settings" +[tool.pyright] +include = ["src"] +exclude = ["__pycache__"] +pythonVersion = "3.8" diff --git a/src/static_precompiler/caching.py b/src/static_precompiler/caching.py index fd0d8bc..1a81ba6 100644 --- a/src/static_precompiler/caching.py +++ b/src/static_precompiler/caching.py @@ -3,16 +3,14 @@ from typing import Optional import django.core.cache -import django.utils.encoding -from django.core.cache import BaseCache from . import settings -def get_cache() -> BaseCache: +def get_cache() -> django.core.cache.BaseCache: if settings.CACHE_NAME: return django.core.cache.caches.get(settings.CACHE_NAME) # type: ignore - return django.core.cache.cache + return django.core.cache.cache # type: ignore def get_cache_key(key: str) -> str: @@ -20,7 +18,7 @@ def get_cache_key(key: str) -> str: def get_hexdigest(plaintext: str, length: Optional[int] = None) -> str: - digest = hashlib.md5(django.utils.encoding.smart_bytes(plaintext)).hexdigest() + digest = hashlib.md5(plaintext.encode()).hexdigest() if length: return digest[:length] return digest diff --git a/src/static_precompiler/compilers/base.py b/src/static_precompiler/compilers/base.py index bd40d6f..a5f4e74 100644 --- a/src/static_precompiler/compilers/base.py +++ b/src/static_precompiler/compilers/base.py @@ -50,7 +50,7 @@ def get_full_source_path(self, source_path: str) -> str: return full_path with contextlib.suppress(django.core.exceptions.SuspiciousOperation): - full_path = finders.find(norm_source_path) + full_path = finders.find(norm_source_path, all=False) if full_path is None: raise ValueError(f"Can't find staticfile named: {source_path}") diff --git a/src/static_precompiler/compilers/libsass.py b/src/static_precompiler/compilers/libsass.py index 3205afc..520fdb2 100644 --- a/src/static_precompiler/compilers/libsass.py +++ b/src/static_precompiler/compilers/libsass.py @@ -45,12 +45,15 @@ def compile_file(self, source_path: str) -> str: try: if self.is_sourcemap_enabled: - compiled, sourcemap = sass.compile( + result = sass.compile( filename=full_source_path, source_map_filename=sourcemap_path, output_filename_hint=full_output_path, include_paths=self.load_paths, ) + if result is None: + raise exceptions.StaticCompilationError(f"Could not compile {source_path}") + compiled, sourcemap = result else: compile_kwargs: Dict[str, Any] = {} if self.load_paths: @@ -78,7 +81,7 @@ def compile_file(self, source_path: str) -> str: def compile_source(self, source: str) -> str: try: - compiled: str = sass.compile(string=source, indented=self.indented, include_paths=self.load_paths) + compiled = sass.compile(string=source, indented=self.indented, include_paths=self.load_paths) except sass.CompileError as e: raise exceptions.StaticCompilationError("Could not compile source") from e diff --git a/src/static_precompiler/utils.py b/src/static_precompiler/utils.py index e684d6c..6a9d70a 100644 --- a/src/static_precompiler/utils.py +++ b/src/static_precompiler/utils.py @@ -2,7 +2,7 @@ import os import posixpath import subprocess -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple from django.utils import encoding @@ -38,12 +38,11 @@ def normalize_whitespace(text: str) -> str: # noinspection PyShadowingBuiltins -def run_command( - args: List[str], input: Optional[Union[bytes, str]] = None, cwd: Optional[str] = None -) -> Tuple[int, str, str]: +def run_command(args: List[str], input: Optional[str] = None, cwd: Optional[str] = None) -> Tuple[int, str, str]: popen_kwargs: Dict[str, Any] = { "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, + "universal_newlines": True, } if cwd is not None: @@ -57,14 +56,11 @@ def run_command( p = subprocess.Popen(args, **popen_kwargs) - if input: - input = encoding.smart_bytes(input) - output, error = p.communicate(input) return_code = p.poll() assert return_code is not None - return return_code, encoding.smart_str(output), encoding.smart_str(error) + return return_code, output, error def compile_static(path: str) -> str: diff --git a/tests/test_base_compiler.py b/tests/test_base_compiler.py index 2b678be..09bb1a0 100644 --- a/tests/test_base_compiler.py +++ b/tests/test_base_compiler.py @@ -195,9 +195,6 @@ def test_compile_lazy(monkeypatch): # noinspection PyUnresolvedReferences assert compiler.compile.calls == [pretend.call("dummy.coffee")] - assert compiler.compile(encoding.force_str("foo")).startswith(encoding.force_str("")) is True - assert compiler.compile(encoding.force_bytes("foo")).startswith(encoding.force_bytes("")) is True - def test_find_dependencies(): compiler = compilers.BaseCompiler() diff --git a/tests/test_scss.py b/tests/test_scss.py index f550b21..32b5fce 100644 --- a/tests/test_scss.py +++ b/tests/test_scss.py @@ -293,7 +293,9 @@ def test_precision(precision, monkeypatch, tmpdir): with open(full_output_path) as compiled: compiled_css = compiled.read() - line_height = re.search(r"line-height: (.+?);", compiled_css).groups()[0] + match = re.search(r"line-height: (.+?);", compiled_css) + assert match + line_height = match.groups()[0] assert len(line_height.split(".")[-1]) == expected_precision