diff --git a/.travis.yml b/.travis.yml index f65a0f9..a019d9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ matrix: install: pip install -U tox-travis -script: tox +script: tox -- -v after_success: - pip install codecov diff --git a/LICENSE b/LICENSE index 7a86ca3..8fcff22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ The MIT License (MIT) -Copyright (c) 2009-2018 - Simon Conseil +Copyright (c) 2009-2020 - Simon Conseil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/docs/changelog.py b/docs/changelog.py index 6742201..d1f66c4 100644 --- a/docs/changelog.py +++ b/docs/changelog.py @@ -51,7 +51,7 @@ A total of %d pull requests were merged for this release. def get_authors(revision_range): - pat = u'^.*\\t(.*)$' + pat = '^.*\\t(.*)$' lst_release, cur_release = [r.strip() for r in revision_range.split('..')] # authors, in current release and previous to current release. @@ -61,7 +61,7 @@ def get_authors(revision_range): re.M)) # Append '+' to new authors. - authors = [s + u' +' for s in cur - pre] + [s for s in cur & pre] + authors = [s + ' +' for s in cur - pre] + [s for s in cur & pre] authors.sort() return authors @@ -72,17 +72,17 @@ def get_pull_requests(repo, revision_range): # From regular merges merges = this_repo.git.log( '--oneline', '--merges', revision_range) - issues = re.findall(u"Merge pull request \\#(\\d*)", merges) + issues = re.findall("Merge pull request \\#(\\d*)", merges) prnums.extend(int(s) for s in issues) # From Homu merges (Auto merges) - issues = re. findall(u"Auto merge of \\#(\\d*)", merges) + issues = re. findall("Auto merge of \\#(\\d*)", merges) prnums.extend(int(s) for s in issues) # From fast forward squash-merges commits = this_repo.git.log( '--oneline', '--no-merges', '--first-parent', revision_range) - issues = re.findall(u'^.*\\(\\#(\\d+)\\)$', commits, re.M) + issues = re.findall('^.*\\(\\#(\\d+)\\)$', commits, re.M) prnums.extend(int(s) for s in issues) # get PR data from github repo @@ -99,19 +99,19 @@ def main(token, revision_range): # document authors authors = get_authors(revision_range) - heading = u"Contributors" + heading = "Contributors" print() print(heading) print("=" * len(heading)) print(author_msg % len(authors)) for s in authors: - print(u'* ' + s) + print('* ' + s) # document pull requests pull_requests = get_pull_requests(github_repo, revision_range) - heading = u"Pull requests merged" - pull_msg = u"* `#{0} <{1}>`__: {2}" + heading = "Pull requests merged" + pull_msg = "* `#{0} <{1}>`__: {2}" print() print(heading) @@ -119,11 +119,11 @@ def main(token, revision_range): print(pull_request_msg % len(pull_requests)) for pull in pull_requests: - title = re.sub(u"\\s+", u" ", pull.title.strip()) + title = re.sub("\\s+", " ", pull.title.strip()) if len(title) > 60: - remainder = re.sub(u"\\s.*$", u"...", title[60:]) + remainder = re.sub("\\s.*$", "...", title[60:]) if len(remainder) > 20: - remainder = title[:80] + u"..." + remainder = title[:80] + "..." else: title = title[:60] + remainder print(pull_msg.format(pull.number, pull.html_url, title)) diff --git a/docs/conf.py b/docs/conf.py index dfa0ca5..965c465 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,7 +38,7 @@ master_doc = 'index' # General information about the project. project = 'Sigal' -copyright = '2012-2019, Simon Conseil' +copyright = '2012-2020, Simon Conseil' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/contribute.rst b/docs/contribute.rst index cb66e26..db29c2d 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -21,7 +21,7 @@ Install sigal in development mode:: Install additional dependencies for development (Sphinx, pytest), and optional dependencies:: - pip install -r requirements.txt + pip install -e .\[all,tests\] Building the docs ----------------- diff --git a/docs/installation.rst b/docs/installation.rst index e089871..8342786 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -8,7 +8,7 @@ With pip:: Or to install with optional dependencies (listed below):: - $ pip install sigal\[all\] + $ pip install sigal\[all,tests\] To install the development version, see the :doc:`contribute`. @@ -29,8 +29,8 @@ The mandatory dependencies are: - Pillow - Python Markdown -There are also a number of optional dependencies for the :doc:`plugins`, listed -in the ``requirements.txt`` file: +There are also a number of optional dependencies for the :doc:`plugins`, +installable with the ``[all]`` marker: - Brotli, zopfli (compress assets plugin) - Boto (upload to S3 plugin) diff --git a/readthedocs.yml b/readthedocs.yml index febce40..b4d0b25 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,8 +1,12 @@ version: 2 +build: + image: latest + python: version: 3.7 install: - method: pip path: . - - requirements: requirements-doc.txt + extra_requirements: + - docs diff --git a/requirements-doc.txt b/requirements-doc.txt deleted file mode 100644 index 38f3862..0000000 --- a/requirements-doc.txt +++ /dev/null @@ -1,3 +0,0 @@ --r requirements.txt -alabaster -Sphinx diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b037f6b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -blinker -boto -brotli -click -coverage -feedgenerator -Jinja2 -Markdown -Pillow -pilkit -pytest -pytest-cov -zopfli diff --git a/setup.cfg b/setup.cfg index 2e933db..67bab00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,8 @@ install_requires = [options.extras_require] all = boto; brotli; feedgenerator; zopfli +tests = pytest; pytest-cov +docs = Sphinx; alabaster [options.packages.find] exclude = diff --git a/sigal/__init__.py b/sigal/__init__.py index a66ea90..9bc5684 100644 --- a/sigal/__init__.py +++ b/sigal/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -74,7 +74,7 @@ def init(path): with open(path, 'w', encoding='utf-8') as f: f.write(conf.decode('utf8')) - print("Sample config file created: {}".format(path)) + print(f"Sample config file created: {path}") @main.command() @@ -224,11 +224,11 @@ def serve(destination, port, config): .format(destination=destination, config=config)) sys.exit(2) - print('DESTINATION : {}'.format(destination)) + print(f'DESTINATION : {destination}') os.chdir(destination) Handler = server.SimpleHTTPRequestHandler httpd = socketserver.TCPServer(("", port), Handler, False) - print(" * Running on http://127.0.0.1:{}/".format(port)) + print(f" * Running on http://127.0.0.1:{port}/") try: httpd.allow_reuse_address = True @@ -255,7 +255,7 @@ def set_meta(target, keys, overwrite=False): """ if not os.path.exists(target): - sys.stderr.write("The target {} does not exist.\n".format(target)) + sys.stderr.write(f"The target {target} does not exist.\n") sys.exit(1) if len(keys) < 2 or len(keys) % 2 > 0: sys.stderr.write("Need an even number of arguments.\n") @@ -273,5 +273,5 @@ def set_meta(target, keys, overwrite=False): with open(descfile, "w") as fp: for i in range(len(keys) // 2): k, v = keys[i * 2:(i + 1) * 2] - fp.write("{}: {}\n".format(k.capitalize(), v)) + fp.write(f"{k.capitalize()}: {v}\n") print("{} metadata key(s) written to {}".format(len(keys) // 2, descfile)) diff --git a/sigal/gallery.py b/sigal/gallery.py index 5625130..4952431 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Copyright (c) 2013 - Christophe-Marie Duquesne # Copyright (c) 2014 - Jonas Kaufmann # Copyright (c) 2015 - François D. @@ -91,7 +91,7 @@ class Media: signals.media_initialized.send(self) def __repr__(self): - return "<%s>(%r)" % (self.__class__.__name__, str(self)) + return "<{}>({!r})".format(self.__class__.__name__, str(self)) def __str__(self): return join(self.path, self.filename) @@ -195,7 +195,7 @@ class Image(Media): if self.raw_exif and self.ext in ('.jpg', '.jpeg') else None) def _get_metadata(self): - super(Image, self)._get_metadata() + super()._get_metadata() # If a title or description hasn't been obtained by other means, look # for the information in IPTC fields if self.title and self.description: @@ -244,7 +244,7 @@ class Video(Media): type = 'video' def __init__(self, filename, path, settings): - super(Video, self).__init__(filename, path, settings) + super().__init__(filename, path, settings) base, ext = splitext(filename) self.src_filename = filename self.date = self._get_file_date() @@ -322,12 +322,12 @@ class Album: signals.album_initialized.send(self) def __repr__(self): - return "<%s>(path=%r, title=%r)" % (self.__class__.__name__, self.path, - self.title) + return "<{}>(path={!r}, title={!r})".format( + self.__class__.__name__, self.path, self.title) def __str__(self): - return ('{} : '.format(self.path) + - ', '.join('{} {}s'.format(count, _type) + return (f'{self.path} : ' + + ', '.join(f'{count} {_type}s' for _type, count in self.medias_count.items())) def __len__(self): @@ -544,7 +544,7 @@ class Album: return None -class Gallery(object): +class Gallery: def __init__(self, settings, ncpu=None, quiet=False): self.settings = settings diff --git a/sigal/image.py b/sigal/image.py index 81ac2c6..f19132f 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Copyright (c) 2015 - François D. # Copyright (c) 2018 - Edwin Steele @@ -105,7 +105,7 @@ def generate_image(source, outname, settings, options=None): if settings['autorotate_images']: try: img = Transpose().process(img) - except (IOError, IndexError): + except (OSError, IndexError): pass # Resize the image @@ -195,7 +195,7 @@ def get_size(file_path): """Return image size (width and height).""" try: im = _read_image(file_path) - except (IOError, IndexError, TypeError, AttributeError) as e: + except (OSError, IndexError, TypeError, AttributeError) as e: logger = logging.getLogger(__name__) logger.error("Could not read size of %s due to %r", file_path, e) else: diff --git a/sigal/log.py b/sigal/log.py index c5d2972..9dde29a 100644 --- a/sigal/log.py +++ b/sigal/log.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2018 - Simon Conseil +# Copyright (c) 2013-2020 - Simon Conseil # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/sigal/plugins/compress_assets.py b/sigal/plugins/compress_assets.py index 723c11b..86814a2 100644 --- a/sigal/plugins/compress_assets.py +++ b/sigal/plugins/compress_assets.py @@ -83,7 +83,7 @@ class BaseCompressor: file_stats = None compressed_stats = None - compressed_filename = '{}.{}'.format(filename, self.suffix) + compressed_filename = f'{filename}.{self.suffix}' try: file_stats = os.stat(filename) compressed_stats = os.stat(compressed_filename) @@ -146,7 +146,7 @@ def get_compressor(settings): logger.error('Unable to import brotli module') else: - logger.error('No such compressor {}'.format(name)) + logger.error(f'No such compressor {name}') def compress_gallery(gallery): diff --git a/sigal/plugins/feeds.py b/sigal/plugins/feeds.py index 0b09044..0d67343 100644 --- a/sigal/plugins/feeds.py +++ b/sigal/plugins/feeds.py @@ -58,9 +58,9 @@ def generate_feed(gallery, medias, feed_type=None, feed_url='', nb_items=0): for item in medias[:nb_items]: if theme == 'galleria': - link = '%s/%s/#%s' % (base_url, item.path, item.url) + link = f'{base_url}/{item.path}/#{item.url}' else: - link = '%s/%s/' % (base_url, item.path) + link = f'{base_url}/{item.path}/' feed.add_item( title=Markup.escape(item.title or item.url), @@ -68,7 +68,7 @@ def generate_feed(gallery, medias, feed_type=None, feed_url='', nb_items=0): # unique_id='tag:%s,%s:%s' % (urlparse(link).netloc, # item.date.date(), # urlparse(link).path.lstrip('/')), - description='' % (base_url, item.path, + description=''.format(base_url, item.path, item.thumbnail), # categories=item.tags if hasattr(item, 'tags') else None, author_name=getattr(item, 'author', ''), diff --git a/sigal/plugins/media_page.py b/sigal/plugins/media_page.py index 2cb52c3..4f5e508 100644 --- a/sigal/plugins/media_page.py +++ b/sigal/plugins/media_page.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Copyright (c) 2014 - Jamie Starke # Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/sigal/plugins/nomedia.py b/sigal/plugins/nomedia.py index 8881e77..ec9ed02 100644 --- a/sigal/plugins/nomedia.py +++ b/sigal/plugins/nomedia.py @@ -104,7 +104,7 @@ def filter_nomedia(album, settings=None): album.medias = [] else: - with open(nomediapath, "r") as nomediaFile: + with open(nomediapath) as nomediaFile: logger.info("Found a .nomedia file in %s, ignoring its " "entries", album.name) ignored = nomediaFile.read().split("\n") diff --git a/sigal/settings.py b/sigal/settings.py index 8110d30..630bb08 100644 --- a/sigal/settings.py +++ b/sigal/settings.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Copyright (c) 2013 - Christophe-Marie Duquesne # Copyright (c) 2017 - Mate Lakat @@ -86,7 +86,7 @@ _DEFAULT_CONFIG = { } -class Status(object): +class Status: SUCCESS = 0 FAILURE = 1 diff --git a/sigal/utils.py b/sigal/utils.py index c828856..c4e10ed 100644 --- a/sigal/utils.py +++ b/sigal/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2011-2018 - Simon Conseil +# Copyright (c) 2011-2020 - Simon Conseil # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -32,7 +32,7 @@ VIDEO_MIMES = {'.mp4': 'video/mp4', MD = None -class Devnull(object): +class Devnull: """'Black hole' for output that should not be printed""" def write(self, *_): @@ -82,7 +82,7 @@ def read_markdown(filename): # Use utf-8-sig codec to remove BOM if it is present. This is only possible # this way prior to feeding the text to the markdown parser (which would # also default to pure utf-8) - with open(filename, 'r', encoding='utf-8-sig') as f: + with open(filename, encoding='utf-8-sig') as f: text = f.read() if MD is None: @@ -122,7 +122,7 @@ def get_mime(ext): return VIDEO_MIMES[ext] -class cached_property(object): +class cached_property: """ A property that is only computed once per instance and then replaces itself with an ordinary attribute. Deleting the attribute resets the property. diff --git a/sigal/video.py b/sigal/video.py index c53980f..da67fcc 100644 --- a/sigal/video.py +++ b/sigal/video.py @@ -1,5 +1,5 @@ # Copyright (c) 2013 - Christophe-Marie Duquesne -# Copyright (c) 2013-2018 - Simon Conseil +# Copyright (c) 2013-2020 - Simon Conseil # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to diff --git a/sigal/writer.py b/sigal/writer.py index 35e1021..38be978 100644 --- a/sigal/writer.py +++ b/sigal/writer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2018 - Simon Conseil +# Copyright (c) 2009-2020 - Simon Conseil # Copyright (c) 2013 - Christophe-Marie Duquesne # Copyright (c) 2018 - Edwin Steele @@ -37,7 +37,7 @@ THEMES_PATH = os.path.normpath(os.path.join( os.path.abspath(os.path.dirname(__file__)), 'themes')) -class AbstractWriter(object): +class AbstractWriter: template_file = None def __init__(self, settings, index_title=''): diff --git a/tests/conftest.py b/tests/conftest.py index b740ee5..a6811a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,4 +39,4 @@ def disconnect_signals(): def pytest_report_header(config): - return "project deps: Pillow-{}".format(PIL.__version__) + return f"project deps: Pillow-{PIL.__version__}" diff --git a/tests/test_gallery.py b/tests/test_gallery.py index 849fa10..1a48ca1 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -83,7 +83,7 @@ def test_media(settings): assert m.title == "Foo Bar" assert m.description == "

This is a funny description of this image

" - assert repr(m) == "('{}')".format(file_path) + assert repr(m) == f"('{file_path}')" assert str(m) == file_path @@ -244,7 +244,7 @@ def test_gallery(settings, tmpdir): out_html = os.path.join(settings['destination'], 'index.html') assert os.path.isfile(out_html) - with open(out_html, 'r') as f: + with open(out_html) as f: html = f.read() assert 'Sigal test gallery' in html diff --git a/tox.ini b/tox.ini index 5452cb3..b9eef2d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,9 @@ python = 3.8: py38 [testenv] -deps = - -r {toxinidir}/requirements.txt +extras = + all + tests commands = pytest --cov sigal --cov-report term-missing tests/ [testenv:check] @@ -28,8 +29,7 @@ commands = [testenv:doc] whitelist_externals = make -deps = - -r {toxinidir}/requirements-doc.txt +extras = docs usedevelop = true commands = make -C docs html