diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml new file mode 100644 index 0000000..63f03fe --- /dev/null +++ b/.github/workflows/lint-and-test.yml @@ -0,0 +1,40 @@ +name: Run Tests and Linter + +on: + pull_request: + branches: + - master + - main + +jobs: + lint-and-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.8.18, 3.9.18, 3.10.13, 3.11.8] + django-version: [3.2, 4.2] + + name: Lint and Test (Python ${{ matrix.python-version }} - Django ${{ matrix.django-version }}) + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -q Django==${{ matrix.django-version }} + pip install -e .[flake8,tests] + + - name: Add current directory to PYTHONPATH + run: echo "PYTHONPATH=$PWD" >> $GITHUB_ENV + + - name: Lint with flake8 + run: flake8 + + - name: Test with pytest + run: pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..76b6d57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-toml + - id: check-added-large-files + - id: debug-statements + - repo: https://github.com/PyCQA/flake8 + rev: "7.0.0" + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-isort + - repo: https://github.com/psf/black + rev: "24.2.0" + hooks: + - id: black + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + - repo: https://github.com/myint/autoflake + rev: v2.2.1 + hooks: + - id: autoflake diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d7e53f1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: python -python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 -env: - - DJANGO_VERSION=1.11 -install: - - pip install -q Django==$DJANGO_VERSION - - pip install -e .[flake8,tests] -script: - - flake8 - - pytest diff --git a/MANIFEST.in b/MANIFEST.in index 12119c8..164330d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ include README.md -recursive-include django_inlinecss *.py \ No newline at end of file +recursive-include django_inlinecss *.py diff --git a/README.md b/README.md index 3a67942..dbf4e07 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/roverdotcom/django-inlinecss.png?branch=master)](https://travis-ci.org/roverdotcom/django-inlinecss) +[![Build Status](https://travis-ci.org/roverdotcom/django-inlinecss.svg?branch=master)](https://travis-ci.org/roverdotcom/django-inlinecss) ## About @@ -14,8 +14,8 @@ template language. - BeautifulSoup - cssutils -- Python 2.7+,3.4+ -- Django 1.11+ +- Python 3.8+ +- Django 3.2+ #### Step 2: Install django_inlinecss diff --git a/django_inlinecss/__version__.py b/django_inlinecss/__version__.py index fbf0a5b..b87975e 100644 --- a/django_inlinecss/__version__.py +++ b/django_inlinecss/__version__.py @@ -1,3 +1,3 @@ -VERSION = (0, 3, 0) +VERSION = (0, 4, 0) -__version__ = '.'.join(map(str, VERSION)) +__version__ = ".".join(map(str, VERSION)) diff --git a/django_inlinecss/conf.py b/django_inlinecss/conf.py index dc0448e..726dd92 100644 --- a/django_inlinecss/conf.py +++ b/django_inlinecss/conf.py @@ -1,32 +1,28 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - - try: import importlib except ImportError: from django.utils import importlib -DEFAULT_ENGINE = 'django_inlinecss.engines.PynlinerEngine' -DEFAULT_CSS_LOADER = 'django_inlinecss.css_loaders.StaticfilesStorageCSSLoader' +DEFAULT_ENGINE = "django_inlinecss.engines.PynlinerEngine" +DEFAULT_CSS_LOADER = "django_inlinecss.css_loaders.StaticfilesStorageCSSLoader" def load_class_by_path(path): - i = path.rfind('.') - module_path, class_name = path[:i], path[i + 1:] + i = path.rfind(".") + module_path, class_name = path[:i], path[i + 1 :] module = importlib.import_module(module_path) return getattr(module, class_name) def get_engine(): from django.conf import settings - engine_path = getattr(settings, 'INLINECSS_ENGINE', DEFAULT_ENGINE) + + engine_path = getattr(settings, "INLINECSS_ENGINE", DEFAULT_ENGINE) return load_class_by_path(engine_path) def get_css_loader(): from django.conf import settings - engine_path = getattr(settings, 'INLINECSS_CSS_LOADER', DEFAULT_CSS_LOADER) + + engine_path = getattr(settings, "INLINECSS_CSS_LOADER", DEFAULT_CSS_LOADER) return load_class_by_path(engine_path) diff --git a/django_inlinecss/css_loaders.py b/django_inlinecss/css_loaders.py index 3021874..9a1aca3 100644 --- a/django_inlinecss/css_loaders.py +++ b/django_inlinecss/css_loaders.py @@ -1,13 +1,8 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - from django.contrib.staticfiles import finders from django.contrib.staticfiles.storage import staticfiles_storage -class BaseCSSLoader(object): +class BaseCSSLoader: def __init__(self): pass @@ -28,10 +23,10 @@ def load(self, path): expanded_path = finders.find(path) if expanded_path is None: - raise IOError('{} does not exist'.format(path)) + raise OSError(f"{path} does not exist") - with open(expanded_path, 'rb') as css_file: - return css_file.read().decode('utf-8') + with open(expanded_path, "rb") as css_file: + return css_file.read().decode("utf-8") class StaticfilesStorageCSSLoader(BaseCSSLoader): @@ -39,4 +34,4 @@ def load(self, path): """ Retrieve CSS contents with staticfiles storage """ - return staticfiles_storage.open(path).read().decode('utf-8') + return staticfiles_storage.open(path).read().decode("utf-8") diff --git a/django_inlinecss/engines.py b/django_inlinecss/engines.py index 55ab018..8de801d 100644 --- a/django_inlinecss/engines.py +++ b/django_inlinecss/engines.py @@ -1,14 +1,7 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from builtins import object - import pynliner -class EngineBase(object): +class EngineBase: def __init__(self, html, css): self.html = html self.css = css diff --git a/django_inlinecss/templatetags/inlinecss.py b/django_inlinecss/templatetags/inlinecss.py index c778ddf..a8979a8 100644 --- a/django_inlinecss/templatetags/inlinecss.py +++ b/django_inlinecss/templatetags/inlinecss.py @@ -1,14 +1,8 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - from django import template -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str from django_inlinecss import conf - register = template.Library() @@ -19,14 +13,14 @@ def __init__(self, nodelist, filter_expressions): def render(self, context): rendered_contents = self.nodelist.render(context) - css = '' + css = "" for expression in self.filter_expressions: path = expression.resolve(context, True) if path is not None: - path = smart_text(path) + path = smart_str(path) css_loader = conf.get_css_loader()() - css = ''.join((css, css_loader.load(path))) + css = "".join((css, css_loader.load(path))) engine = conf.get_engine()(html=rendered_contents, css=css) return engine.render() @@ -34,13 +28,11 @@ def render(self, context): @register.tag def inlinecss(parser, token): - nodelist = parser.parse(('endinlinecss',)) + nodelist = parser.parse(("endinlinecss",)) # prevent second parsing of endinlinecss parser.delete_first_token() args = token.split_contents()[1:] - return InlineCssNode( - nodelist, - [parser.compile_filter(arg) for arg in args]) + return InlineCssNode(nodelist, [parser.compile_filter(arg) for arg in args]) diff --git a/django_inlinecss/tests/static/foobar.css b/django_inlinecss/tests/static/foobar.css index 6381e58..6239e62 100644 --- a/django_inlinecss/tests/static/foobar.css +++ b/django_inlinecss/tests/static/foobar.css @@ -2,5 +2,5 @@ div.foo { margin: 10px 15px 20px 25px; } div.bar { - padding: 10px 15px 20px 25px; + padding: 10px 15px 20px 25px; } diff --git a/django_inlinecss/tests/test_css_loaders.py b/django_inlinecss/tests/test_css_loaders.py index f79a1c2..a8b7ed7 100644 --- a/django_inlinecss/tests/test_css_loaders.py +++ b/django_inlinecss/tests/test_css_loaders.py @@ -1,10 +1,6 @@ """ Test CSS loaders """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals from django.conf import settings from django.test import TestCase @@ -14,34 +10,34 @@ from django_inlinecss.css_loaders import StaticfilesStorageCSSLoader -@override_settings(STATICFILES_DIRS=[settings.STATIC_ROOT], STATIC_ROOT='') +@override_settings(STATICFILES_DIRS=[settings.STATIC_ROOT], STATIC_ROOT="") class StaticfilesFinderCSSLoaderTestCase(TestCase): def setUp(self): self.loader = StaticfilesFinderCSSLoader() - super(StaticfilesFinderCSSLoaderTestCase, self).setUp() + super().setUp() def test_loads_existing_css_file(self): - css = self.loader.load('bar.css') - self.assertIn('div.bar {', css) + css = self.loader.load("bar.css") + self.assertIn("div.bar {", css) def test_load_file_does_not_exist(self): with self.assertRaises(IOError) as e: - self.loader.load('missing.css') + self.loader.load("missing.css") - self.assertEqual(str(e.exception), 'missing.css does not exist') + self.assertEqual(str(e.exception), "missing.css does not exist") class StaticfilesStorageCSSLoaderTestCase(TestCase): def setUp(self): self.loader = StaticfilesStorageCSSLoader() - super(StaticfilesStorageCSSLoaderTestCase, self).setUp() + super().setUp() def test_loads_existing_css_file(self): - css = self.loader.load('bar.css') - self.assertIn('div.bar {', css) + css = self.loader.load("bar.css") + self.assertIn("div.bar {", css) def test_load_file_does_not_exist(self): with self.assertRaises(IOError) as e: - self.loader.load('missing.css') + self.loader.load("missing.css") - self.assertEqual(e.exception.strerror, 'No such file or directory') + self.assertEqual(e.exception.strerror, "No such file or directory") diff --git a/django_inlinecss/tests/test_templatetags.py b/django_inlinecss/tests/test_templatetags.py index 06d6227..29055f5 100644 --- a/django_inlinecss/tests/test_templatetags.py +++ b/django_inlinecss/tests/test_templatetags.py @@ -4,48 +4,42 @@ The actual CSS inlining displayed here is extremely simple: tests of the CSS selector functionality is independent. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals import os +from unittest.mock import patch from django.conf import settings from django.template.loader import get_template from django.test import TestCase from django.test.utils import override_settings from django.utils.safestring import mark_safe -from mock import patch class InlinecssTests(TestCase): def setUp(self): - super(InlinecssTests, self).setUp() + super().setUp() def assert_foo_and_bar_rendered(self, rendered): foo_div_regex = ( r'
' r'\s+This is the "foo" div.\s+' - r'<\/div>') - self.assertRegex( - rendered, - foo_div_regex) + r"<\/div>" + ) + self.assertRegex(rendered, foo_div_regex) bar_div_regex = ( r'
' r'\s+This is the "bar" div.\s+' - r'<\/div>') - self.assertRegex( - rendered, - bar_div_regex) + r"<\/div>" + ) + self.assertRegex(rendered, bar_div_regex) def test_single_staticfiles_css(self): """ Test the basic inlining case of using the staticfiles loader to load a CSS file and inline it as part of a rendering step. """ - template = get_template('single_staticfiles_css.html') + template = get_template("single_staticfiles_css.html") rendered = template.render({}) self.assert_foo_and_bar_rendered(rendered) @@ -55,7 +49,7 @@ def test_multiple_staticfiles_css(self): This tests that passing two css files works. """ - template = get_template('multiple_staticfiles_css.html') + template = get_template("multiple_staticfiles_css.html") rendered = template.render({}) self.assert_foo_and_bar_rendered(rendered) @@ -64,8 +58,8 @@ def test_variable_defined_staticfiles_css(self): Test that the staticfiles paths passed to the templatetag may be defined as variables instead of strings. """ - template = get_template('variable_defined_staticfiles_css.html') - context = {'foo_css': 'foo.css', 'bar_css': 'bar.css'} + template = get_template("variable_defined_staticfiles_css.html") + context = {"foo_css": "foo.css", "bar_css": "bar.css"} rendered = template.render(context) self.assert_foo_and_bar_rendered(rendered) @@ -75,9 +69,8 @@ def test_variable_and_string_defined_staticfiles_css(self): Test that we can mix and match variable-defined CSS files & those defined quoted in the templatetag. """ - template = get_template( - 'variable_and_string_defined_staticfiles_css.html') - context = {'foo_css': 'foo.css'} + template = get_template("variable_and_string_defined_staticfiles_css.html") + context = {"foo_css": "foo.css"} rendered = template.render(context) self.assert_foo_and_bar_rendered(rendered) @@ -86,7 +79,7 @@ def test_inline_css(self): Test that