diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 56244ca..fcf6213 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.7', '3.8', '3.9', '3.10'] + python: ['3.9', '3.10', '3.11'] fail-fast: false steps: diff --git a/Makefile b/Makefile index 2238ba3..ad1c188 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/fabfile.py b/fabfile.py index 3ddb927..1744a99 100644 --- a/fabfile.py +++ b/fabfile.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/geany.org.service b/geany.org.service index 9712e82..4fe841d 100644 --- a/geany.org.service +++ b/geany.org.service @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/geany/apps.py b/geany/apps.py index dc310e6..c5f2a48 100644 --- a/geany/apps.py +++ b/geany/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,4 +16,4 @@ class GeanyAppConfig(AppConfig): name = 'geany' - verbose_name = "Geany" + verbose_name = 'Geany' diff --git a/geany/decorators.py b/geany/decorators.py index 97da831..2643756 100644 --- a/geany/decorators.py +++ b/geany/decorators.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # based on http://djangosnippets.org/snippets/564/ # # LICENCE: This program is free software: you can redistribute it and/or modify @@ -14,8 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from binascii import hexlify import inspect +from binascii import hexlify from django.core.cache import cache as _djcache diff --git a/geany/management/commands/dump_database.py b/geany/management/commands/dump_database.py index 629c707..b97aba2 100644 --- a/geany/management/commands/dump_database.py +++ b/geany/management/commands/dump_database.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -23,7 +22,7 @@ class Command(BaseCommand): - help = "Dump the database (excluding users, sessions and logs)" + help = 'Dump the database (excluding users, sessions and logs)' # noqa: A003 # ---------------------------------------------------------------------- def handle(self, *args, **options): @@ -33,7 +32,7 @@ def handle(self, *args, **options): # ---------------------------------------------------------------------- def _dump_main_database(self): - print('Dump main database') + self.stdout.write('Dump main database') call_command( 'dumpdata', '--exclude', 'auth.user', @@ -97,7 +96,7 @@ def _dump_nightly_builds_database(self): database_nightlybuilds_filename = 'tmp_database_nightlybuilds.json' database_nightlybuild_targets_filename = 'tmp_database_nightlybuild_targets.json' - print('Dump nightlybuilds.NightlyBuild') + self.stdout.write('Dump nightlybuilds.NightlyBuild') call_command( 'dumpdata', '--database', 'nightlybuilds', @@ -106,7 +105,7 @@ def _dump_nightly_builds_database(self): '--output', database_nightlybuilds_filename, 'nightlybuilds.NightlyBuild') - print('Dump nightlybuilds.NightlyBuildTarget') + self.stdout.write('Dump nightlybuilds.NightlyBuildTarget') call_command( 'dumpdata', '--database', 'nightlybuilds', diff --git a/geany/settings.py b/geany/settings.py index 586a450..8c9f968 100644 --- a/geany/settings.py +++ b/geany/settings.py @@ -19,6 +19,8 @@ from markdown.extensions.toc import TocExtension +# ruff: noqa: ERA001 + ###################### # MEZZANINE SETTINGS # ###################### @@ -109,7 +111,7 @@ # ('/admin', '/example') would force all URLs beginning with # /admin or /example to run over SSL. Defaults to: # -SSL_FORCE_URL_PREFIXES = ("/admin", "/account") +SSL_FORCE_URL_PREFIXES = ('/admin', '/account') # Django security settings SECURE_CONTENT_TYPE_NOSNIFF = True @@ -138,7 +140,7 @@ 'www.geany.org') INTERNAL_IPS = ('127.0.0.1', '10.0.44.3') -SECRET_KEY = 'change-me' +SECRET_KEY = 'change-me' # noqa: S105 # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name @@ -154,7 +156,7 @@ # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = "en" +LANGUAGE_CODE = 'en' # Supported languages LANGUAGES = ( @@ -175,7 +177,7 @@ # to load the internationalization machinery. USE_I18N = False -AUTHENTICATION_BACKENDS = ("mezzanine.core.auth_backends.MezzanineBackend",) +AUTHENTICATION_BACKENDS = ('mezzanine.core.auth_backends.MezzanineBackend',) # The numeric mode to set newly-uploaded files to. The value should be # a mode you'd pass directly to os.chmod. @@ -193,24 +195,24 @@ ############# DATABASES = { - "default": { - "ENGINE": "django.db.backends.mysql", - "NAME": "dbname", - "USER": "", - "PASSWORD": "", - "HOST": "127.0.0.1", - "OPTIONS": { - "init_command": "SET sql_mode='STRICT_ALL_TABLES'; SET storage_engine=INNODB" + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'dbname', + 'USER': '', + 'PASSWORD': '', + 'HOST': '127.0.0.1', + 'OPTIONS': { + 'init_command': "SET sql_mode='STRICT_ALL_TABLES'; SET storage_engine=INNODB" }, }, 'nightlybuilds': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname', - "USER": "", - "PASSWORD": "", - "HOST": "127.0.0.1", - "OPTIONS": { - "init_command": "SET sql_mode='STRICT_ALL_TABLES';" + 'USER': '', + 'PASSWORD': '', + 'HOST': '127.0.0.1', + 'OPTIONS': { + 'init_command': "SET sql_mode='STRICT_ALL_TABLES';" }, } } @@ -238,22 +240,22 @@ # URL prefix for static files. # Example: "http://media.lawrence.com/static/" -STATIC_URL = "/static/" +STATIC_URL = '/static/' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" -STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip("/")) +STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip('/')) # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" -MEDIA_URL = "/media/" +MEDIA_URL = '/media/' # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip("/").split("/")) +MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip('/').split('/')) # Package/module name to import the root urlpatterns from for the project. ROOT_URLCONF = f'{PROJECT_APP}.urls' @@ -263,7 +265,7 @@ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, 'DIRS': [ - os.path.join(PROJECT_APP_PATH, "templates"), + os.path.join(PROJECT_APP_PATH, 'templates'), ], 'OPTIONS': { 'context_processors': [ @@ -286,53 +288,53 @@ # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'compressor.finders.CompressorFinder', ) # Store these package names here as they may change in the future since # at the moment we are using custom forks of them. -PACKAGE_NAME_FILEBROWSER = "filebrowser_safe" -PACKAGE_NAME_GRAPPELLI = "grappelli_safe" +PACKAGE_NAME_FILEBROWSER = 'filebrowser_safe' +PACKAGE_NAME_GRAPPELLI = 'grappelli_safe' ################ # APPLICATIONS # ################ INSTALLED_APPS = ( - "clearcache", - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.messages", - "django.contrib.redirects", - "django.contrib.sessions", - "django.contrib.sites", - "django.contrib.sitemaps", - "django.contrib.staticfiles", - "mezzanine.boot", - "mezzanine.conf", - "mezzanine.core", - "mezzanine.generic", - "mezzanine.pages", - "mezzanine.forms", - "mezzanine.galleries", + 'clearcache', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.redirects', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.sitemaps', + 'django.contrib.staticfiles', + 'mezzanine.boot', + 'mezzanine.conf', + 'mezzanine.core', + 'mezzanine.generic', + 'mezzanine.pages', + 'mezzanine.forms', + 'mezzanine.galleries', # we - "geany.apps.GeanyAppConfig", - "news.apps.NewsAppConfig", - "latest_version.apps.LatestVersionAppConfig", - "static_docs.apps.StaticDocsAppConfig", - "pastebin.apps.PastebinAppConfig", - "nightlybuilds.apps.NightlyBuildsAppConfig", - "urlshortener.apps.UrlShortenerAppConfig", + 'geany.apps.GeanyAppConfig', + 'news.apps.NewsAppConfig', + 'latest_version.apps.LatestVersionAppConfig', + 'static_docs.apps.StaticDocsAppConfig', + 'pastebin.apps.PastebinAppConfig', + 'nightlybuilds.apps.NightlyBuildsAppConfig', + 'urlshortener.apps.UrlShortenerAppConfig', # 3rd party - "compressor", - "django_extensions", - "mezzanine_pagedown", - "mezzanine_sync_pages.apps.MezzanineSyncPagesAppConfig", + 'compressor', + 'django_extensions', + 'mezzanine_pagedown', + 'mezzanine_sync_pages.apps.MezzanineSyncPagesAppConfig', # "shortener", # disabled until it is fixed for Django 4.0 or we remove it completely PACKAGE_NAME_FILEBROWSER, PACKAGE_NAME_GRAPPELLI, @@ -342,27 +344,27 @@ # these middleware classes will be applied in the order given, and in the # response phase the middleware will be applied in reverse order. MIDDLEWARE = ( - "log_request_id.middleware.RequestIDMiddleware", - "mezzanine.core.middleware.UpdateCacheMiddleware", + 'log_request_id.middleware.RequestIDMiddleware', + 'mezzanine.core.middleware.UpdateCacheMiddleware', - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', # Uncomment if using internationalisation or localisation # "django.middleware.locale.LocaleMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - - "mezzanine.core.request.CurrentRequestMiddleware", - "mezzanine.core.middleware.RedirectFallbackMiddleware", - "mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware", - "mezzanine.core.middleware.SitePermissionMiddleware", + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + 'mezzanine.core.request.CurrentRequestMiddleware', + 'mezzanine.core.middleware.RedirectFallbackMiddleware', + 'mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware', + 'mezzanine.core.middleware.SitePermissionMiddleware', # Uncomment the following if using any of the SSL settings: # "mezzanine.core.middleware.SSLRedirectMiddleware", - "mezzanine.pages.middleware.PageMiddleware", - "mezzanine.core.middleware.FetchFromCacheMiddleware", + 'mezzanine.pages.middleware.PageMiddleware', + 'mezzanine.core.middleware.FetchFromCacheMiddleware', ) # pagedown / markdown @@ -396,33 +398,33 @@ # dashboard DASHBOARD_TAGS = ( - ("mezzanine_tags.app_list",), - ("mezzanine_tags.recent_actions",), - ("comment_tags.recent_comments",), + ('mezzanine_tags.app_list',), + ('mezzanine_tags.recent_actions',), + ('comment_tags.recent_comments',), ) ADMIN_MENU_ORDER = ( - (_("Content"), ( - "pages.Page", - "news.NewsPost", - "generic.ThreadedComment", - "mezzanine_blocks.Block", - "mezzanine_blocks.RichBlock", - "mezzanine_sync_pages.MezzanineSyncPages", - (_("Media Library"), "fb_browse"),)), - (_("Site"), ( - "sites.Site", - "redirects.Redirect", - "conf.Setting", - (_("Clear Cache"), "clearcache_admin"), + (_('Content'), ( + 'pages.Page', + 'news.NewsPost', + 'generic.ThreadedComment', + 'mezzanine_blocks.Block', + 'mezzanine_blocks.RichBlock', + 'mezzanine_sync_pages.MezzanineSyncPages', + (_('Media Library'), 'fb_browse'),)), + (_('Site'), ( + 'sites.Site', + 'redirects.Redirect', + 'conf.Setting', + (_('Clear Cache'), 'clearcache_admin'), )), - (_("Geany"), ( - "latest_version.LatestVersion", + (_('Geany'), ( + 'latest_version.LatestVersion', )), - (_("Users"), ("auth.User", "auth.Group",))) # pylint: disable=hard-coded-auth-user + (_('Users'), ('auth.User', 'auth.Group',))) # pylint: disable=hard-coded-auth-user # caching & sessions -SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" +SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_COOKIE_SECURE = True @@ -457,7 +459,7 @@ def skip_404_not_found(record): return True -logging.captureWarnings(True) # log warnings using the logging subsystem +logging.captureWarnings(capture=True) # log warnings using the logging subsystem LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -602,14 +604,15 @@ def skip_404_not_found(record): local_settings_file_name = os.environ.get('LOCAL_SETTINGS_PY', 'local_settings.py') filename = os.path.join(PROJECT_APP_PATH, local_settings_file_name) # pylint: disable=invalid-name if os.path.exists(filename): - from importlib.util import module_from_spec, spec_from_file_location import sys + from importlib.util import module_from_spec, spec_from_file_location + module_name = f'{PROJECT_APP}.local_settings' # pylint: disable=invalid-name spec = spec_from_file_location(module_name, filename) # pylint: disable=invalid-name module = module_from_spec(spec) # pylint: disable=invalid-name sys.modules[module_name] = module with open(filename, 'rb') as local_config: - exec(local_config.read()) # pylint: disable=exec-used + exec(local_config.read()) # noqa: S102 pylint: disable=exec-used #################### diff --git a/geany/sitemaps.py b/geany/sitemaps.py index 8141ac8..d18c7e2 100644 --- a/geany/sitemaps.py +++ b/geany/sitemaps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -181,9 +180,9 @@ def _get_static_items(self): def _get_items(self, item_generator_name): items = [] for generator_class, url_patterns, site_domain, exclude_views in self._sitemap_generators: - site_domain = self._get_site_domain_or_default(site_domain) + site_domain_final = self._get_site_domain_or_default(site_domain) generator = generator_class( - site_domain, + site_domain_final, url_patterns, exclude_views=exclude_views) item_generator = getattr(generator, item_generator_name) diff --git a/geany/templatetags/geany_tags.py b/geany/templatetags/geany_tags.py index 8570633..71fc10c 100644 --- a/geany/templatetags/geany_tags.py +++ b/geany/templatetags/geany_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -53,8 +52,8 @@ def do_evaluate(parser, token): # pylint: disable=unused-argument _, variable, _, target_var_name = token.split_contents() except ValueError as exc: token_name = token.contents.split()[1] - raise template.TemplateSyntaxError( - f'{token_name!r} tag requires a single argument') from exc + msg = f'{token_name!r} tag requires a single argument' + raise template.TemplateSyntaxError(msg) from exc return EvaluateNode(variable, target_var_name) diff --git a/geany/urls.py b/geany/urls.py index 863677c..4dc9509 100644 --- a/geany/urls.py +++ b/geany/urls.py @@ -11,6 +11,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import mezzanine_pagedown.urls from django.conf.urls.i18n import i18n_patterns from django.contrib import admin from django.contrib.sitemaps.views import sitemap @@ -19,7 +20,6 @@ from django.views.i18n import set_language from django.views.static import serve as static_serve from mezzanine.conf import settings -import mezzanine_pagedown.urls from geany import urls_legacy from geany.sitemaps import GeanyMainSitemap @@ -28,7 +28,7 @@ # pylint: disable=invalid-name -sitemaps = {"sitemaps": {"all": GeanyMainSitemap}} +sitemaps = {'sitemaps': {'all': GeanyMainSitemap}} admin.autodiscover() @@ -37,7 +37,7 @@ # Change the admin prefix here to use an alternate URL for the # admin interface, which would be marginally more secure. path('admin/clearcache/', include('clearcache.urls')), - path("admin/", include(admin.site.urls)), + path('admin/', include(admin.site.urls)), ) if settings.USE_MODELTRANSLATION: @@ -63,7 +63,8 @@ path('p/', include('pastebin.urls')), # URL Shortener - # path('s/', include('urlshortener.urls')), # disabled until it is fixed for Django 4.0 + # disabled until it is fixed for Django 4.0 + # path('s/', include('urlshortener.urls')), # noqa: ERA001 # /news/ News path('news/', include('news.urls')), diff --git a/geany/urls_legacy.py b/geany/urls_legacy.py index 1e9cd23..fd133af 100644 --- a/geany/urls_legacy.py +++ b/geany/urls_legacy.py @@ -185,7 +185,7 @@ def _add_url_mappings(mapping, urlpatterns_): for old_url, new_url in mapping.items(): if old_url.startswith('/'): - old_url = old_url[1:] + old_url = old_url[1:] # noqa: PLW2901 url_pattern = re_path( fr'^{old_url}$', diff --git a/latest_version/admin.py b/latest_version/admin.py index b501ea2..c7f8651 100644 --- a/latest_version/admin.py +++ b/latest_version/admin.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/latest_version/apps.py b/latest_version/apps.py index a1d2aa1..d19937d 100644 --- a/latest_version/apps.py +++ b/latest_version/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,12 +16,12 @@ class LatestVersionAppConfig(AppConfig): name = 'latest_version' - verbose_name = "LatestVersion" + verbose_name = 'LatestVersion' # ---------------------------------------------------------------------- def ready(self): # register our urlpatterns to the global sitemap generator # pylint: disable=import-outside-toplevel - from geany.sitemaps import sitemap_registry, StaticSitemap + from geany.sitemaps import StaticSitemap, sitemap_registry from latest_version.urls import urlpatterns sitemap_registry.add(StaticSitemap, urlpatterns, exclude_views=['latest_version_legacy']) diff --git a/latest_version/context_processors.py b/latest_version/context_processors.py index ba7b85d..b194173 100644 --- a/latest_version/context_processors.py +++ b/latest_version/context_processors.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -15,9 +14,9 @@ from mezzanine.conf import settings from geany.decorators import ( - cache_function, CACHE_KEY_LATEST_VERSION_LATEST_VERSION, CACHE_TIMEOUT_1HOUR, + cache_function, ) from latest_version.models import LatestVersion from latest_version.releases import ReleaseVersionsProvider diff --git a/latest_version/models.py b/latest_version/models.py index 17dcc15..287d1c5 100644 --- a/latest_version/models.py +++ b/latest_version/models.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -36,8 +35,8 @@ class Meta: verbose_name_plural = 'Latest Version' # ---------------------------------------------------------------------- - def delete(self, using=None, keep_parents=False): - """Never delete anything""" + def __str__(self): + return f'{self.name} {self.version}' # ---------------------------------------------------------------------- def save(self, *args, **kwargs): # pylint: disable=signature-differs @@ -47,5 +46,5 @@ def save(self, *args, **kwargs): # pylint: disable=signature-differs [CACHE_KEY_LATEST_VERSION_LATEST_VERSION, CACHE_KEY_STATIC_DOCS_RELEASE_NOTES]) # ---------------------------------------------------------------------- - def __str__(self): - return f'{self.name} {self.version}' + def delete(self, using=None, keep_parents=False): + """Never delete anything""" diff --git a/latest_version/releases.py b/latest_version/releases.py index c2b2122..44412db 100644 --- a/latest_version/releases.py +++ b/latest_version/releases.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -12,9 +11,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from pathlib import Path import logging import re +from pathlib import Path from django.conf import settings from packaging.version import parse as parse_version diff --git a/news/admin.py b/news/admin.py index 6fc72d0..ab70bac 100644 --- a/news/admin.py +++ b/news/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -20,12 +19,12 @@ class NewsPostAdmin(admin.ModelAdmin): list_display = ('title', 'user', 'status', 'publish_date') - list_editable = ("status",) + list_editable = ('status',) list_filter = ('publish_date', 'status') date_hierarchy = 'publish_date' exclude = ('slug', 'user', 'entry_date') actions = ['_toggle_many_published'] - radio_fields = {"status": admin.HORIZONTAL} + radio_fields = {'status': admin.HORIZONTAL} # ---------------------------------------------------------------------- def save_model(self, request, obj, form, change): diff --git a/news/apps.py b/news/apps.py index 211487a..e0301b8 100644 --- a/news/apps.py +++ b/news/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,7 +16,7 @@ class NewsAppConfig(AppConfig): name = 'news' - verbose_name = "News" + verbose_name = 'News' # ---------------------------------------------------------------------- def ready(self): diff --git a/news/feeds.py b/news/feeds.py index 2e3635a..92d7fe0 100644 --- a/news/feeds.py +++ b/news/feeds.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -23,12 +22,12 @@ class LatestNewsPostsFeed(Feed): - title = "Geany project news" - description = "News feed for the Geany project" + title = 'Geany project news' + description = 'News feed for the Geany project' # ---------------------------------------------------------------------- def link(self): - return reverse("news_list") + return reverse('news_list') # ---------------------------------------------------------------------- def items(self): @@ -41,7 +40,7 @@ def item_title(self, item): # ---------------------------------------------------------------------- def item_description(self, item): description = richtext_filters(item.content) - absolute_urls_name = "mezzanine.utils.html.absolute_urls" + absolute_urls_name = 'mezzanine.utils.html.absolute_urls' if absolute_urls_name not in settings.RICHTEXT_FILTERS: description = absolute_urls(description) return description diff --git a/news/models.py b/news/models.py index ebd715c..a2751c4 100644 --- a/news/models.py +++ b/news/models.py @@ -83,6 +83,10 @@ class Meta: verbose_name = _('News') verbose_name_plural = _('News') + # ---------------------------------------------------------------------- + def __str__(self): + return self.title + # ---------------------------------------------------------------------- def save(self, *args, **kwargs): # pylint: disable=signature-differs if not self.slug: @@ -92,7 +96,3 @@ def save(self, *args, **kwargs): # pylint: disable=signature-differs # ---------------------------------------------------------------------- def get_absolute_url(self): return reverse('news_detail', kwargs={'newspost_slug': self.slug}) - - # ---------------------------------------------------------------------- - def __str__(self): - return self.title diff --git a/news/sitemaps.py b/news/sitemaps.py index b08b556..e66ebfd 100644 --- a/news/sitemaps.py +++ b/news/sitemaps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/news/templatetags/news_tags.py b/news/templatetags/news_tags.py index eedee03..6f40cb4 100644 --- a/news/templatetags/news_tags.py +++ b/news/templatetags/news_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -21,8 +20,8 @@ # ---------------------------------------------------------------------- -@register.inclusion_tag("news/list_embedded.html", takes_context=True) +@register.inclusion_tag('news/list_embedded.html', takes_context=True) def get_recent_news(context): user = context.request.user - context["recent_news_posts"] = NewsPost.objects.recently_published(count=4, for_user=user) + context['recent_news_posts'] = NewsPost.objects.recently_published(count=4, for_user=user) return context diff --git a/news/views.py b/news/views.py index 402ec5d..26a87e7 100644 --- a/news/views.py +++ b/news/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/nightlybuilds/apps.py b/nightlybuilds/apps.py index c0c8198..5f8eb63 100644 --- a/nightlybuilds/apps.py +++ b/nightlybuilds/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,4 +16,4 @@ class NightlyBuildsAppConfig(AppConfig): name = 'nightlybuilds' - verbose_name = "Nightly Builds" + verbose_name = 'Nightly Builds' diff --git a/nightlybuilds/database_routers.py b/nightlybuilds/database_routers.py index 23ee0c5..031b6f5 100644 --- a/nightlybuilds/database_routers.py +++ b/nightlybuilds/database_routers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -12,6 +11,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# ruff: noqa: SLF001 # pylint: disable=protected-access,unused-argument diff --git a/nightlybuilds/models.py b/nightlybuilds/models.py index 1055271..1acbedf 100644 --- a/nightlybuilds/models.py +++ b/nightlybuilds/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -58,6 +57,10 @@ class Meta: ordering = ('-build_date',) db_table = 'nightly_build' + # ---------------------------------------------------------------------- + def __str__(self): + return f'{self.build_date} {self.nightly_build_target}' + # ---------------------------------------------------------------------- def get_status(self): return not self.status @@ -68,7 +71,3 @@ def get_status_text(self): return 'Built successfully' return 'Build failed, see the logs for details' - - # ---------------------------------------------------------------------- - def __str__(self): - return f'{self.build_date} {self.nightly_build_target}' diff --git a/nightlybuilds/templatetags/nightlybuilds_tags.py b/nightlybuilds/templatetags/nightlybuilds_tags.py index 8f34508..f1bd0db 100644 --- a/nightlybuilds/templatetags/nightlybuilds_tags.py +++ b/nightlybuilds/templatetags/nightlybuilds_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -26,10 +25,7 @@ # ---------------------------------------------------------------------- @register.simple_tag def get_build_log(nightly_build, log_type): - if log_type == 'Stdout': - log = nightly_build.log_stdout - else: - log = nightly_build.log_stderr + log = nightly_build.log_stdout if log_type == 'Stdout' else nightly_build.log_stderr if log: logfile_path = os.path.join(BASE_DIR, nightly_build.nightly_build_target.folder, log) diff --git a/nightlybuilds/views.py b/nightlybuilds/views.py index e711f9c..b50dc6e 100644 --- a/nightlybuilds/views.py +++ b/nightlybuilds/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -19,7 +18,7 @@ class NightlyBuildsView(ListView): - template_name = "nightlybuilds.html" + template_name = 'nightlybuilds.html' context_object_name = 'nightlybuilds' diff --git a/pastebin/admin.py b/pastebin/admin.py index 12f37ea..9055237 100644 --- a/pastebin/admin.py +++ b/pastebin/admin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/pastebin/api/create.py b/pastebin/api/create.py index 35127b9..1bf9044 100644 --- a/pastebin/api/create.py +++ b/pastebin/api/create.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -95,8 +94,8 @@ def _validate_passed_fields(self): provided_fields = set(self._data.keys()) additional_fields = provided_fields.difference(self.valid_fields) if additional_fields: - raise SnippetValidationError( - f'Invalid fields provided ({",".join(additional_fields)})') + msg = f'Invalid fields provided ({",".join(additional_fields)})' + raise SnippetValidationError(msg) # ---------------------------------------------------------------------- def _validate_against_snippet_form(self): diff --git a/pastebin/apps.py b/pastebin/apps.py index e9a8c63..41e57d1 100644 --- a/pastebin/apps.py +++ b/pastebin/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,12 +16,12 @@ class PastebinAppConfig(AppConfig): name = 'pastebin' - verbose_name = "Pastebin" + verbose_name = 'Pastebin' # ---------------------------------------------------------------------- def ready(self): # Sitemap framework # pylint: disable=import-outside-toplevel - from geany.sitemaps import sitemap_registry, StaticSitemap + from geany.sitemaps import StaticSitemap, sitemap_registry from pastebin.urls import urlpatterns sitemap_registry.add(StaticSitemap, urlpatterns, exclude_views=['snippet_api']) diff --git a/pastebin/forms.py b/pastebin/forms.py index 3dbbd8b..c2adb83 100644 --- a/pastebin/forms.py +++ b/pastebin/forms.py @@ -60,7 +60,8 @@ def _clean_field(self, field_name): if value: regex = Spamword.objects.get_regex() if regex.findall(value.lower()): - raise forms.ValidationError('This snippet was identified as SPAM.') + msg = 'This snippet was identified as SPAM.' + raise forms.ValidationError(msg) return value diff --git a/pastebin/highlight.py b/pastebin/highlight.py index 43c0c81..87dd735 100644 --- a/pastebin/highlight.py +++ b/pastebin/highlight.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -74,7 +73,8 @@ def pygmentize(code_string, lexer_name=LEXER_DEFAULT): if lexer_name: lexer = get_lexer_by_name(lexer_name) else: - raise Exception('Unknown lexer') + msg = 'Unknown lexer' + raise ValueError(msg) # noqa: TRY301 except Exception: lexer = PythonLexer() diff --git a/pastebin/management/commands/cleanup_snippets.py b/pastebin/management/commands/cleanup_snippets.py index 66ebcf9..dae6d4f 100644 --- a/pastebin/management/commands/cleanup_snippets.py +++ b/pastebin/management/commands/cleanup_snippets.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -21,8 +20,7 @@ class Command(BaseCommand): - - help = 'Purges snippets that are expired' + help = 'Purges snippets that are expired' # noqa: A003 # ---------------------------------------------------------------------- def add_arguments(self, parser): @@ -30,7 +28,7 @@ def add_arguments(self, parser): '--dry-run', '-d', action='store_true', dest='dry_run', - help='Don\'t do anything.') + help="Don't do anything.") # ---------------------------------------------------------------------- def handle(self, *args, **options): diff --git a/pastebin/management/commands/generate_snippets_css.py b/pastebin/management/commands/generate_snippets_css.py index efe9db1..bf03449 100644 --- a/pastebin/management/commands/generate_snippets_css.py +++ b/pastebin/management/commands/generate_snippets_css.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,7 +16,7 @@ class Command(BaseCommand): - help = "Regenerate CSS for snippet sxntax highlighting py Pygments" + help = 'Regenerate CSS for snippet sxntax highlighting py Pygments' # noqa: A003 requires_system_checks = False # ---------------------------------------------------------------------- diff --git a/pastebin/models.py b/pastebin/models.py index 5f1bd87..3b7195a 100644 --- a/pastebin/models.py +++ b/pastebin/models.py @@ -11,10 +11,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from datetime import timedelta import random import re import time +from datetime import timedelta from django.core.cache import cache from django.db import models @@ -32,7 +32,8 @@ # ---------------------------------------------------------------------- def generate_secret_id(length=5): - return ''.join([random.choice(CHARS) for i in range(length)]) # pylint: disable=unused-variable + randon_chars = random.choice(CHARS) # noqa: S311 + return ''.join([randon_chars for i in range(length)]) # pylint: disable=unused-variable class Snippet(models.Model): @@ -54,6 +55,26 @@ class Snippet(models.Model): class Meta: ordering = ('-published',) + # ---------------------------------------------------------------------- + def __str__(self): + return f'{self.secret_id}' + + # ---------------------------------------------------------------------- + def save(self, *args, **kwargs): # pylint: disable=signature-differs + if not self.pk and not self.secret_id: + self.secret_id = generate_secret_id() + if not self.published: + self.published = timezone.now() + + self.content_highlighted = self.content + super().save(*args, **kwargs) + # invalidate cache + cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL]) + + # ---------------------------------------------------------------------- + def get_absolute_url(self): + return reverse('snippet_details', kwargs={'snippet_id': self.secret_id}) + # ---------------------------------------------------------------------- def age(self): age = time.mktime(self.published.timetuple()) @@ -61,14 +82,14 @@ def age(self): # ---------------------------------------------------------------------- def _readable_delta(self, from_seconds, until_seconds=None): - '''Returns a nice readable delta. + """Returns a nice readable delta. readable_delta(1, 2) # 1 second ago readable_delta(1000, 2000) # 16 minutes ago readable_delta(1000, 9000) # 2 hours, 133 minutes ago readable_delta(1000, 987650) # 11 days ago readable_delta(1000) # 15049 days ago (relative to now) - ''' + """ if not until_seconds: until_seconds = time.time() @@ -83,12 +104,12 @@ def _readable_delta(self, from_seconds, until_seconds=None): # show a fuzzy but useful approximation of the time delta if delta.days: return f'{delta.days} days ago' - elif delta_hours: + if delta_hours: return f'{delta_hours} hours ago' - elif delta_minutes: + if delta_minutes: return f'{delta_minutes} minutes ago' - else: - return f'{delta.seconds} seconds ago' + + return f'{delta.seconds} seconds ago' # ---------------------------------------------------------------------- def get_linecount(self): @@ -98,39 +119,19 @@ def get_linecount(self): def content_splitted(self): return self.content_highlighted.splitlines() - # ---------------------------------------------------------------------- - def save(self, *args, **kwargs): # pylint: disable=signature-differs - if not self.pk and not self.secret_id: - self.secret_id = generate_secret_id() - if not self.published: - self.published = timezone.now() - - self.content_highlighted = self.content - super().save(*args, **kwargs) - # invalidate cache - cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL]) - # ---------------------------------------------------------------------- def delete(self, *args, **kwargs): # pylint: disable=signature-differs super().delete(*args, **kwargs) # invalidate cache cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL]) - # ---------------------------------------------------------------------- - def get_absolute_url(self): - return reverse('snippet_details', kwargs={'snippet_id': self.secret_id}) - - # ---------------------------------------------------------------------- - def __str__(self): - return f'{self.secret_id}' - class SpamwordManager(models.Manager): # ---------------------------------------------------------------------- def get_regex(self): return re.compile( - r'|'.join((i[1] for i in self.values_list())), + r'|'.join(i[1] for i in self.values_list()), re.MULTILINE) diff --git a/pastebin/templatetags/pastebin_tags.py b/pastebin/templatetags/pastebin_tags.py index 815a4b7..059fbe1 100644 --- a/pastebin/templatetags/pastebin_tags.py +++ b/pastebin/templatetags/pastebin_tags.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -41,10 +40,7 @@ def timeuntil_or_forever(snippet_expire): @register.filter def highlight(snippet, line_count=None): highlighted = pygmentize(snippet.content, snippet.lexer) - if highlighted: - lines = highlighted.splitlines() - else: - lines = snippet.content.splitlines() + lines = highlighted.splitlines() if highlighted else snippet.content.splitlines() if line_count: lines = lines[:line_count] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7f38c9f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,130 @@ +### +### Ruff +### +[tool.ruff] +line-length = 100 +select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # mccabe + "DJ", # flake8-django + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "EM", # flake8-errmsg + "ERA", # eradicate + "EXE", # flake8-executable + "F", # Pyflakes + "FBT", # flake8-boolean-trap + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "NPY", # NumPy-specific rules + "PD", # pandas-vet + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # Pylint + "PT", # flake8-pytest-style + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLF", # flake8-self + "T10", # flake8-debugger + "T20", # flake8-print + "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 +] + +ignore = [ + 'DJ001', # Django model text-based fields shouldn't be nullable + 'FBT002', # Boolean default value in function definition + 'BLE001', # Do not catch blind exception: `Exception + 'RET504', # Unnecessary variable assignment before `return` statement + 'SIM114', # Combine `if` branches using logical `or` operator + +] + +fixable = ["I", "Q000"] +unfixable = [] + +exclude = [ + ".eggs", + ".git", + ".ruff_cache", + ".tox", + "__pypackages__", + "_build", + "venv", + "migrations" +] + +[tool.ruff.isort] +lines-after-imports = 2 + +[tool.ruff.flake8-quotes] +inline-quotes = "single" + +[tool.ruff.pylint] +allow-magic-value-types = ["bytes", "int", "str"] +max-args = 7 + +### +### PyLint +### +[tool.pylint.main] +ignore = ".git" +ignore-patterns = [ + "local_settings.py", + "local_settings.docker.py", +] +persistent = false +load-plugins=[ + "pylint_django", + "pylint.extensions.bad_builtin", + "pylint.extensions.check_elif", + "pylint.extensions.comparetozero", + "pylint.extensions.emptystring", + "pylint.extensions.mccabe", + "pylint.extensions.overlapping_exceptions", + "pylint.extensions.redefined_variable_type", +] + +[tool.pylint."messages control"] +disable = [ + "empty-docstring", + "logging-format-interpolation", + "missing-docstring", + "no-else-return", +] + +[tool.pylint.reports] +output-format = "parseable" +reports = false + +[tool.pylint.format] +max-line-length = 100 + +[tool.pylint.variables] +dummy-variables-rgx = "_|dummy" + +[tool.pylint.design] +min-public-methods = 0 +max-attributes = 10 +max-args = 7 +max-parents = 9 + +[tool.pylint.exceptions] +overgeneral-exceptions = [] diff --git a/static_docs/apps.py b/static_docs/apps.py index 7548c87..f4a63d8 100644 --- a/static_docs/apps.py +++ b/static_docs/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -17,4 +16,4 @@ class StaticDocsAppConfig(AppConfig): name = 'static_docs' - verbose_name = "Static Docs" + verbose_name = 'Static Docs' diff --git a/static_docs/generate_i18n_statistics.py b/static_docs/generate_i18n_statistics.py index 81d786e..37d756f 100644 --- a/static_docs/generate_i18n_statistics.py +++ b/static_docs/generate_i18n_statistics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -12,13 +11,13 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from json import dump, JSONEncoder +import re +from json import JSONEncoder, dump from os import listdir, makedirs from os.path import join, splitext -from subprocess import CalledProcessError, check_output, STDOUT +from subprocess import STDOUT, CalledProcessError, check_output from tempfile import TemporaryDirectory from time import time -import re from babel import Locale, UnknownLocaleError @@ -96,7 +95,7 @@ def _generate(self, temp_path): self._update_pot_file() self._fetch_pot_stats() self._fetch_message_catalogs() - for self._message_catalog in self._message_catalogs: + for self._message_catalog in self._message_catalogs: # noqa: B020 self._update_message_catalog() self._fetch_message_catalog_stats() @@ -149,13 +148,13 @@ def _execute_command(self, command): cwd=self._destination_path, stderr=STDOUT) output_utf8 = output.decode('utf-8') - return output_utf8 except CalledProcessError as exc: command_line = ' '.join(command) error_message = exc.output.decode('utf-8') - raise ValueError( - f'Command: "{command_line}" exited with code {exc.returncode}: {error_message}' - ) from exc + msg = f'Command: "{command_line}" exited with code {exc.returncode}: {error_message}' + raise ValueError(msg) from exc + + return output_utf8 # ---------------------------------------------------------------------- def _fetch_pot_stats(self): @@ -173,7 +172,8 @@ def _read_po_translation_statistics(self, filename): fuzzy = match.group('fuzzy') untranslated = match.group('untranslated') else: - raise ValueError(f'Unable to parse msgfmt output: {output}') + msg = f'Unable to parse msgfmt output: {output}' + raise ValueError(msg) return TranslationStatistics( translated=int(translated) if translated is not None else 0, diff --git a/static_docs/github_client.py b/static_docs/github_client.py index 205fdbf..0481b5b 100644 --- a/static_docs/github_client.py +++ b/static_docs/github_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -12,8 +11,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from base64 import standard_b64decode, standard_b64encode import logging +from base64 import standard_b64decode, standard_b64encode import requests @@ -46,13 +45,13 @@ def get_file_contents(self, filename, user=None, repository=None): # ---------------------------------------------------------------------- def _request(self, url, status_404_expected=False): - request_args = {'timeout': HTTP_REQUEST_TIMEOUT, 'stream': False} + request_args = {'stream': False} if self._auth_token is not None: authorization_header = self._factor_authorization_header() request_args['headers'] = authorization_header try: - with requests.get(url, **request_args) as response: + with requests.get(url, timeout=HTTP_REQUEST_TIMEOUT, **request_args) as response: self._log_request(response, status_404_expected) self._log_rate_limit(response) # error out on 4xx and 5xx status codes @@ -60,8 +59,8 @@ def _request(self, url, status_404_expected=False): except requests.exceptions.HTTPError as exc: if exc.response.status_code == 404 and status_404_expected: return None - else: - raise + + raise return response diff --git a/static_docs/management/commands/generate_i18n_statistics.py b/static_docs/management/commands/generate_i18n_statistics.py index b05f7aa..0f35593 100644 --- a/static_docs/management/commands/generate_i18n_statistics.py +++ b/static_docs/management/commands/generate_i18n_statistics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -19,7 +18,7 @@ class Command(BaseCommand): - help = "Generate a JSON file with I18N statistics after updating PO files" + help = 'Generate a JSON file with I18N statistics after updating PO files' # noqa: A003 # ---------------------------------------------------------------------- def handle(self, *args, **options): diff --git a/static_docs/views.py b/static_docs/views.py index fcbd206..92b26d4 100644 --- a/static_docs/views.py +++ b/static_docs/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -12,11 +11,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from datetime import datetime import json import logging import os.path import re +from datetime import datetime, timezone from django.conf import settings from django.core.cache import cache @@ -25,10 +24,10 @@ from mezzanine_pagedown.filters import plain as markdown_plain from geany.decorators import ( - cache_function, CACHE_KEY_STATIC_DOCS_RELEASE_NOTES, CACHE_TIMEOUT_1HOUR, CACHE_TIMEOUT_24HOURS, + cache_function, ) from static_docs.github_client import GitHubApiClient @@ -118,7 +117,7 @@ def _parse_news_file(self): releases.append(current_release) current_release_notes = [] else: - line = line.lstrip() # remove any indentation + line = line.lstrip() # noqa: PLW2901 - remove any indentation if line and not line.startswith('*'): # we got a section: make it bold and add an additional new line # to make Markdown recognise the following lines as list @@ -165,7 +164,7 @@ def _get_release_notes_for_version(self, releases, version=None): release = rel break else: - raise Http404() + raise Http404 # convert the selected release (the one we want to display) to Markdown release.release_notes = markdown_plain(release.release_notes) @@ -185,7 +184,8 @@ def _get_release_from_github(self, version=None): return None # adapt date - release_datetime = datetime.strptime(github_release['published_at'], '%Y-%m-%dT%H:%M:%SZ') + release_datetime = datetime.strptime(github_release['published_at'], '%Y-%m-%dT%H:%M:%SZ')\ + .astimezone(tz=timezone.utc) release_date = release_datetime.strftime('%B %d, %Y') release = ReleaseDto() @@ -207,7 +207,7 @@ class ToDoView(StaticDocsView): Grab the TODO file from GIT master via Github API, parse it and send it back to the template """ - template_name = "pages/documentation/todo.html" + template_name = 'pages/documentation/todo.html' # ---------------------------------------------------------------------- def get_context_data(self, **kwargs): @@ -229,15 +229,15 @@ def _parse_news_file(self): class I18NStatisticsView(TemplateView): - template_name = "pages/i18n.html" + template_name = 'pages/i18n.html' # ---------------------------------------------------------------------- def get_context_data(self, **kwargs): i18n_statistics = self._get_i18n_statistics() context = super().get_context_data(**kwargs) context['i18n_statistics'] = i18n_statistics - context['generated_datetime'] = datetime.utcfromtimestamp( - i18n_statistics['generated_timestamp']) + context['generated_datetime'] = datetime.fromtimestamp( + i18n_statistics['generated_timestamp'], tz=timezone.utc) context['static_docs_geany_destination_url'] = settings.STATIC_DOCS_GEANY_DESTINATION_URL return context @@ -256,7 +256,7 @@ class ThemesView(StaticDocsView): Fetch the Geany-Themes index from https://github.com/geany/geany-themes/tree/master/index """ - template_name = "pages/download/themes.html" + template_name = 'pages/download/themes.html' # ---------------------------------------------------------------------- def get_context_data(self, **kwargs): diff --git a/tox.ini b/tox.ini index 6e6ca77..f1ba9e0 100644 --- a/tox.ini +++ b/tox.ini @@ -15,21 +15,19 @@ skip_missing_interpreters = true skipsdist = true envlist = - safety,py37,py38,py39,py310 + safety,py39,py310,py311 geany_modules = geany latest_version news nightlybuilds pastebin static_docs [testenv] deps = - flake8 - isort + ruff pylint pylint-django -r{toxinidir}/requirements.txt commands = - {envbindir}/flake8 {[tox]geany_modules} - {envbindir}/isort --check-only --diff {[tox]geany_modules} - {envbindir}/pylint --rcfile=tox.ini --django-settings-module=geany.settings {[tox]geany_modules} + {envbindir}/ruff check {[tox]geany_modules} + {envbindir}/pylint --rcfile=pyproject.toml --django-settings-module=geany.settings {[tox]geany_modules} [testenv:safety] deps = @@ -37,61 +35,3 @@ deps = -r{toxinidir}/requirements.txt commands = {envbindir}/safety check - -[flake8] -exclude = build,.git,docs,migrations,local_settings.py,local_settings.docker.py -ignore = E127,E128, -max-line-length = 100 - -[isort] -line_length = 100 -indent = 4 -multi_line_output = 3 -length_sort = false -force_alphabetical_sort_within_sections = true -sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER -lines_after_imports = 2 -from_first = true -include_trailing_comma = true -skip = local_settings.py,local_settings.docker.py - -# the following sections are for pylint -[pylint.main] -ignore=.git -ignore-patterns=local_settings.py,local_settings.docker.py -persistent=no -load-plugins= - pylint_django, - pylint.extensions.bad_builtin, - pylint.extensions.check_elif, - pylint.extensions.comparetozero, - pylint.extensions.emptystring, - pylint.extensions.mccabe, - pylint.extensions.overlapping_exceptions, - pylint.extensions.redefined_variable_type - -[pylint] -disable= - empty-docstring, - logging-format-interpolation, - missing-docstring, - no-else-return - -[pylint.reports] -output-format=parseable -reports=no - -[pylint.format] -max-line-length=100 - -[pylint.variables] -dummy-variables-rgx=_|dummy - -[pylint.design] -min-public-methods=0 -max-attributes=10 -max-args=7 -max-parents=9 - -[pylint.exceptions] -overgeneral-exceptions= diff --git a/urlshortener/apps.py b/urlshortener/apps.py index c5fea0b..33d31b0 100644 --- a/urlshortener/apps.py +++ b/urlshortener/apps.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/urlshortener/urls.py b/urlshortener/urls.py index 775748c..6f58577 100644 --- a/urlshortener/urls.py +++ b/urlshortener/urls.py @@ -1,4 +1,3 @@ -# coding: utf-8 # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/urlshortener/views.py b/urlshortener/views.py index 7723b96..42ed757 100644 --- a/urlshortener/views.py +++ b/urlshortener/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/uwsgi.ini b/uwsgi.ini index 06c6347..7fc6cea 100644 --- a/uwsgi.ini +++ b/uwsgi.ini @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or