From adaaf30e0e55491ad1791c0741f4cf1706cff68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Mon, 2 Dec 2024 18:21:09 +0100 Subject: [PATCH] Add Ruff --- .bandit.yaml | 31 -- .github/workflows/main.yaml | 2 +- .pre-commit-config.yaml | 54 +-- .prospector.yaml | 30 +- admin/c2cgeoportal_admin/__init__.py | 4 +- .../lib/ogcserver_synchronizer.py | 15 +- .../c2cgeoportal_admin/schemas/dimensions.py | 3 +- .../schemas/functionalities.py | 4 +- .../c2cgeoportal_admin/schemas/interfaces.py | 3 +- admin/c2cgeoportal_admin/schemas/metadata.py | 5 +- .../schemas/restriction_areas.py | 3 +- admin/c2cgeoportal_admin/schemas/roles.py | 3 +- admin/c2cgeoportal_admin/schemas/treegroup.py | 9 +- admin/c2cgeoportal_admin/views/__init__.py | 4 +- .../views/dimension_layers.py | 2 +- .../views/functionalities.py | 6 +- admin/c2cgeoportal_admin/views/interfaces.py | 2 +- .../c2cgeoportal_admin/views/layer_groups.py | 2 +- admin/c2cgeoportal_admin/views/layers.py | 2 +- admin/c2cgeoportal_admin/views/layers_cog.py | 4 +- .../views/layers_vectortiles.py | 4 +- admin/c2cgeoportal_admin/views/layers_wms.py | 9 +- admin/c2cgeoportal_admin/views/layers_wmts.py | 9 +- admin/c2cgeoportal_admin/views/layertree.py | 8 +- .../c2cgeoportal_admin/views/logged_views.py | 3 +- admin/c2cgeoportal_admin/views/logs.py | 11 +- .../views/oauth2_clients.py | 2 +- admin/c2cgeoportal_admin/views/ogc_servers.py | 6 +- .../views/restriction_areas.py | 2 +- admin/c2cgeoportal_admin/views/roles.py | 4 +- admin/c2cgeoportal_admin/views/themes.py | 2 +- .../views/themes_ordering.py | 6 +- admin/c2cgeoportal_admin/views/treeitems.py | 7 +- admin/c2cgeoportal_admin/views/users.py | 6 +- admin/c2cgeoportal_admin/widgets.py | 3 +- .../tests/lib/test_ogcserver_synchronizer.py | 4 +- bin/azure | 2 +- bin/build-l10n | 2 +- ci/changelog | 2 +- commons/c2cgeoportal_commons/alembic/env.py | 6 +- .../main/166ff2dcc48d_create_database.py | 25 +- ..._trigger_on_role_updates_user_in_static.py | 6 +- .../main/56dc90838d90_fix_removing_layerv1.py | 8 +- ..._trigger_on_role_updates_user_in_static.py | 6 +- ...527cd_add_layer_column_in_layerv1_table.py | 6 +- ...c0_add_trigger_to_be_able_to_correctly_.py | 10 +- ...345672454_merge_2_4_and_master_branches.py | 1 - ...a88908_move_user_table_to_static_schema.py | 34 +- ...1b17b20_add_timezone_on_datetime_fields.py | 12 +- ...e9613256_wip_add_openid_connect_support.py | 1 - .../c2cgeoportal_commons/models/__init__.py | 2 +- commons/c2cgeoportal_commons/models/main.py | 23 +- commons/c2cgeoportal_commons/models/static.py | 2 +- .../c2cgeoportal_commons/testing/__init__.py | 5 +- .../testing/initializedb.py | 22 +- doc/integrator/requirements.rst | 2 +- doc/pyproject.toml | 8 - docker/config/bin/eval-templates | 6 +- docker/qgisserver/.bandit.yaml | 2 - docker/qgisserver/.prospector.yaml | 4 +- docker/qgisserver/poetry.lock | 32 +- docker/qgisserver/pyproject.toml | 21 +- geoportal/c2cgeoportal_geoportal/__init__.py | 44 +- .../c2cgeoportal_geoportal/lib/__init__.py | 28 +- .../lib/authentication.py | 2 +- .../c2cgeoportal_geoportal/lib/caching.py | 6 +- .../c2cgeoportal_geoportal/lib/checker.py | 14 +- .../lib/common_headers.py | 1 - .../lib/dbreflection.py | 17 +- .../lib/filter_capabilities.py | 27 +- .../lib/functionality.py | 6 +- .../c2cgeoportal_geoportal/lib/layers.py | 27 +- .../lib/lingva_extractor.py | 58 ++- .../c2cgeoportal_geoportal/lib/oauth2.py | 381 +++++++++++------- geoportal/c2cgeoportal_geoportal/lib/oidc.py | 38 +- .../c2cgeoportal_geoportal/lib/wmstparsing.py | 10 +- geoportal/c2cgeoportal_geoportal/lib/xsd.py | 22 +- .../geoportal/.prospector.yaml | 29 +- .../{{cookiecutter.project}}/pyproject.toml | 9 +- .../scripts/c2cupgrade.py | 138 +++---- .../scripts/manage_users.py | 10 +- .../c2cgeoportal_geoportal/scripts/pcreate.py | 19 +- .../scripts/theme2fts.py | 11 +- .../scripts/urllogin.py | 2 - geoportal/c2cgeoportal_geoportal/views/dev.py | 4 +- .../c2cgeoportal_geoportal/views/dynamic.py | 7 +- .../c2cgeoportal_geoportal/views/entry.py | 2 - .../views/fulltextsearch.py | 4 +- .../views/geometry_processing.py | 3 +- .../c2cgeoportal_geoportal/views/i18n.py | 1 - .../c2cgeoportal_geoportal/views/layers.py | 30 +- .../c2cgeoportal_geoportal/views/login.py | 19 +- .../views/mapserverproxy.py | 11 +- .../c2cgeoportal_geoportal/views/ogcproxy.py | 4 +- .../c2cgeoportal_geoportal/views/pdfreport.py | 8 +- .../views/printproxy.py | 2 +- .../c2cgeoportal_geoportal/views/proxy.py | 2 +- .../c2cgeoportal_geoportal/views/raster.py | 6 +- .../views/resourceproxy.py | 2 +- .../c2cgeoportal_geoportal/views/shortener.py | 13 +- .../c2cgeoportal_geoportal/views/theme.py | 50 +-- .../views/tinyowsproxy.py | 22 +- .../views/vector_tiles.py | 2 +- poetry.lock | 38 +- pyproject.toml | 28 +- scripts/get-version | 4 +- scripts/updated_version | 2 +- scripts/upgrade | 4 +- 108 files changed, 861 insertions(+), 830 deletions(-) delete mode 100644 .bandit.yaml delete mode 100644 docker/qgisserver/.bandit.yaml diff --git a/.bandit.yaml b/.bandit.yaml deleted file mode 100644 index a04a4bb1dc..0000000000 --- a/.bandit.yaml +++ /dev/null @@ -1,31 +0,0 @@ -profile: - sql: - exclude: - - /commons/c2cgeoportal_commons/alembic/main/ - - /commons/c2cgeoportal_commons/alembic/static/ - tests: - - B608 # Possible SQL injection vector through string-based query construction. - subprocess: - exclude: - - /commons/c2cgeoportal_commons/testing/ - - /commons/tests/ - - /geoportal/tests/ - - /geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py - - /admin/tests/ - tests: - - B603 # subprocess call - check for execution of untrusted input. - - B607 # Starting a process with a partial executable path - - B404 # Consider possible security implications associated with call module. - tmp: - exclude: - - /geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py - tests: - - B108 # Probable insecure usage of temp file/directory. -skips: - - B101 # Test for use of assert - - B603 # subprocess call - check for execution of untrusted input. - - B607 # Starting a process with a partial executable path - - B608 # Possible SQL injection vector through string-based query construction. - - B108 # Probable insecure usage of temp file/directory. - - B404 # Consider possible security implications associated with call module. - - B113 # Requests call without timeout. Done by c2cwsgiutils diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 0688a4e1c0..58ef79b732 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -34,7 +34,7 @@ jobs: # When we upgrade this we should also upgrade the requirements # in the documentation: doc/integrator/requirements.rst # and the first pyupgrade pre-commit hook in .pre-commit-config.yaml - MIN_PYTHON_VERSION: '3.8' + MIN_PYTHON_VERSION: '3.10' steps: - run: '! ls BACKPORT_TODO' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3be1489d3b..99314b324a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -104,64 +104,24 @@ repos: docker/config/haproxy_dev/localhost\.pem |geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter\.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3\.sql )$ - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 hooks: - # The script that will run on the project host - - id: pyupgrade + - id: ruff-format args: - - --py38-plus - files: |- - (?x)^( - geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter\.project}}/(build - |scripts/.*) - |scripts/(get-version - |upgrade) - )$ - # All other - - id: pyupgrade - args: - - --py310-plus - # geoportal/c2cgeoportal_geoportal/views/theme\.py is present because of issue: - # https://bugs.launchpad.net/lxml/+bug/2079018 - exclude: |- - (?x)^( - geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter\.project}}/(build - |scripts/.*) - |scripts/(get-version - |upgrade) - |geoportal/c2cgeoportal_geoportal/views/theme\.py - )$ - - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 - hooks: - - id: autoflake - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - exclude: |- - (?x)^( - commons/c2cgeoportal_commons/alembic/script\.py\.mako - |.*\.rst - |.*\.rst\.tmpl - |geoportal/c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__\.py - )$ + - --line-length=110 - repo: https://github.com/PyCQA/prospector rev: v1.13.3 hooks: - id: prospector args: - - --tool=pydocstyle + - --tool=ruff - --die-on-tool-error - --output-format=pylint additional_dependencies: - - prospector-profile-duplicated==1.8.0 # pypi + - prospector-profile-duplicated==1.8.1 # pypi - prospector-profile-utils==1.13.0 # pypi + - ruff==0.8.1 # pypi - repo: https://github.com/sbrunner/jsonschema-validator rev: 0.3.2 hooks: diff --git a/.prospector.yaml b/.prospector.yaml index 95964a60c7..c6b2c654c5 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -3,6 +3,8 @@ inherits: - utils:base - utils:fix - utils:no-design-checks + - utils:unsafe + - utils:c2cwsgiutils ignore-paths: - commons/setup.py @@ -10,28 +12,14 @@ ignore-paths: - admin/setup.py - geoportal/c2cgeoportal_geoportal/scaffolds - docker/qgisserver + - commons/c2cgeoportal_commons/alembic/main + - commons/c2cgeoportal_commons/alembic/static -pycodestyle: - disable: - # Buggy checks with Python 3.12 - - W604 # backticks are deprecated, use 'repr()' - - W603 # '<>' is deprecated, use '!=' - - E702 # multiple statements on one line (semicolon) - - E713 # test for membership should be 'not in' +mypy: + options: + python_version: '3.10' -pydocstyle: +ruff: disable: - D102 # Missing docstring in public method - - D104 # Missing docstring in public package - - D105 # Missing docstring in magic method - - D107 # Missing docstring in __init__ - - D200 # One-line docstring should fit on one line with quotes - - D202 # No blank lines allowed after function docstring (found 1) - - D203 # 1 blank line required before class docstring (found 0) - - D212 # Multi-line docstring summary should start at the first line - - D407 # Missing dashed underline after section ('Arguments') - - D412 # No blank lines allowed between a section header and its content ('Arguments') - -bandit: - options: - config: .bandit.yaml + - D107 # Missing docstring in `__init__` diff --git a/admin/c2cgeoportal_admin/__init__.py b/admin/c2cgeoportal_admin/__init__.py index 4d4227d388..2747e3c5a7 100644 --- a/admin/c2cgeoportal_admin/__init__.py +++ b/admin/c2cgeoportal_admin/__init__.py @@ -93,7 +93,9 @@ def get_tm_session( ) # Add fake user as we do not have authentication from geoportal - from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel + User, + ) config.add_request_method( lambda request: User( diff --git a/admin/c2cgeoportal_admin/lib/ogcserver_synchronizer.py b/admin/c2cgeoportal_admin/lib/ogcserver_synchronizer.py index 64905a6f47..3ca3f03444 100644 --- a/admin/c2cgeoportal_admin/lib/ogcserver_synchronizer.py +++ b/admin/c2cgeoportal_admin/lib/ogcserver_synchronizer.py @@ -29,16 +29,15 @@ import functools import logging from io import StringIO -from typing import Any, Optional, cast +from typing import Any, cast from xml.etree.ElementTree import Element # nosec import pyramid.request import requests -from defusedxml import ElementTree -from sqlalchemy.orm.session import Session - from c2cgeoportal_commons.lib.url import get_url2 from c2cgeoportal_commons.models import main +from defusedxml import ElementTree +from sqlalchemy.orm.session import Session class dry_run_transaction: # noqa ignore=N801: class names should use CapWords convention @@ -238,7 +237,7 @@ def get_theme(self, el: ElementTree) -> main.Theme: name = name_el.text theme = cast( - Optional[main.Theme], + main.Theme | None, self._request.dbsession.query(main.Theme).filter(main.Theme.name == name).one_or_none(), ) @@ -263,7 +262,7 @@ def get_layer_group(self, el: Element, parent: main.TreeGroup) -> main.LayerGrou assert name is not None group = cast( - Optional[main.LayerGroup], + main.LayerGroup | None, ( self._request.dbsession.query(main.LayerGroup) .filter(main.LayerGroup.name == name) @@ -294,7 +293,7 @@ def get_layer_wms(self, el: Element, parent: main.TreeGroup | None = None) -> ma assert name is not None layer = cast( - Optional[main.LayerWMS], + main.LayerWMS | None, self._request.dbsession.query(main.LayerWMS).filter(main.LayerWMS.name == name).one_or_none(), ) @@ -358,7 +357,7 @@ def get_layer_wms(self, el: Element, parent: main.TreeGroup | None = None) -> ma return layer - @functools.lru_cache(maxsize=10) + @functools.lru_cache(maxsize=10) # noqa: B019 def wms_capabilities(self) -> bytes: errors: set[str] = set() url = get_url2( diff --git a/admin/c2cgeoportal_admin/schemas/dimensions.py b/admin/c2cgeoportal_admin/schemas/dimensions.py index 3326057d16..4a9fb0c27e 100644 --- a/admin/c2cgeoportal_admin/schemas/dimensions.py +++ b/admin/c2cgeoportal_admin/schemas/dimensions.py @@ -30,11 +30,10 @@ import colander from c2cgeoform.schema import GeoFormSchemaNode +from c2cgeoportal_commons.models.main import Dimension from deform.widget import MappingWidget, SequenceWidget from sqlalchemy.orm.attributes import InstrumentedAttribute -from c2cgeoportal_commons.models.main import Dimension - def dimensions_schema_node( prop: InstrumentedAttribute[Any], # pylint: disable=unsubscriptable-object diff --git a/admin/c2cgeoportal_admin/schemas/functionalities.py b/admin/c2cgeoportal_admin/schemas/functionalities.py index 327aa56b56..a05d544032 100644 --- a/admin/c2cgeoportal_admin/schemas/functionalities.py +++ b/admin/c2cgeoportal_admin/schemas/functionalities.py @@ -31,12 +31,11 @@ import colander from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator +from c2cgeoportal_commons.models.main import Functionality from sqlalchemy import inspect, select from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.sql.functions import concat -from c2cgeoportal_commons.models.main import Functionality - def available_functionalities_for(settings: dict[str, Any], model: type[Any]) -> list[dict[str, Any]]: """Return filtered list of functionality definitions.""" @@ -81,7 +80,6 @@ def functionalities_schema_node( prop: InstrumentedAttribute[Any], model: type[Any] ) -> colander.SequenceSchema: """Get the schema of the functionalities.""" - return colander.SequenceSchema( GeoFormManyToManySchemaNode(Functionality, None), name=prop.key, diff --git a/admin/c2cgeoportal_admin/schemas/interfaces.py b/admin/c2cgeoportal_admin/schemas/interfaces.py index 212aefc3c1..2bf697e42a 100644 --- a/admin/c2cgeoportal_admin/schemas/interfaces.py +++ b/admin/c2cgeoportal_admin/schemas/interfaces.py @@ -31,9 +31,8 @@ import colander from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator -from sqlalchemy.orm.attributes import InstrumentedAttribute - from c2cgeoportal_commons.models.main import Interface +from sqlalchemy.orm.attributes import InstrumentedAttribute def interfaces_schema_node( diff --git a/admin/c2cgeoportal_admin/schemas/metadata.py b/admin/c2cgeoportal_admin/schemas/metadata.py index 4fa216ff61..cb0c738392 100644 --- a/admin/c2cgeoportal_admin/schemas/metadata.py +++ b/admin/c2cgeoportal_admin/schemas/metadata.py @@ -31,14 +31,14 @@ import colander import pyramid.request from c2cgeoform.schema import GeoFormSchemaNode +from c2cgeoportal_commons.lib.validators import url +from c2cgeoportal_commons.models.main import Metadata from deform.widget import MappingWidget, SelectWidget, SequenceWidget, TextAreaWidget from sqlalchemy import inspect from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.mapper import Mapper from c2cgeoportal_admin import _ -from c2cgeoportal_commons.lib.validators import url -from c2cgeoportal_commons.models.main import Metadata def get_relevant_for(model: type[Any] | Mapper[Any]) -> set[str]: @@ -202,7 +202,6 @@ def _translate_available_metadata( def metadata_schema_node(prop: InstrumentedAttribute[Any], model: type[Any]) -> colander.SequenceSchema: """Get the schema of a collection of metadata.""" - # Deferred which returns a dictionary with metadata name as key and metadata definition as value. # Needed to get the metadata types on UI side. metadata_definitions_dict = colander.deferred( diff --git a/admin/c2cgeoportal_admin/schemas/restriction_areas.py b/admin/c2cgeoportal_admin/schemas/restriction_areas.py index d13e70b3de..cd22bd6ea2 100644 --- a/admin/c2cgeoportal_admin/schemas/restriction_areas.py +++ b/admin/c2cgeoportal_admin/schemas/restriction_areas.py @@ -31,9 +31,8 @@ import colander from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator -from sqlalchemy.orm.attributes import InstrumentedAttribute - from c2cgeoportal_commons.models.main import RestrictionArea +from sqlalchemy.orm.attributes import InstrumentedAttribute def restrictionareas_schema_node( diff --git a/admin/c2cgeoportal_admin/schemas/roles.py b/admin/c2cgeoportal_admin/schemas/roles.py index 308a322b43..8ec1357ee3 100644 --- a/admin/c2cgeoportal_admin/schemas/roles.py +++ b/admin/c2cgeoportal_admin/schemas/roles.py @@ -31,9 +31,8 @@ import colander from c2cgeoform.ext.deform_ext import RelationCheckBoxListWidget from c2cgeoform.schema import GeoFormManyToManySchemaNode, manytomany_validator -from sqlalchemy.orm.attributes import InstrumentedAttribute - from c2cgeoportal_commons.models.main import Role +from sqlalchemy.orm.attributes import InstrumentedAttribute def roles_schema_node( diff --git a/admin/c2cgeoportal_admin/schemas/treegroup.py b/admin/c2cgeoportal_admin/schemas/treegroup.py index ea47cb501d..187fb37f29 100644 --- a/admin/c2cgeoportal_admin/schemas/treegroup.py +++ b/admin/c2cgeoportal_admin/schemas/treegroup.py @@ -34,13 +34,13 @@ import pyramid.request import sqlalchemy from c2cgeoform.schema import GeoFormSchemaNode +from c2cgeoportal_commons.lib.literal import Literal +from c2cgeoportal_commons.models.main import LayergroupTreeitem, TreeGroup, TreeItem from sqlalchemy.orm import aliased from sqlalchemy.sql.expression import case, func from c2cgeoportal_admin import _ from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget -from c2cgeoportal_commons.lib.literal import Literal -from c2cgeoportal_commons.models.main import LayergroupTreeitem, TreeGroup, TreeItem _LOG = logging.getLogger(__name__) @@ -74,7 +74,8 @@ def treeitems( assert isinstance(dbsession, sqlalchemy.orm.Session) group = case( - (func.count(LayergroupTreeitem.id) == 0, "Unlinked"), else_="Others" # pylint: disable=not-callable + (func.count(LayergroupTreeitem.id) == 0, "Unlinked"), # pylint: disable=not-callable + else_="Others", ) query = ( @@ -123,7 +124,7 @@ def treeitems( def children_validator(node, cstruct): """Get the validator on the children nodes.""" for dict_ in cstruct: - if not dict_["treeitem_id"] in [item["id"] for item in node.candidates]: + if dict_["treeitem_id"] not in [item["id"] for item in node.candidates]: raise colander.Invalid( node, _("Value {} does not exist in table {} or is not allowed to avoid cycles").format( diff --git a/admin/c2cgeoportal_admin/views/__init__.py b/admin/c2cgeoportal_admin/views/__init__.py index 611546b28b..189e935184 100644 --- a/admin/c2cgeoportal_admin/views/__init__.py +++ b/admin/c2cgeoportal_admin/views/__init__.py @@ -4,9 +4,7 @@ class IsAdminPredicate: - """ - A custom predicate that checks if the request is for the admin interface. - """ + """A custom predicate that checks if the request is for the admin interface.""" def __init__(self, val, info): del info diff --git a/admin/c2cgeoportal_admin/views/dimension_layers.py b/admin/c2cgeoportal_admin/views/dimension_layers.py index 169627eb40..51209e8891 100644 --- a/admin/c2cgeoportal_admin/views/dimension_layers.py +++ b/admin/c2cgeoportal_admin/views/dimension_layers.py @@ -32,10 +32,10 @@ import sqlalchemy.orm.query from c2cgeoform.views.abstract_views import ListField +from c2cgeoportal_commons.models.main import DimensionLayer from sqlalchemy.orm import subqueryload from c2cgeoportal_admin.views.layers import LayerViews -from c2cgeoportal_commons.models.main import DimensionLayer _list_field = partial(ListField, DimensionLayer) diff --git a/admin/c2cgeoportal_admin/views/functionalities.py b/admin/c2cgeoportal_admin/views/functionalities.py index 71de35fcfc..ea4ab57a41 100644 --- a/admin/c2cgeoportal_admin/views/functionalities.py +++ b/admin/c2cgeoportal_admin/views/functionalities.py @@ -40,12 +40,12 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import Functionality from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from c2cgeoportal_admin import _ from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.models.main import Functionality _list_field = partial(ListField, Functionality) @@ -103,7 +103,9 @@ def delete(self) -> DeleteResponse: return super().delete() @view_config( - route_name="c2cgeoform_item_duplicate", request_method="GET", renderer="../templates/edit.jinja2" # type: ignore[misc] + route_name="c2cgeoform_item_duplicate", + request_method="GET", + renderer="../templates/edit.jinja2", # type: ignore[misc] ) def duplicate(self) -> ObjectResponse: return super().duplicate() diff --git a/admin/c2cgeoportal_admin/views/interfaces.py b/admin/c2cgeoportal_admin/views/interfaces.py index 806534cc6a..2a8b6406ae 100644 --- a/admin/c2cgeoportal_admin/views/interfaces.py +++ b/admin/c2cgeoportal_admin/views/interfaces.py @@ -37,10 +37,10 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import Interface from pyramid.view import view_config, view_defaults from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.models.main import Interface _list_field = partial(ListField, Interface) diff --git a/admin/c2cgeoportal_admin/views/layer_groups.py b/admin/c2cgeoportal_admin/views/layer_groups.py index fc5a61d93a..ae68c2f2f5 100644 --- a/admin/c2cgeoportal_admin/views/layer_groups.py +++ b/admin/c2cgeoportal_admin/views/layer_groups.py @@ -39,6 +39,7 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import LayerGroup, TreeGroup from deform.widget import FormWidget from pyramid.view import view_config, view_defaults @@ -46,7 +47,6 @@ from c2cgeoportal_admin.schemas.treegroup import children_schema_node from c2cgeoportal_admin.schemas.treeitem import parent_id_node from c2cgeoportal_admin.views.treeitems import TreeItemViews -from c2cgeoportal_commons.models.main import LayerGroup, TreeGroup _list_field = partial(ListField, LayerGroup) diff --git a/admin/c2cgeoportal_admin/views/layers.py b/admin/c2cgeoportal_admin/views/layers.py index 038472b15f..b2b47521bd 100644 --- a/admin/c2cgeoportal_admin/views/layers.py +++ b/admin/c2cgeoportal_admin/views/layers.py @@ -32,10 +32,10 @@ import sqlalchemy import sqlalchemy.orm.query from c2cgeoform.views.abstract_views import ListField +from c2cgeoportal_commons.models.main import Interface, Layer from sqlalchemy.orm import subqueryload from c2cgeoportal_admin.views.treeitems import TreeItemViews -from c2cgeoportal_commons.models.main import Interface, Layer _list_field = partial(ListField, Layer) diff --git a/admin/c2cgeoportal_admin/views/layers_cog.py b/admin/c2cgeoportal_admin/views/layers_cog.py index 7ba329eba9..f655304baf 100644 --- a/admin/c2cgeoportal_admin/views/layers_cog.py +++ b/admin/c2cgeoportal_admin/views/layers_cog.py @@ -40,6 +40,8 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.lib.literal import Literal +from c2cgeoportal_commons.models.main import LayerCOG, LayerGroup from deform.widget import FormWidget from pyramid.httpexceptions import HTTPNotFound from pyramid.view import view_config, view_defaults @@ -50,8 +52,6 @@ from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node from c2cgeoportal_admin.schemas.treeitem import parent_id_node from c2cgeoportal_admin.views.layers import LayerViews -from c2cgeoportal_commons.lib.literal import Literal -from c2cgeoportal_commons.models.main import LayerCOG, LayerGroup _list_field = partial(ListField, LayerCOG) diff --git a/admin/c2cgeoportal_admin/views/layers_vectortiles.py b/admin/c2cgeoportal_admin/views/layers_vectortiles.py index c451da4128..6648c9ddf8 100644 --- a/admin/c2cgeoportal_admin/views/layers_vectortiles.py +++ b/admin/c2cgeoportal_admin/views/layers_vectortiles.py @@ -40,6 +40,8 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.lib.literal import Literal +from c2cgeoportal_commons.models.main import LayerGroup, LayerVectorTiles from deform.widget import FormWidget from pyramid.httpexceptions import HTTPNotFound from pyramid.view import view_config, view_defaults @@ -50,8 +52,6 @@ from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node from c2cgeoportal_admin.schemas.treeitem import parent_id_node from c2cgeoportal_admin.views.dimension_layers import DimensionLayerViews -from c2cgeoportal_commons.lib.literal import Literal -from c2cgeoportal_commons.models.main import LayerGroup, LayerVectorTiles _list_field = partial(ListField, LayerVectorTiles) diff --git a/admin/c2cgeoportal_admin/views/layers_wms.py b/admin/c2cgeoportal_admin/views/layers_wms.py index 8acd8bab2b..520fd2ffd8 100644 --- a/admin/c2cgeoportal_admin/views/layers_wms.py +++ b/admin/c2cgeoportal_admin/views/layers_wms.py @@ -40,6 +40,14 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import ( + LayerGroup, + LayerWMS, + LayerWMTS, + LogAction, + OGCServer, + TreeItem, +) from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from sqlalchemy import delete, insert, inspect, update @@ -52,7 +60,6 @@ from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node from c2cgeoportal_admin.schemas.treeitem import parent_id_node from c2cgeoportal_admin.views.dimension_layers import DimensionLayerViews -from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, LogAction, OGCServer, TreeItem _list_field = partial(ListField, LayerWMS) diff --git a/admin/c2cgeoportal_admin/views/layers_wmts.py b/admin/c2cgeoportal_admin/views/layers_wmts.py index 1798d6317c..cdbf4416ba 100644 --- a/admin/c2cgeoportal_admin/views/layers_wmts.py +++ b/admin/c2cgeoportal_admin/views/layers_wmts.py @@ -40,6 +40,14 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import ( + LayerGroup, + LayerWMS, + LayerWMTS, + LogAction, + OGCServer, + TreeItem, +) from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from sqlalchemy import delete, insert, inspect, update @@ -52,7 +60,6 @@ from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node from c2cgeoportal_admin.schemas.treeitem import parent_id_node from c2cgeoportal_admin.views.dimension_layers import DimensionLayerViews -from c2cgeoportal_commons.models.main import LayerGroup, LayerWMS, LayerWMTS, LogAction, OGCServer, TreeItem _list_field = partial(ListField, LayerWMTS) diff --git a/admin/c2cgeoportal_admin/views/layertree.py b/admin/c2cgeoportal_admin/views/layertree.py index c9a1b5ebab..e229418c34 100644 --- a/admin/c2cgeoportal_admin/views/layertree.py +++ b/admin/c2cgeoportal_admin/views/layertree.py @@ -30,12 +30,18 @@ import pyramid.request from c2cgeoform.views.abstract_views import DeleteResponse, ItemAction +from c2cgeoportal_commons.models.main import ( + Interface, + Layer, + LayergroupTreeitem, + Theme, + TreeItem, +) from pyramid.httpexceptions import HTTPNotFound from pyramid.view import view_config, view_defaults from translationstring import TranslationStringFactory from c2cgeoportal_admin import _ -from c2cgeoportal_commons.models.main import Interface, Layer, LayergroupTreeitem, Theme, TreeItem itemtypes_tables = { "theme": "themes", diff --git a/admin/c2cgeoportal_admin/views/logged_views.py b/admin/c2cgeoportal_admin/views/logged_views.py index 85f287ecbe..da1464c16f 100644 --- a/admin/c2cgeoportal_admin/views/logged_views.py +++ b/admin/c2cgeoportal_admin/views/logged_views.py @@ -29,10 +29,9 @@ from typing import Generic, TypeVar from c2cgeoform.views.abstract_views import AbstractViews, DeleteResponse, SaveResponse -from pyramid.httpexceptions import HTTPFound - from c2cgeoportal_commons.models import Base from c2cgeoportal_commons.models.main import Log, LogAction +from pyramid.httpexceptions import HTTPFound _T = TypeVar("_T", bound=Log) diff --git a/admin/c2cgeoportal_admin/views/logs.py b/admin/c2cgeoportal_admin/views/logs.py index 922df76cb0..0e20869e1c 100644 --- a/admin/c2cgeoportal_admin/views/logs.py +++ b/admin/c2cgeoportal_admin/views/logs.py @@ -28,11 +28,16 @@ from functools import partial from c2cgeoform import JSONDict -from c2cgeoform.views.abstract_views import AbstractViews, GridResponse, IndexResponse, ItemAction, ListField -from pyramid.view import view_config, view_defaults - +from c2cgeoform.views.abstract_views import ( + AbstractViews, + GridResponse, + IndexResponse, + ItemAction, + ListField, +) from c2cgeoportal_commons.models import _ from c2cgeoportal_commons.models.main import AbstractLog +from pyramid.view import view_config, view_defaults _list_field = partial(ListField, AbstractLog) diff --git a/admin/c2cgeoportal_admin/views/oauth2_clients.py b/admin/c2cgeoportal_admin/views/oauth2_clients.py index e30d5618a5..44297b2b49 100644 --- a/admin/c2cgeoportal_admin/views/oauth2_clients.py +++ b/admin/c2cgeoportal_admin/views/oauth2_clients.py @@ -37,10 +37,10 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.static import Log, OAuth2Client from pyramid.view import view_config, view_defaults from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.models.static import Log, OAuth2Client _list_field = partial(ListField, OAuth2Client) diff --git a/admin/c2cgeoportal_admin/views/ogc_servers.py b/admin/c2cgeoportal_admin/views/ogc_servers.py index 92e97c6b44..a1599cf0ed 100644 --- a/admin/c2cgeoportal_admin/views/ogc_servers.py +++ b/admin/c2cgeoportal_admin/views/ogc_servers.py @@ -45,6 +45,9 @@ SaveResponse, UserMessage, ) +from c2cgeoportal_commons.lib.literal import Literal +from c2cgeoportal_commons.models import cache_invalidate_cb +from c2cgeoportal_commons.models.main import LogAction, OGCServer from deform.widget import FormWidget from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config, view_defaults @@ -53,9 +56,6 @@ from c2cgeoportal_admin import _ from c2cgeoportal_admin.lib.ogcserver_synchronizer import OGCServerSynchronizer from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.lib.literal import Literal -from c2cgeoportal_commons.models import cache_invalidate_cb -from c2cgeoportal_commons.models.main import LogAction, OGCServer _list_field = partial(ListField, OGCServer) diff --git a/admin/c2cgeoportal_admin/views/restriction_areas.py b/admin/c2cgeoportal_admin/views/restriction_areas.py index 17b63d499f..3edcb63f3f 100644 --- a/admin/c2cgeoportal_admin/views/restriction_areas.py +++ b/admin/c2cgeoportal_admin/views/restriction_areas.py @@ -39,6 +39,7 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import Layer, RestrictionArea from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from sqlalchemy.orm import subqueryload @@ -47,7 +48,6 @@ from c2cgeoportal_admin.schemas.treegroup import treeitem_edit_url from c2cgeoportal_admin.views.logged_views import LoggedViews from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget -from c2cgeoportal_commons.models.main import Layer, RestrictionArea _list_field = partial(ListField, RestrictionArea) diff --git a/admin/c2cgeoportal_admin/views/roles.py b/admin/c2cgeoportal_admin/views/roles.py index ec8ee1706d..f26e1572d5 100644 --- a/admin/c2cgeoportal_admin/views/roles.py +++ b/admin/c2cgeoportal_admin/views/roles.py @@ -39,6 +39,8 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import Role +from c2cgeoportal_commons.models.static import User from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from sqlalchemy.orm import subqueryload @@ -47,8 +49,6 @@ from c2cgeoportal_admin.schemas.restriction_areas import restrictionareas_schema_node from c2cgeoportal_admin.views.logged_views import LoggedViews from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget -from c2cgeoportal_commons.models.main import Role -from c2cgeoportal_commons.models.static import User _list_field = partial(ListField, Role) diff --git a/admin/c2cgeoportal_admin/views/themes.py b/admin/c2cgeoportal_admin/views/themes.py index 15fc45980d..7914988884 100644 --- a/admin/c2cgeoportal_admin/views/themes.py +++ b/admin/c2cgeoportal_admin/views/themes.py @@ -39,6 +39,7 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.models.main import Functionality, Interface, Role, Theme from deform.widget import FormWidget from pyramid.view import view_config, view_defaults from sqlalchemy.orm import subqueryload @@ -50,7 +51,6 @@ from c2cgeoportal_admin.schemas.roles import roles_schema_node from c2cgeoportal_admin.schemas.treegroup import children_schema_node from c2cgeoportal_admin.views.treeitems import TreeItemViews -from c2cgeoportal_commons.models.main import Functionality, Interface, Role, Theme _list_field = partial(ListField, Theme) diff --git a/admin/c2cgeoportal_admin/views/themes_ordering.py b/admin/c2cgeoportal_admin/views/themes_ordering.py index c9faa7175a..80f4615039 100644 --- a/admin/c2cgeoportal_admin/views/themes_ordering.py +++ b/admin/c2cgeoportal_admin/views/themes_ordering.py @@ -29,6 +29,7 @@ import colander from c2cgeoform.schema import GeoFormSchemaNode from c2cgeoform.views.abstract_views import AbstractViews, ObjectResponse, SaveResponse +from c2cgeoportal_commons.models.main import Theme, TreeItem from deform import ValidationFailure from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config @@ -36,7 +37,6 @@ from c2cgeoportal_admin import _ from c2cgeoportal_admin.schemas.treegroup import treeitem_edit_url from c2cgeoportal_admin.widgets import ChildrenWidget, ChildWidget -from c2cgeoportal_commons.models.main import Theme, TreeItem class ThemeOrderSchema(GeoFormSchemaNode): # pylint: disable=abstract-method @@ -61,7 +61,7 @@ def themes(node, kw): # pylint: disable=unused-argument def themes_validator(node, cstruct): """Validate the theme.""" for dict_ in cstruct: - if not dict_["id"] in [item["id"] for item in node.candidates]: + if dict_["id"] not in [item["id"] for item in node.candidates]: raise colander.Invalid( node, _("Value {} does not exist in table {}").format(dict_["id"], Theme.__tablename__), @@ -126,7 +126,7 @@ def save(self) -> SaveResponse: self._request.dbsession.flush() return HTTPFound(self._request.route_url("layertree")) except ValidationFailure as e: - # FIXME see https://github.com/Pylons/deform/pull/243 + # FIXME see https://github.com/Pylons/deform/pull/243 # pylint: disable=fixme self._populate_widgets(form.schema) return { "title": form.title, diff --git a/admin/c2cgeoportal_admin/views/treeitems.py b/admin/c2cgeoportal_admin/views/treeitems.py index f762d9f22b..0c3d2a5196 100644 --- a/admin/c2cgeoportal_admin/views/treeitems.py +++ b/admin/c2cgeoportal_admin/views/treeitems.py @@ -31,12 +31,17 @@ import sqlalchemy from c2cgeoform.views.abstract_views import ListField, SaveResponse +from c2cgeoportal_commons.models.main import ( + LayergroupTreeitem, + Metadata, + TreeGroup, + TreeItem, +) from pyramid.view import view_config from sqlalchemy.orm import subqueryload from sqlalchemy.sql.functions import concat from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.models.main import LayergroupTreeitem, Metadata, TreeGroup, TreeItem _list_field = partial(ListField, TreeItem) diff --git a/admin/c2cgeoportal_admin/views/users.py b/admin/c2cgeoportal_admin/views/users.py index 769de9aa64..f42c4943a9 100644 --- a/admin/c2cgeoportal_admin/views/users.py +++ b/admin/c2cgeoportal_admin/views/users.py @@ -38,6 +38,9 @@ ObjectResponse, SaveResponse, ) +from c2cgeoportal_commons.lib.email_ import send_email_config +from c2cgeoportal_commons.models.main import Role +from c2cgeoportal_commons.models.static import Log, User from deform.widget import FormWidget from passwordgenerator import pwgenerator from pyramid.httpexceptions import HTTPFound @@ -46,9 +49,6 @@ from c2cgeoportal_admin.schemas.roles import roles_schema_node from c2cgeoportal_admin.views.logged_views import LoggedViews -from c2cgeoportal_commons.lib.email_ import send_email_config -from c2cgeoportal_commons.models.main import Role -from c2cgeoportal_commons.models.static import Log, User _list_field = partial(ListField, User) diff --git a/admin/c2cgeoportal_admin/widgets.py b/admin/c2cgeoportal_admin/widgets.py index bce6599c96..57c8e1a07a 100644 --- a/admin/c2cgeoportal_admin/widgets.py +++ b/admin/c2cgeoportal_admin/widgets.py @@ -29,12 +29,11 @@ import colander import pyramid.request +from c2cgeoportal_commons.models.main import TreeItem from colander import Mapping, SchemaNode from deform import widget from deform.widget import MappingWidget, SequenceWidget -from c2cgeoportal_commons.models.main import TreeItem - registry = widget.default_resource_registry registry.set_js_resources( "magicsuggest", None, "c2cgeoportal_admin:node_modules/magicsuggest-alpine/magicsuggest-min.js" diff --git a/admin/tests/lib/test_ogcserver_synchronizer.py b/admin/tests/lib/test_ogcserver_synchronizer.py index c193facbb5..9d333f602a 100644 --- a/admin/tests/lib/test_ogcserver_synchronizer.py +++ b/admin/tests/lib/test_ogcserver_synchronizer.py @@ -38,9 +38,7 @@ def wms_capabilities(content=DEFAULT_CONTENT): {} -""".format( - content - ) +""".format(content) @pytest.fixture(scope="function") diff --git a/bin/azure b/bin/azure index dd09b06725..24c4cea98f 100755 --- a/bin/azure +++ b/bin/azure @@ -25,7 +25,7 @@ import argparse import glob import os -import subprocess +import subprocess # nosec import yaml from azure.storage.blob import BlobServiceClient, ContainerClient, __version__ diff --git a/bin/build-l10n b/bin/build-l10n index ce85daa550..ea64b9a151 100755 --- a/bin/build-l10n +++ b/bin/build-l10n @@ -4,7 +4,7 @@ import argparse import glob import os import shutil -import subprocess +import subprocess # nosec def main() -> None: diff --git a/ci/changelog b/ci/changelog index 661004c953..665b61d1cc 100755 --- a/ci/changelog +++ b/ci/changelog @@ -31,7 +31,7 @@ import json import os import re -import subprocess +import subprocess # nosec import sys from typing import Any diff --git a/commons/c2cgeoportal_commons/alembic/env.py b/commons/c2cgeoportal_commons/alembic/env.py index 6c05079fe3..99667969b5 100755 --- a/commons/c2cgeoportal_commons/alembic/env.py +++ b/commons/c2cgeoportal_commons/alembic/env.py @@ -103,7 +103,11 @@ def run_migrations_online() -> None: # Autogenerate config alembic_name = context.config.get_main_option("type") - from c2cgeoportal_commons.models import Base, main, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + Base, + main, + static, + ) _schema = main._schema if alembic_name == "main" else static._schema # pylint: disable=protected-access diff --git a/commons/c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py b/commons/c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py index 3b40b6fddb..d745fb9b7f 100644 --- a/commons/c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py +++ b/commons/c2cgeoportal_commons/alembic/main/166ff2dcc48d_create_database.py @@ -40,7 +40,15 @@ from alembic import op from c2c.template.config import config from sqlalchemy import Column, ForeignKey, MetaData, Table -from sqlalchemy.types import Boolean, DateTime, Float, Integer, String, Unicode, UserDefinedType +from sqlalchemy.types import ( + Boolean, + DateTime, + Float, + Integer, + String, + Unicode, + UserDefinedType, +) # revision identifiers, used by Alembic. revision = "166ff2dcc48d" @@ -93,10 +101,7 @@ def upgrade() -> None: Column("readwrite", Boolean, default=False), schema=schema, ) - op.execute( - "SELECT AddGeometryColumn('%(schema)s', 'restrictionarea', " - "'area', %(srid)s, 'POLYGON', 2)" % {"schema": schema, "srid": srid} - ) + op.execute(f"SELECT AddGeometryColumn('{schema}', 'restrictionarea', " f"'area', {srid}, 'POLYGON', 2)") op.create_table( "shorturl", Column("id", Integer, primary_key=True), @@ -116,10 +121,7 @@ def upgrade() -> None: Column("description", Unicode), schema=schema, ) - op.execute( - "SELECT AddGeometryColumn('%(schema)s', 'role', " - "'extent', %(srid)s, 'POLYGON', 2)" % {"schema": schema, "srid": srid} - ) + op.execute(f"SELECT AddGeometryColumn('{schema}', 'role', " f"'extent', {srid}, 'POLYGON', 2)") role = Table("role", MetaData(), Column("name", Unicode, unique=True, nullable=False), schema=schema) op.bulk_insert(role, [{"name": "role_admin"}]) @@ -172,10 +174,7 @@ def upgrade() -> None: Column("params", Unicode, nullable=True), schema=schema, ) - op.execute( - "SELECT AddGeometryColumn('%(schema)s', 'tsearch', 'the_geom', " - "%(srid)s, 'GEOMETRY', 2)" % {"schema": schema, "srid": srid} - ) + op.execute(f"SELECT AddGeometryColumn('{schema}', 'tsearch', 'the_geom', " f"{srid}, 'GEOMETRY', 2)") op.create_index("tsearch_ts_idx", "tsearch", ["ts"], schema=schema, postgresql_using="gin") op.create_table( "treegroup", diff --git a/commons/c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py b/commons/c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py index 777bdf654f..b2ad419528 100644 --- a/commons/c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py +++ b/commons/c2cgeoportal_commons/alembic/main/21f11066f8ec_trigger_on_role_updates_user_in_static.py @@ -71,7 +71,7 @@ def downgrade() -> None: schema = config["schema"] op.execute( - """ + f""" CREATE OR REPLACE FUNCTION {schema}.on_role_name_change() RETURNS trigger AS $$ @@ -82,7 +82,5 @@ def downgrade() -> None: RETURN NEW; END; $$ -LANGUAGE plpgsql""".format( - schema=schema - ) +LANGUAGE plpgsql""" ) diff --git a/commons/c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py b/commons/c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py index c6298f2232..cbacc5cece 100644 --- a/commons/c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py +++ b/commons/c2cgeoportal_commons/alembic/main/56dc90838d90_fix_removing_layerv1.py @@ -50,11 +50,9 @@ def upgrade() -> None: schema = config["schema"] op.execute( - ( - "DELETE from {schema}.layer_restrictionarea WHERE layer_id IN (" - "SELECT id from {schema}.treeitem WHERE type = 'layerv1'" - ");" - ).format(schema=schema) + f"DELETE from {schema}.layer_restrictionarea WHERE layer_id IN (" + f"SELECT id from {schema}.treeitem WHERE type = 'layerv1'" + ");" ) op.execute(f"DELETE from {schema}.treeitem WHERE type = 'layerv1';") diff --git a/commons/c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py b/commons/c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py index 4107a44967..0a8278fe29 100644 --- a/commons/c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py +++ b/commons/c2cgeoportal_commons/alembic/main/7530011a66a7_trigger_on_role_updates_user_in_static.py @@ -71,7 +71,7 @@ def downgrade() -> None: schema = config["schema"] op.execute( - """ + f""" CREATE OR REPLACE FUNCTION {schema}.on_role_name_change() RETURNS trigger AS $$ @@ -82,7 +82,5 @@ def downgrade() -> None: RETURN NEW; END; $$ -LANGUAGE plpgsql""".format( - schema=schema - ) +LANGUAGE plpgsql""" ) diff --git a/commons/c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py b/commons/c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py index 1129d0194a..ed914b6974 100644 --- a/commons/c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py +++ b/commons/c2cgeoportal_commons/alembic/main/7d271f4527cd_add_layer_column_in_layerv1_table.py @@ -53,10 +53,10 @@ def upgrade() -> None: op.add_column("layerv1", Column("layer", Unicode), schema=schema) op.execute( - "UPDATE {schema}.layerv1 AS l1 " + f"UPDATE {schema}.layerv1 AS l1 " "SET layer = name " - "FROM {schema}.treeitem AS ti " - "WHERE l1.id = ti.id".format(schema=schema) + f"FROM {schema}.treeitem AS ti " + "WHERE l1.id = ti.id" ) diff --git a/commons/c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py b/commons/c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py index 143943fe90..de7fef0297 100644 --- a/commons/c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py +++ b/commons/c2cgeoportal_commons/alembic/main/9268a1dffac0_add_trigger_to_be_able_to_correctly_.py @@ -50,7 +50,7 @@ def upgrade() -> None: schema = config["schema"] op.execute( - """ + f""" CREATE FUNCTION {schema}.on_role_name_change() RETURNS trigger AS $$ @@ -61,14 +61,12 @@ def upgrade() -> None: RETURN NEW; END; $$ -LANGUAGE plpgsql""".format( - schema=schema - ) +LANGUAGE plpgsql""" ) op.execute( - "CREATE TRIGGER on_role_name_change AFTER UPDATE ON {schema}.role FOR EACH ROW " - "EXECUTE PROCEDURE {schema}.on_role_name_change()".format(schema=schema) + f"CREATE TRIGGER on_role_name_change AFTER UPDATE ON {schema}.role FOR EACH ROW " + f"EXECUTE PROCEDURE {schema}.on_role_name_change()" ) diff --git a/commons/c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py b/commons/c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py index 50ba1783b8..190e3b3380 100644 --- a/commons/c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py +++ b/commons/c2cgeoportal_commons/alembic/main/eeb345672454_merge_2_4_and_master_branches.py @@ -35,7 +35,6 @@ Create Date: 2019-09-03 09:11:57.786920 """ - # revision identifiers, used by Alembic. revision = "eeb345672454" down_revision = ("78fd093c8393", "56dc90838d90") diff --git a/commons/c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py b/commons/c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py index 68f7f3294a..b21d92d217 100644 --- a/commons/c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py +++ b/commons/c2cgeoportal_commons/alembic/static/1da396a88908_move_user_table_to_static_schema.py @@ -82,20 +82,13 @@ def upgrade() -> None: try: op.execute( - "INSERT INTO %(staticschema)s.user " - "(type, username, password, email, is_password_changed, role_name%(parent_column)s) (" + f"INSERT INTO {staticschema}.user " + f"(type, username, password, email, is_password_changed, role_name{parent_column}) (" "SELECT u.type, u.username, u.password, u.email, " - "u.is_password_changed, r.name%(parent_select)s " - "FROM %(schema)s.user AS u " - "LEFT OUTER JOIN %(schema)s.role AS r ON (r.id = u.role_id) %(parent_join)s" + f"u.is_password_changed, r.name{parent_select} " + f"FROM {schema}.user AS u " + f"LEFT OUTER JOIN {schema}.role AS r ON (r.id = u.role_id) {parent_join}" ")" - % { - "staticschema": staticschema, - "schema": schema, - "parent_select": parent_select, - "parent_column": parent_column, - "parent_join": parent_join, - } ) op.drop_table("user", schema=schema) except Exception: # pylint: disable=broad-exception-caught @@ -135,20 +128,13 @@ def downgrade() -> None: parent_join = f"LEFT OUTER JOIN {parentschema}.role AS pr ON (pr.name = u.parent_role_name)" op.execute( - "INSERT INTO %(schema)s.user " - "(type, username, password, email, is_password_changed, role_id%(parent_column)s) (" + f"INSERT INTO {schema}.user " + f"(type, username, password, email, is_password_changed, role_id{parent_column}) (" "SELECT u.type, u.username, u.password, u.email, " - "u.is_password_changed, r.id%(parent_select)s " - "FROM %(staticschema)s.user AS u " - "LEFT OUTER JOIN %(schema)s.role AS r ON (r.name = u.role_name) %(parent_join)s" + f"u.is_password_changed, r.id{parent_select} " + f"FROM {staticschema}.user AS u " + f"LEFT OUTER JOIN {schema}.role AS r ON (r.name = u.role_name) {parent_join}" ")" - % { - "staticschema": staticschema, - "schema": schema, - "parent_select": parent_select, - "parent_column": parent_column, - "parent_join": parent_join, - } ) op.drop_table("user", schema=staticschema) diff --git a/commons/c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py b/commons/c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py index 99d9d4ee71..1d5917363c 100644 --- a/commons/c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py +++ b/commons/c2cgeoportal_commons/alembic/static/53d671b17b20_add_timezone_on_datetime_fields.py @@ -50,14 +50,12 @@ def upgrade() -> None: staticschema = config["schema_static"] op.execute( - """ + f""" SET TIME ZONE 'UTC'; ALTER TABLE {staticschema}.user ALTER COLUMN last_login TYPE timestamp with time zone; SET TIME ZONE LOCAL; ALTER TABLE {staticschema}.user ALTER COLUMN expire_on TYPE timestamp with time zone; -""".format( - staticschema=staticschema - ) +""" ) @@ -66,12 +64,10 @@ def downgrade() -> None: staticschema = config["schema_static"] op.execute( - """ + f""" SET TIME ZONE 'UTC'; ALTER TABLE {staticschema}.user ALTER COLUMN last_login TYPE timestamp without time zone; SET TIME ZONE LOCAL; ALTER TABLE {staticschema}.user ALTER COLUMN expire_on TYPE timestamp without time zone; -""".format( - staticschema=staticschema - ) +""" ) diff --git a/commons/c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py b/commons/c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py index 59b750d800..5f3625a956 100644 --- a/commons/c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py +++ b/commons/c2cgeoportal_commons/alembic/static/aa41e9613256_wip_add_openid_connect_support.py @@ -35,7 +35,6 @@ Create Date: 2024-08-30 15:56:31.163378 """ - from alembic import op from c2c.template.config import config diff --git a/commons/c2cgeoportal_commons/models/__init__.py b/commons/c2cgeoportal_commons/models/__init__.py index d12489f7f4..275aaefe16 100644 --- a/commons/c2cgeoportal_commons/models/__init__.py +++ b/commons/c2cgeoportal_commons/models/__init__.py @@ -47,7 +47,7 @@ class BaseType(sqlalchemy.ext.declarative.DeclarativeMeta, type): - pass + """Base type for all the models class.""" Base: BaseType = sqlalchemy.orm.declarative_base() diff --git a/commons/c2cgeoportal_commons/models/main.py b/commons/c2cgeoportal_commons/models/main.py index ffc87dd9f4..0e008f48cb 100644 --- a/commons/c2cgeoportal_commons/models/main.py +++ b/commons/c2cgeoportal_commons/models/main.py @@ -56,7 +56,13 @@ from c2cgeoform.ext.colander_ext import Geometry as ColanderGeometry from c2cgeoform.ext.deform_ext import MapWidget, RelationSelect2Widget from colander import drop - from deform.widget import CheckboxWidget, HiddenWidget, SelectWidget, TextAreaWidget, TextInputWidget + from deform.widget import ( + CheckboxWidget, + HiddenWidget, + SelectWidget, + TextAreaWidget, + TextInputWidget, + ) colander_null = colander.null except ModuleNotFoundError: @@ -84,7 +90,6 @@ def __init__(self, *args: Any, **kwargs: Any): def state_str(state: Any) -> str: """Return a string describing an instance via its InstanceState.""" - return "None" if state is None else f"<{state.class_.__name__} {state.obj()}>" # In the original function sqlalchemy use the id of the object that don't allow us to give some useful @@ -308,7 +313,7 @@ def __str__(self) -> str: return f"{self.name}[{self.id}]>" @property - def bounds(self) -> tuple[float, float, float, float] | None: # TODO + def bounds(self) -> tuple[float, float, float, float] | None: if self.extent is None: return None return cast(tuple[float, float, float, float], to_shape(self.extent).bounds) @@ -367,11 +372,7 @@ def is_in_interface(self, name: str) -> bool: if not hasattr(self, "interfaces"): return False - for interface in self.interfaces: - if interface.name == name: - return True - - return False + return any(interface.name == name for interface in self.interfaces) def get_metadata(self, name: str) -> list["Metadata"]: return [metadata for metadata in self.metadatas if metadata.name == name] @@ -1035,7 +1036,7 @@ def __init__( @staticmethod def get_default(dbsession: Session) -> DimensionLayer | None: return cast( - Optional[DimensionLayer], + DimensionLayer | None, dbsession.query(LayerWMS).filter(LayerWMS.name == "wms-defaults").one_or_none(), ) @@ -1179,7 +1180,7 @@ def __init__(self, name: str = "", public: bool = True, image_type: ImageType = @staticmethod def get_default(dbsession: Session) -> DimensionLayer | None: return cast( - Optional[DimensionLayer], + DimensionLayer | None, dbsession.query(LayerWMTS).filter(LayerWMTS.name == "wmts-defaults").one_or_none(), ) @@ -1374,7 +1375,7 @@ def __init__(self, name: str = "", public: bool = True, style: str = "", sql: st @staticmethod def get_default(dbsession: Session) -> DimensionLayer | None: return cast( - Optional[DimensionLayer], + DimensionLayer | None, dbsession.query(LayerVectorTiles) .filter(LayerVectorTiles.name == "vector-tiles-defaults") .one_or_none(), diff --git a/commons/c2cgeoportal_commons/models/static.py b/commons/c2cgeoportal_commons/models/static.py index 0e60d1f80d..3327f93962 100644 --- a/commons/c2cgeoportal_commons/models/static.py +++ b/commons/c2cgeoportal_commons/models/static.py @@ -324,7 +324,7 @@ def set_temp_password(self, password: str) -> None: @staticmethod def __encrypt_password_legacy(password: str) -> str: """Hash the given password with SHA1.""" - return sha1(password.encode("utf8")).hexdigest() # nosec + return sha1(password.encode("utf8")).hexdigest() # noqa: S324 @staticmethod def __encrypt_password(password: str) -> str: diff --git a/commons/c2cgeoportal_commons/testing/__init__.py b/commons/c2cgeoportal_commons/testing/__init__.py index 14a6ff17af..debb5ae292 100644 --- a/commons/c2cgeoportal_commons/testing/__init__.py +++ b/commons/c2cgeoportal_commons/testing/__init__.py @@ -78,11 +78,10 @@ def get_tm_session( def generate_mappers() -> None: """Initialize the model for a Pyramid app.""" - # import or define all models here to ensure they are attached to the # Base.metadata prior to any initialization routines - import c2cgeoportal_commons.models.main # pylint: disable=unused-import,import-outside-toplevel - import c2cgeoportal_commons.models.static # pylint: disable=import-outside-toplevel + import c2cgeoportal_commons.models.main # pylint: disable=unused-import,import-outside-toplevel # noqa: F401 + import c2cgeoportal_commons.models.static # pylint: disable=import-outside-toplevel # noqa: F401 # run configure_mappers after defining all of the models to ensure # all relationships can be setup diff --git a/commons/c2cgeoportal_commons/testing/initializedb.py b/commons/c2cgeoportal_commons/testing/initializedb.py index e3951d17c6..e9167ce9dc 100644 --- a/commons/c2cgeoportal_commons/testing/initializedb.py +++ b/commons/c2cgeoportal_commons/testing/initializedb.py @@ -46,11 +46,15 @@ def usage(argv: list[str]) -> None: def schema_exists(connection: Connection, schema_name: str) -> bool: """Get if the schema exist.""" - sql = f""" -SELECT count(*) AS count -FROM information_schema.schemata -WHERE schema_name = '{schema_name}'; -""" + del schema_name + + sql = " ".join( + [ # noqa: S608 + "SELECT count(*) AS count", + "FROM information_schema.schemata", + "WHERE schema_name = '{schema_name}';", + ] + ) result = connection.execute(sqlalchemy.text(sql)) row = result.first() return cast(bool, row[0] == 1) # type: ignore[index] @@ -64,8 +68,12 @@ def truncate_tables(connection: Connection) -> None: def setup_test_data(dbsession: Session) -> None: """Initialize the testing data.""" - from c2cgeoportal_commons.models.main import Role # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Role, + ) + from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel + User, + ) role_admin = dbsession.merge(Role(name="role_admin")) role_user = dbsession.merge(Role(name="role_user")) diff --git a/doc/integrator/requirements.rst b/doc/integrator/requirements.rst index 7181204e53..3f3cad91c2 100644 --- a/doc/integrator/requirements.rst +++ b/doc/integrator/requirements.rst @@ -11,7 +11,7 @@ components installed on your system: * **Git** * **Docker** >= 17.05 -* **Python** >= 3.8, with ``pip`` +* **Python** >= 3.10, with ``pip`` * **Apache** >= 2.4 (optional, can be used as a front for SSL) * **PostgreSQL** >= 9.1/**PostGIS** >= 2.1 diff --git a/doc/pyproject.toml b/doc/pyproject.toml index 1bb00bb8ba..d894991c3d 100644 --- a/doc/pyproject.toml +++ b/doc/pyproject.toml @@ -1,11 +1,3 @@ -[tool.black] -line-length = 110 -target-version = ['py39'] - -[tool.isort] -profile = "black" -line_length = 110 - [tool.poetry] name = 'c2cgeoportal' version = '0.0.0' diff --git a/docker/config/bin/eval-templates b/docker/config/bin/eval-templates index 3680ed0321..05d9bfa97c 100755 --- a/docker/config/bin/eval-templates +++ b/docker/config/bin/eval-templates @@ -5,7 +5,7 @@ import glob import os import random import re -import subprocess +import subprocess # nosec import urllib.parse import zipfile from typing import cast @@ -115,14 +115,14 @@ def _main() -> None: if os.environ.get("VISIBLE_ENTRY_POINT"): os.environ["VISIBLE_ENTRY_POINT_RE_ESCAPED"] = re.escape(os.environ["VISIBLE_ENTRY_POINT"]) os.environ["MAPSERVER_DATA_SUBSELECT"] = ( - "SELECT {ST_JOIN}(ra.area) " + "SELECT {ST_JOIN}(ra.area) " # nosec "FROM {PGSCHEMA}.restrictionarea AS ra, {PGSCHEMA}.role_restrictionarea AS rra, " "{PGSCHEMA}.layer_restrictionarea AS lra, {PGSCHEMA}.treeitem AS la " "WHERE rra.role_id in (%role_ids%) AND rra.restrictionarea_id = ra.id " "AND lra.restrictionarea_id = ra.id AND lra.layer_id = la.id AND la.name = " ).format(PGSCHEMA=os.environ["PGSCHEMA"], ST_JOIN=os.environ.get("ST_JOIN", "ST_Collect")) os.environ["MAPSERVER_DATA_NOAREA_SUBSELECT"] = ( - "SELECT rra.role_id " + "SELECT rra.role_id " # nosec "FROM {PGSCHEMA}.restrictionarea AS ra, {PGSCHEMA}.role_restrictionarea AS rra, " "{PGSCHEMA}.layer_restrictionarea AS lra, {PGSCHEMA}.treeitem AS la " "WHERE rra.restrictionarea_id = ra.id AND lra.restrictionarea_id = ra.id " diff --git a/docker/qgisserver/.bandit.yaml b/docker/qgisserver/.bandit.yaml deleted file mode 100644 index 1bf9b483e1..0000000000 --- a/docker/qgisserver/.bandit.yaml +++ /dev/null @@ -1,2 +0,0 @@ -skips: - - B101 # Use of assert detected. diff --git a/docker/qgisserver/.prospector.yaml b/docker/qgisserver/.prospector.yaml index 7c072ce7a2..e71bb10da0 100644 --- a/docker/qgisserver/.prospector.yaml +++ b/docker/qgisserver/.prospector.yaml @@ -7,6 +7,6 @@ inherits: pyroma: run: False -bandit: +mypy: options: - config: .bandit.yaml + python_version: '3.10' diff --git a/docker/qgisserver/poetry.lock b/docker/qgisserver/poetry.lock index 9d04896bb6..d8fc2352ac 100644 --- a/docker/qgisserver/poetry.lock +++ b/docker/qgisserver/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "astroid" @@ -1007,6 +1007,7 @@ pylint-django = ">=2.6.1" pylint-flask = "0.6" PyYAML = "*" requirements-detector = ">=1.3.2" +ruff = {version = "*", optional = true, markers = "extra == \"with-ruff\" or extra == \"with_everything\""} setoptconf-tmp = ">=0.3.1,<0.4.0" toml = ">=0.10.2,<0.11.0" @@ -1416,6 +1417,33 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.8.1" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, + {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, + {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, + {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, + {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, + {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, + {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, +] + [[package]] name = "semver" version = "3.0.2" @@ -2015,4 +2043,4 @@ test = ["zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "b0baac3eb7b2e7b3ba939035e02fca84001764b6ce40d0a0bf0cc1fd4e442e85" +content-hash = "c8b2c78df70ab71817b89f0a09a672456017e1598eff4d86fbeeb41dd1dfcfca" diff --git a/docker/qgisserver/pyproject.toml b/docker/qgisserver/pyproject.toml index f213313f26..5c9851cc62 100644 --- a/docker/qgisserver/pyproject.toml +++ b/docker/qgisserver/pyproject.toml @@ -1,20 +1,5 @@ -[tool.mypy] -python_version = 3.10 -warn_redundant_casts = true -warn_unused_ignores = true -check_untyped_defs = true -skip_version_check = true -ignore_missing_imports = true - -[tool.black] -line-length = 110 -target-version = ['py310'] - -[tool.isort] -profile = "black" -line_length = 110 -known_third_party = "c2cwsgiutils,c2cgeoform,qgis,pytest" -known_first_party = ["geomapfish_qgisserver", "c2cgeoportal_commons"] +[tool.ruff] +target-version = 'py310' [tool.poetry] name = 'c2cgeoportal' @@ -38,7 +23,7 @@ psycopg2 = "2.9.10" pytz = "2024.2" [tool.poetry.dev-dependencies] -prospector = { extras = ["with_mypy", "with_bandit"], version = "1.13.3" } +prospector = { version = "1.13.3", extras = ["with_mypy", "with_bandit", "with_ruff"] } prospector-profile-duplicated = "1.8.0" prospector-profile-utils = "1.13.0" types-pyyaml = "6.0.12.20240917" diff --git a/geoportal/c2cgeoportal_geoportal/__init__.py b/geoportal/c2cgeoportal_geoportal/__init__.py index 2e38add0fe..857f3c3c30 100644 --- a/geoportal/c2cgeoportal_geoportal/__init__.py +++ b/geoportal/c2cgeoportal_geoportal/__init__.py @@ -37,6 +37,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast import c2cgeoform +import c2cgeoportal_commons.models import c2cwsgiutils import c2cwsgiutils.db import c2cwsgiutils.index @@ -50,6 +51,7 @@ import sqlalchemy.orm import zope.event.classhandler from c2cgeoform import translator +from c2cgeoportal_commons.models import InvalidateCacheEvent from c2cwsgiutils.health_check import HealthCheck from c2cwsgiutils.prometheus import MemoryMapCollector from deform import Form @@ -62,10 +64,14 @@ from pyramid_mako import add_mako_renderer from sqlalchemy.orm import joinedload -import c2cgeoportal_commons.models import c2cgeoportal_geoportal.views -from c2cgeoportal_commons.models import InvalidateCacheEvent -from c2cgeoportal_geoportal.lib import C2CPregenerator, caching, check_collector, checker, oidc +from c2cgeoportal_geoportal.lib import ( + C2CPregenerator, + caching, + check_collector, + checker, + oidc, +) from c2cgeoportal_geoportal.lib.cacheversion import version_cache_buster from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers @@ -79,7 +85,9 @@ from c2cgeoportal_geoportal.views.entry import Entry, canvas_view, custom_view if TYPE_CHECKING: - from c2cgeoportal_commons.models import static # pylint: disable=ungrouped-imports,useless-suppression + from c2cgeoportal_commons.models import ( + static, # pylint: disable=ungrouped-imports,useless-suppression + ) _LOG = logging.getLogger(__name__) @@ -174,7 +182,6 @@ def add_interface_ngeo( permission: str | None = None, ) -> None: """Add the ngeo interfaces views and routes.""" - config.add_route(route_name, route, request_method="GET") # Permalink theme: recover the theme for generating custom viewer.js url config.add_route( @@ -202,7 +209,6 @@ def add_interface_canvas( permission: str | None = None, ) -> None: """Add the ngeo interfaces views and routes.""" - renderer = f"/etc/geomapfish/interfaces/{route_name}.html.mako" config.add_route(route_name, route, request_method="GET") # Permalink theme: recover the theme for generating custom viewer.js URL @@ -235,7 +241,6 @@ def add_interface_custom( permission: str | None = None, ) -> None: """Add custom interfaces views and routes.""" - config.add_route(route_name, route, request_method="GET") # Permalink theme: recover the theme for generating custom viewer.js URL config.add_route( @@ -259,7 +264,6 @@ def add_interface_custom( def add_admin_interface(config: pyramid.config.Configurator) -> None: """Add the administration interface views and routes.""" - assert c2cgeoportal_commons.models.DBSession is not None config.add_request_method( @@ -318,7 +322,6 @@ def is_allowed_url( Allowed if URL netloc is request host or is found in allowed hosts. """ - url_netloc = urllib.parse.urlparse(url).netloc return url_netloc, url_netloc == request.host or url_netloc in _get_netlocs(allowed_hosts) @@ -329,7 +332,6 @@ def is_allowed_host(request: pyramid.request.Request) -> bool: Allowed if URL netloc is request host or is found in allowed hosts. """ - return request.host in request.registry.settings.get("allowed_hosts", []) @@ -354,7 +356,7 @@ def is_valid_referrer(request: pyramid.request.Request, settings: dict[str, Any] def create_get_user_from_request( - settings: dict[str, Any] + settings: dict[str, Any], ) -> Callable[[pyramid.request.Request, str | None], Optional["static.User"]]: """Get the get_user_from_request function.""" @@ -370,8 +372,12 @@ def get_user_from_request( * it has been deactivated * the referrer is invalid """ - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel + User, + ) assert DBSession is not None @@ -467,8 +473,12 @@ def default_user_validator(request: pyramid.request.Request, username: str, pass otherwise. """ del request # unused - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel + User, + ) assert DBSession is not None @@ -930,7 +940,9 @@ def init_db_sessions( ) c2cgeoportal_commons.models.Base.metadata.clear() - from c2cgeoportal_commons.models import main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + main, + ) if health_check is not None: for name, session in c2cgeoportal_commons.models.DBSessions.items(): diff --git a/geoportal/c2cgeoportal_geoportal/lib/__init__.py b/geoportal/c2cgeoportal_geoportal/lib/__init__.py index 3590649165..b531a7dbee 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/__init__.py +++ b/geoportal/c2cgeoportal_geoportal/lib/__init__.py @@ -38,10 +38,10 @@ import dateutil import pyramid.request import pyramid.response +from c2cgeoportal_commons.lib.url import get_url2 from pyramid.interfaces import IRoutePregenerator from zope.interface import implementer -from c2cgeoportal_commons.lib.url import get_url2 from c2cgeoportal_geoportal.lib.cacheversion import get_cache_version from c2cgeoportal_geoportal.lib.caching import get_region @@ -145,8 +145,12 @@ def get_setting(settings: Any, path: Iterable[str], default: Any = None) -> Any: @_CACHE_REGION_OBJ.cache_on_arguments() def get_ogc_server_wms_url_ids(request: pyramid.request.Request, host: str) -> dict[str, list[int]]: """Get the OGCServer ids mapped on the WMS URL.""" - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + OGCServer, + ) del host # used for cache assert DBSession is not None @@ -163,8 +167,12 @@ def get_ogc_server_wms_url_ids(request: pyramid.request.Request, host: str) -> d @_CACHE_REGION_OBJ.cache_on_arguments() def get_ogc_server_wfs_url_ids(request: pyramid.request.Request, host: str) -> dict[str, list[int]]: """Get the OGCServer ids mapped on the WFS URL.""" - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + OGCServer, + ) del host # used for cache assert DBSession is not None @@ -218,7 +226,10 @@ def _get_intranet_networks( @_CACHE_REGION.cache_on_arguments() def get_role_id(name: str) -> int: """Get the role ID.""" - from c2cgeoportal_commons.models import DBSession, main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + main, + ) assert DBSession is not None @@ -250,7 +261,4 @@ def get_roles_name(request: pyramid.request.Request) -> pyramid.response.Respons def is_intranet(request: pyramid.request.Request) -> bool: """Get if it's an intranet user.""" address = ipaddress.ip_address(request.client_addr) - for network in _get_intranet_networks(request): - if address in network: - return True - return False + return any(address in network for network in _get_intranet_networks(request)) diff --git a/geoportal/c2cgeoportal_geoportal/lib/authentication.py b/geoportal/c2cgeoportal_geoportal/lib/authentication.py index b8feb9dd2e..e311524f3a 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/authentication.py +++ b/geoportal/c2cgeoportal_geoportal/lib/authentication.py @@ -67,7 +67,7 @@ def __init__( self.debug = debug def unauthenticated_userid(self, request: pyramid.request.Request) -> str | None: - if not request.method == "GET" or "auth" not in request.params: + if request.method != "GET" or "auth" not in request.params: return None auth_enc = request.params.get("auth") if auth_enc is None: diff --git a/geoportal/c2cgeoportal_geoportal/lib/caching.py b/geoportal/c2cgeoportal_geoportal/lib/caching.py index 826c5ef254..f887b7c728 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/caching.py +++ b/geoportal/c2cgeoportal_geoportal/lib/caching.py @@ -33,6 +33,7 @@ import pyramid.interfaces import zope.interface +from c2cgeoportal_commons.models import Base from dogpile.cache.api import NO_VALUE, CacheBackend, CachedValue, NoValue from dogpile.cache.backends.memory import MemoryBackend from dogpile.cache.backends.redis import RedisBackend, RedisSentinelBackend @@ -40,8 +41,6 @@ from dogpile.cache.util import sha1_mangle_key from sqlalchemy.orm.util import identity_key -from c2cgeoportal_commons.models import Base - if TYPE_CHECKING: from dogpile.cache.api import SerializedReturnType else: @@ -65,7 +64,6 @@ def keygen_function(namespace: Any, function: Callable[..., Any]) -> Callable[.. This is used by :meth:`.CacheRegion.cache_on_arguments` to generate a cache key from a decorated function. """ - if namespace is None: namespace = (function.__module__, function.__name__) else: @@ -140,7 +138,7 @@ def get(self, key: str) -> CachedValue | bytes | NoValue: assert isinstance(val, bytes) value = self._redis.deserializer(val) # type: ignore[misc] if value != NO_VALUE and self._use_memory_cache: - assert isinstance(value, (CachedValue, bytes)) + assert isinstance(value, CachedValue | bytes) self._memory.set(key, value) return value diff --git a/geoportal/c2cgeoportal_geoportal/lib/checker.py b/geoportal/c2cgeoportal_geoportal/lib/checker.py index 710ba551ae..9b9abdd5a0 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/checker.py +++ b/geoportal/c2cgeoportal_geoportal/lib/checker.py @@ -147,8 +147,8 @@ def check(_request: pyramid.request.Request, response: pyramid.response.Response health_check.add_url_check( name="checker_fulltextsearch", - url=lambda r: get_both(r)["url"], # type: ignore - headers=lambda r: get_both(r)["headers"], # type: ignore + url=lambda r: get_both(r)["url"], # type: ignore[misc] + headers=lambda r: get_both(r)["headers"], # type: ignore[misc] params={"query": fts_settings["search"], "limit": "1"}, check_cb=check, level=fts_settings["level"], @@ -156,8 +156,12 @@ def check(_request: pyramid.request.Request, response: pyramid.response.Response def _themes_errors(settings: dict[str, Any], health_check: c2cwsgiutils.health_check.HealthCheck) -> None: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import Interface # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Interface, + ) assert DBSession is not None @@ -256,7 +260,7 @@ def __call__(self, request: pyramid.request.Request) -> None: cmd: list[str] = ["check-example", url] env = dict(os.environ) for name, value in self.route.get("environment", {}).items(): - if isinstance(value, (list, dict)): + if isinstance(value, list | dict): value = json.dumps(value) elif not isinstance(value, str): value = str(value) diff --git a/geoportal/c2cgeoportal_geoportal/lib/common_headers.py b/geoportal/c2cgeoportal_geoportal/lib/common_headers.py index c80a59c271..3d276abfcc 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/common_headers.py +++ b/geoportal/c2cgeoportal_geoportal/lib/common_headers.py @@ -123,7 +123,6 @@ def _set_common_headers( content_type: str | None, ) -> pyramid.response.Response: """Set the common headers.""" - response.headers.update(service_headers_settings.get("headers", {})) if cache in (Cache.PRIVATE, Cache.PRIVATE_NO): diff --git a/geoportal/c2cgeoportal_geoportal/lib/dbreflection.py b/geoportal/c2cgeoportal_geoportal/lib/dbreflection.py index 6fcaaaa8b5..feb0c0b989 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/dbreflection.py +++ b/geoportal/c2cgeoportal_geoportal/lib/dbreflection.py @@ -76,7 +76,9 @@ def __get__( return getattr(target, self.value_attr) if target else None def __set__(self, obj: str, val: str) -> None: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) assert DBSession is not None @@ -118,8 +120,10 @@ def get_table( engine = session.bind.engine metadata = MetaData() else: - from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + Base, + DBSession, + ) assert DBSession is not None assert DBSession.bind is not None @@ -162,7 +166,6 @@ def get_class( valid string. If there is no table identified by tablename in the database a NoSuchTableError SQLAlchemy exception is raised. """ - tablename, schema = _get_schema(tablename) table = get_table(tablename, schema, None, primary_key=primary_key) @@ -187,7 +190,9 @@ def _create_class( readonly_attributes: list[str] | None = None, pk_name: str | None = None, ) -> type: - from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + Base, + ) exclude_properties = exclude_properties or () attributes = { @@ -201,7 +206,7 @@ def _create_class( # The randint is to fix the SAWarning: This declarative base already contains a class with the same # class name and module name cls = type( - f"{table.name.capitalize()}_{random.randint(0, 9999999)}", # nosec + f"{table.name.capitalize()}_{random.randint(0, 9999999)}", # noqa: S311 (GeoInterface, Base), attributes, ) diff --git a/geoportal/c2cgeoportal_geoportal/lib/filter_capabilities.py b/geoportal/c2cgeoportal_geoportal/lib/filter_capabilities.py index c1f2f566b8..ea5946a499 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/filter_capabilities.py +++ b/geoportal/c2cgeoportal_geoportal/lib/filter_capabilities.py @@ -32,25 +32,33 @@ import xml.sax.xmlreader # nosec from collections.abc import Callable from io import StringIO -from typing import Any, Union +from typing import Any from xml.sax.saxutils import XMLFilterBase, XMLGenerator # nosec import defusedxml.expatreader import pyramid.httpexceptions import pyramid.request import requests +from c2cgeoportal_commons.lib.url import Url from owslib.map.wms111 import ContentMetadata as ContentMetadata111 from owslib.map.wms130 import ContentMetadata as ContentMetadata130 from owslib.wms import WebMapService from pyramid.httpexceptions import HTTPBadGateway -from c2cgeoportal_commons.lib.url import Url -from c2cgeoportal_geoportal.lib import caching, get_ogc_server_wfs_url_ids, get_ogc_server_wms_url_ids -from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers, get_writable_layers +from c2cgeoportal_geoportal.lib import ( + caching, + get_ogc_server_wfs_url_ids, + get_ogc_server_wms_url_ids, +) +from c2cgeoportal_geoportal.lib.layers import ( + get_private_layers, + get_protected_layers, + get_writable_layers, +) _CACHE_REGION = caching.get_region("std") _LOG = logging.getLogger(__name__) -ContentMetadata = Union[ContentMetadata111, ContentMetadata130] +ContentMetadata = ContentMetadata111 | ContentMetadata130 @_CACHE_REGION.cache_on_arguments() @@ -111,7 +119,6 @@ def filter_capabilities( request: pyramid.request.Request, content: str, wms: bool, url: Url, headers: dict[str, str] ) -> str: """Filter the WMS/WFS capabilities.""" - wms_structure_ = wms_structure(request, url, headers.get("Host")) ogc_server_ids = ( @@ -153,7 +160,6 @@ def filter_capabilities( def filter_wfst_capabilities(content: str, wfs_url: Url, request: pyramid.request.Request) -> str: """Filter the WTS capabilities.""" - writable_layers: set[str] = set() ogc_server_ids = get_ogc_server_wfs_url_ids(request, request.host).get(wfs_url.url()) if ogc_server_ids is None: @@ -312,7 +318,7 @@ def _keep_layer(self, layer_name: str) -> bool: ) def characters(self, content: str) -> None: - if self.in_name and self.layers_path and not self.layers_path[-1].self_hidden is True: + if self.in_name and self.layers_path and self.layers_path[-1].self_hidden is not True: layer_name = normalize_typename(content) if self._keep_layer(layer_name): for layer in self.layers_path: @@ -342,9 +348,8 @@ def normalize_tag(tag: str) -> str: e.g. '{https://....}TypeName' -> 'TypeName' """ normalized = tag - if len(tag) >= 3: - if tag[0] == "{": - normalized = tag[1:].split("}")[1] + if len(tag) >= 3 and tag[0] == "{": + normalized = tag[1:].split("}")[1] return normalized.lower() diff --git a/geoportal/c2cgeoportal_geoportal/lib/functionality.py b/geoportal/c2cgeoportal_geoportal/lib/functionality.py index bcc2179f94..74563b3ae1 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/functionality.py +++ b/geoportal/c2cgeoportal_geoportal/lib/functionality.py @@ -30,9 +30,9 @@ from typing import Any, cast import pyramid.request +from c2cgeoportal_commons.models import main, static from sqlalchemy.orm import joinedload -from c2cgeoportal_commons.models import main, static from c2cgeoportal_geoportal.lib import get_typed, get_types_map, is_intranet from c2cgeoportal_geoportal.lib.caching import get_region @@ -43,7 +43,9 @@ @_CACHE_REGION_OBJ.cache_on_arguments() def _get_role(name: str) -> dict[str, Any]: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) assert DBSession is not None diff --git a/geoportal/c2cgeoportal_geoportal/lib/layers.py b/geoportal/c2cgeoportal_geoportal/lib/layers.py index 99fdb22165..115a44fb79 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/layers.py +++ b/geoportal/c2cgeoportal_geoportal/lib/layers.py @@ -42,7 +42,10 @@ def _get_layers_query(request: Request, what: sqlalchemy.orm.Mapper[Any] | type[Any]) -> Query[Any]: - from c2cgeoportal_commons.models import DBSession, main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + main, + ) assert DBSession is not None @@ -64,7 +67,9 @@ def get_protected_layers_query( Private layers but accessible to the user. """ - from c2cgeoportal_commons.models import main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + main, + ) q = _get_layers_query(request, what) q = q.filter(main.Layer.public.is_(False)) @@ -76,7 +81,9 @@ def get_protected_layers_query( def get_writable_layers_query(request: Request, ogc_server_ids: Iterable[int]) -> Query["main.LayerWMS"]: """Get the writable layers query.""" - from c2cgeoportal_commons.models import main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + main, + ) q = _get_layers_query(request, main.LayerWMS) return ( @@ -92,7 +99,10 @@ def get_protected_layers(request: Request, ogc_server_ids: Iterable[int]) -> dic Private layers but accessible to the user. """ - from c2cgeoportal_commons.models import DBSession, main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + main, + ) assert DBSession is not None @@ -104,7 +114,9 @@ def get_protected_layers(request: Request, ogc_server_ids: Iterable[int]) -> dic def get_writable_layers(request: Request, ogc_server_ids: Iterable[int]) -> dict[int, "main.LayerWMS"]: """Get the writable layers.""" - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) assert DBSession is not None @@ -117,7 +129,10 @@ def get_writable_layers(request: Request, ogc_server_ids: Iterable[int]) -> dict @CACHE_REGION.cache_on_arguments() def get_private_layers(ogc_server_ids: Iterable[int]) -> dict[int, "main.LayerWMS"]: """Get the private layers.""" - from c2cgeoportal_commons.models import DBSession, main # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + main, + ) assert DBSession is not None diff --git a/geoportal/c2cgeoportal_geoportal/lib/lingva_extractor.py b/geoportal/c2cgeoportal_geoportal/lib/lingva_extractor.py index 253606f9fa..59a72a6210 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/lingva_extractor.py +++ b/geoportal/c2cgeoportal_geoportal/lib/lingva_extractor.py @@ -29,10 +29,10 @@ import json import os import re -import subprocess +import subprocess # nosec import traceback from collections.abc import Callable -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import TYPE_CHECKING, Any, cast from xml.dom import Node from xml.parsers.expat import ExpatError @@ -42,6 +42,7 @@ import yaml from bottle import MakoTemplate, template from c2c.template.config import config +from c2cgeoportal_commons.lib.url import Url, get_url2 from defusedxml.minidom import parseString from geoalchemy2.types import Geometry from lingva.extractors import Extractor, Message @@ -54,13 +55,14 @@ from sqlalchemy.orm.util import class_mapper import c2cgeoportal_geoportal -from c2cgeoportal_commons.lib.url import Url, get_url2 from c2cgeoportal_geoportal.lib.bashcolor import Color, colorize from c2cgeoportal_geoportal.lib.caching import init_region from c2cgeoportal_geoportal.views.layers import Layers, get_layer_class if TYPE_CHECKING: - from c2cgeoportal_commons.models import main # pylint: disable=ungrouped-imports,useless-suppression + from c2cgeoportal_commons.models import ( + main, # pylint: disable=ungrouped-imports,useless-suppression + ) class LinguaExtractorException(Exception): @@ -76,7 +78,7 @@ def _get_config(key: str, default: str | None = None) -> str | None: """ request = pyramid.threadlocal.get_current_request() if request is not None: - return cast(Optional[str], request.params.get(key.lower(), default)) + return cast(str | None, request.params.get(key.lower(), default)) return os.environ.get(key, default) @@ -174,23 +176,23 @@ def __call__( int_filename = filename if re.match("^" + re.escape(f"./{self.config['package']}/templates"), filename): try: - empty_template = Template("") # nosec + empty_template = Template("") # noqa: S702 - class Lookup(TemplateLookup): # type: ignore + class Lookup(TemplateLookup): # type: ignore[misc] def get_template(self, uri: str) -> Template: del uri # unused return empty_template - class MyTemplate(MakoTemplate): # type: ignore + class MyTemplate(MakoTemplate): # type: ignore[misc] tpl = None def prepare(self, **kwargs: Any) -> None: options.update({"input_encoding": self.encoding}) lookup = Lookup(**kwargs) if self.source: - self.tpl = Template(self.source, lookup=lookup, **kwargs) # nosec + self.tpl = Template(self.source, lookup=lookup, **kwargs) # noqa: S702 else: - self.tpl = Template( # nosec + self.tpl = Template( # noqa: S702 uri=self.name, filename=self.filename, lookup=lookup, **kwargs ) @@ -252,10 +254,13 @@ def init_db(settings: dict[str, Any]) -> None: First test the connection, on when environment it should be OK, with the command line we should get an exception ind initialize the connection. """ - try: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import Theme # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Theme, + ) assert DBSession is not None @@ -302,7 +307,7 @@ def __call__( init_region({"backend": "dogpile.cache.memory"}, "obj") with open(filename, encoding="utf8") as config_file: - gmf_config = yaml.load(config_file, Loader=yaml.BaseLoader) # nosec + gmf_config = yaml.load(config_file, Loader=yaml.BaseLoader) # noqa: S506 # For application config (config.yaml) if "vars" in gmf_config: return self._collect_app_config(filename) @@ -332,7 +337,9 @@ def _collect_app_config(self, filename: str) -> list[Message]: DBSession, DBSessions, ) - from c2cgeoportal_commons.models.main import Metadata # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Metadata, + ) assert DBSession is not None @@ -385,7 +392,6 @@ def _collect_app_config(self, filename: str) -> list[Message]: interface_config.get("constants", {}) .get("gmfDisplayQueryGridOptions", {}) .get("mergeTabs", {}) - .keys() ): location = ( f"interfaces_config/{interface}/constants/gmfDisplayQueryGridOptions/" @@ -479,7 +485,9 @@ def __call__( try: init_db(self.config) - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) assert DBSession is not None @@ -588,8 +596,12 @@ def _import( has_interfaces: bool = True, name_regex: str = ".*", ) -> None: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import Interface # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Interface, + ) assert DBSession is not None @@ -667,8 +679,12 @@ def _import_layer_wms(self, layer: "main.Layer", messages: list[str]) -> None: raise def _import_layer_wmts(self, layer: "main.Layer", messages: list[str]) -> None: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + OGCServer, + ) assert DBSession is not None diff --git a/geoportal/c2cgeoportal_geoportal/lib/oauth2.py b/geoportal/c2cgeoportal_geoportal/lib/oauth2.py index a43daed74f..9becfd4aa2 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/oauth2.py +++ b/geoportal/c2cgeoportal_geoportal/lib/oauth2.py @@ -30,12 +30,12 @@ from typing import Any, TypedDict import basicauth +import c2cgeoportal_commons import oauthlib.common import oauthlib.oauth2 import pyramid.threadlocal from pyramid.httpexceptions import HTTPBadRequest -import c2cgeoportal_commons from c2cgeoportal_geoportal.lib.caching import get_region _LOG = logging.getLogger(__name__) @@ -73,9 +73,11 @@ def authenticate_client( both body and query can be obtained by direct attribute access, i.e. request.client_id for client_id in the URL query. - Keyword Arguments: - - request: oauthlib.common.Request + Arguments: + --------- + request: oauthlib.common.Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: True or False @@ -86,6 +88,7 @@ def authenticate_client( - Refresh Token Grant .. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1 + """ del args, kwargs @@ -117,7 +120,10 @@ def authenticate_client_id( _LOG.debug("authenticate_client_id %s", client_id) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -163,9 +169,11 @@ def client_authentication_required( client credentials or whenever Client provided client authentication, see `Section 6`_ - Keyword Arguments: - - request: oauthlib.common.Request + Arguments: + --------- + request: oauthlib.common.Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: True or False @@ -177,6 +185,7 @@ def client_authentication_required( .. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2 .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3 .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6 + """ del request, args, kwargs @@ -205,24 +214,30 @@ def confirm_redirect_uri( the client's allowed redirect URIs, but against the URI used when the code was saved. - Keyword Arguments: - - client_id: Unicode client identifier - code: Unicode authorization_code. - redirect_uri: Unicode absolute URI - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + code: Unicode authorization_code. + redirect_uri: Unicode absolute URI + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: True or False Method is used by: - Authorization Code Grant (during token request) + """ del args, kwargs _LOG.debug("confirm_redirect_uri %s %s", client_id, redirect_uri) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -248,16 +263,19 @@ def get_default_redirect_uri( """ Get the default redirect URI for the client. - Keyword Arguments: - - client_id: Unicode client identifier - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: The default redirect URI for the client Method is used by: - Authorization Code Grant - Implicit Grant + """ del request, args, kwargs @@ -275,10 +293,12 @@ def get_default_scopes( """ Get the default scopes for the client. - Keyword Arguments: - - client_id: Unicode client identifier - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: List of default scopes @@ -287,6 +307,7 @@ def get_default_scopes( - Implicit Grant - Resource Owner Password Credentials Grant - Client Credentials grant + """ del request, args, kwargs @@ -304,15 +325,18 @@ def get_original_scopes( """ Get the list of scopes associated with the refresh token. - Keyword Arguments: - - refresh_token: Unicode refresh token - request: The HTTP Request + Arguments: + --------- + refresh_token: Unicode refresh token + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: List of scopes. Method is used by: - Refresh token grant + """ del refresh_token, request, args, kwargs @@ -352,17 +376,20 @@ def introspect_token( efficiency, but must fallback to other types to be compliant with RFC. The dict of claims is added to request.token after this method. - Keyword Arguments: - - token: The token string. - token_type_hint: access_token or refresh_token. - request: OAuthlib request. + Arguments: + --------- + token: The token string. + token_type_hint: access_token or refresh_token. + request: OAuthlib request. + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Introspect Endpoint (all grants are compatible) .. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2 .. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4 + """ del token, request, args, kwargs @@ -381,20 +408,26 @@ def invalidate_authorization_code( """ Invalidate an authorization code after use. - Keyword Arguments: - - client_id: Unicode client identifier - code: The authorization code grant (request.code). - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + code: The authorization code grant (request.code). + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant + """ del args, kwargs _LOG.debug("invalidate_authorization_code %s", client_id) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -426,14 +459,17 @@ def is_within_original_scope( used in situations where returning all valid scopes from the get_original_scopes is not practical. - Keyword Arguments: - - request_scopes: A list of scopes that were requested by client - refresh_token: Unicode refresh_token - request: The HTTP Request + Arguments: + --------- + request_scopes: A list of scopes that were requested by client + refresh_token: Unicode refresh_token + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Refresh token grant + """ del request, args, kwargs @@ -452,14 +488,17 @@ def revoke_token( """ Revoke an access or refresh token. - Keyword Arguments: - - token: The token string. - token_type_hint: access_token or refresh_token. - request: The HTTP Request + Arguments: + --------- + token: The token string. + token_type_hint: access_token or refresh_token. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Revocation Endpoint + """ del token, request, args, kwargs @@ -475,12 +514,13 @@ def rotate_refresh_token(self, request: oauthlib.common.Request) -> bool: or replaced with a new one (rotated). Return True to rotate and and False for keeping original. - Keyword Arguments: - - request: oauthlib.common.Request + Arguments: + --------- + request: oauthlib.common.Request Method is used by: - Refresh Token Grant + """ del request @@ -515,11 +555,13 @@ def save_authorization_code( chose to send one. That value should be saved and used in 'validate_code'. - Keyword Arguments: - - client_id: Unicode client identifier - code: A dict of the authorization code grant and, optionally, state. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + code: A dict of the authorization code grant and, optionally, state. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant @@ -528,12 +570,16 @@ def save_authorization_code( Code Challenge (request.code_challenge) and Code Challenge Method (request.code_challenge_method) + """ del args, kwargs _LOG.debug("save_authorization_code %s", client_id) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -604,11 +650,13 @@ def save_bearer_token( Note that while "scope" is a string-separated list of authorized scopes, the original list is still available in request.scopes - Keyword Arguments: - - client_id: Unicode client identifier - token: A Bearer token dict - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + token: A Bearer token dict + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Returns: The default redirect URI for the client @@ -617,12 +665,16 @@ def save_bearer_token( - Implicit Grant - Resource Owner Password Credentials Grant (might not associate a client) - Client Credentials grant + """ del args, kwargs _LOG.debug("save_bearer_token") - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -654,11 +706,11 @@ def validate_bearer_token( """ Ensure the Bearer token is valid and authorized access to scopes. - Keyword Arguments: - - token: A string of random characters. - scopes: A list of scopes associated with the protected resource. - request: The HTTP Request + Arguments: + --------- + token: A string of random characters. + scopes: A list of scopes associated with the protected resource. + request: The HTTP Request A key to OAuth 2 security and restricting impact of leaked tokens is the short expiration time of tokens, *always ensure the token has not @@ -690,22 +742,25 @@ def validate_bearer_token( one provided for django these attributes will be made available in all protected views as keyword arguments. - Keyword Arguments: - - token: Unicode Bearer token - scopes: List of scopes (defined by you) - request: The HTTP Request + Arguments: + --------- + token: Unicode Bearer token + scopes: List of scopes (defined by you) + request: The HTTP Request Method is indirectly used by all core Bearer token issuing grant types: - Authorization Code Grant - Implicit Grant - Resource Owner Password Credentials Grant - Client Credentials Grant - """ + """ _LOG.debug("validate_bearer_token %s", scopes) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -736,20 +791,26 @@ def validate_client_id( to set request.client to the client object associated with the given client_id. - Keyword Arguments: - - client_id: Unicode client identifier - request: oauthlib.common.Request + Arguments: + --------- + client_id: Unicode client identifier + request: oauthlib.common.Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant - Implicit Grant + """ del args, kwargs _LOG.debug("validate_client_id") - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -784,21 +845,27 @@ def validate_code( associated with this authorization code. Similarly request.scopes must also be set. - Keyword Arguments: - - client_id: Unicode client identifier - code: Unicode authorization code - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + code: Unicode authorization code + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant + """ del args, kwargs _LOG.debug("validate_code %s", client_id) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -839,18 +906,21 @@ def validate_grant_type( """ Ensure client is authorized to use the grant_type requested. - Keyword Arguments: - - client_id: Unicode client identifier - grant_type: Unicode grant type, i.e. authorization_code, password. - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + grant_type: Unicode grant type, i.e. authorization_code, password. + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant - Resource Owner Password Credentials Grant - Client Credentials Grant - Refresh Token Grant + """ del client, request, args, kwargs @@ -877,21 +947,27 @@ def validate_redirect_uri( All clients should register the absolute URIs of all URIs they intend to redirect to. The registration is outside of the scope of oauthlib. - Keyword Arguments: - - client_id: Unicode client identifier - redirect_uri: Unicode absolute URI - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + redirect_uri: Unicode absolute URI + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant - Implicit Grant + """ del request, args, kwargs _LOG.debug("validate_redirect_uri %s %s", client_id, redirect_uri) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -918,22 +994,28 @@ def validate_refresh_token( OBS! The request.user attribute should be set to the resource owner associated with this refresh token. - Keyword Arguments: - - refresh_token: Unicode refresh token - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + refresh_token: Unicode refresh token + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant (indirectly by issuing refresh tokens) - Resource Owner Password Credentials Grant (also indirectly) - Refresh Token Grant + """ del args, kwargs _LOG.debug("validate_refresh_token %s", client.client_id if client else None) - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -961,16 +1043,19 @@ def validate_response_type( """ Ensure client is authorized to use the response_type requested. - Keyword Arguments: - - client_id: Unicode client identifier - response_type: Unicode response type, i.e. code, token. - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + response_type: Unicode response type, i.e. code, token. + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Authorization Code Grant - Implicit Grant + """ del client, request, args, kwargs @@ -990,18 +1075,21 @@ def validate_scopes( """ Ensure the client is authorized access to requested scopes. - Keyword Arguments: - - client_id: Unicode client identifier - scopes: List of scopes (defined by you) - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Arguments: + --------- + client_id: Unicode client identifier + scopes: List of scopes (defined by you) + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by all core grant types: - Authorization Code Grant - Implicit Grant - Resource Owner Password Credentials Grant - Client Credentials Grant + """ del client, request, args, kwargs @@ -1026,15 +1114,18 @@ def validate_user( not set you will be unable to associate a token with a user in the persistence method used (commonly, save_bearer_token). - Keyword Arguments: - - username: Unicode username - password: Unicode password - client: Client object set by you, see authenticate_client. - request: The HTTP Request + Argument: + --------- + username: Unicode username + password: Unicode password + client: Client object set by you, see authenticate_client. + request: The HTTP Request + args: Additional arguments, ignored + kwargs: Additional keyword arguments, ignored Method is used by: - Resource Owner Password Credentials Grant + """ del password, client, request, args, kwargs @@ -1056,10 +1147,10 @@ def is_pkce_required(self, client_id: int, request: oauthlib.common.Request) -> a technique to mitigate against the threat through the use of Proof Key for Code Exchange (PKCE, pronounced “pixy”). See RFC7636. - Keyword Arguments: - - client_id: Client identifier. - request (oauthlib.common.Request): OAuthlib request. + Arguments: + --------- + client_id: Client identifier. + request (oauthlib.common.Request): OAuthlib request. Method is used by: @@ -1068,7 +1159,10 @@ def is_pkce_required(self, client_id: int, request: oauthlib.common.Request) -> """ - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -1096,20 +1190,23 @@ def get_code_challenge(self, code: str, request: oauthlib.common.Request) -> str Return the code_challenge associated to the code. If None is returned, code is considered to not be associated to any challenges. - Keyword Arguments: - - code: Authorization code. - request: OAuthlib request. + Arguments: + --------- + code: Authorization code. + request: OAuthlib request. Return: - code_challenge string Method is used by: Authorization Code Grant - when PKCE is active + """ - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None @@ -1136,10 +1233,10 @@ def get_code_challenge_method(self, code: str, request: oauthlib.common.Request) Must return plain or S256. You can return a custom value if you have implemented your own AuthorizationCodeGrant class. - Keyword Arguments: - - code: Authorization code. - request: OAuthlib request. + Arguments: + --------- + code: Authorization code. + request: OAuthlib request. Return type: @@ -1148,8 +1245,12 @@ def get_code_challenge_method(self, code: str, request: oauthlib.common.Request) Method is used by: Authorization Code Grant - when PKCE is active + """ - from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + static, + ) assert DBSession is not None diff --git a/geoportal/c2cgeoportal_geoportal/lib/oidc.py b/geoportal/c2cgeoportal_geoportal/lib/oidc.py index 083aa2b116..71964b445c 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/oidc.py +++ b/geoportal/c2cgeoportal_geoportal/lib/oidc.py @@ -34,7 +34,11 @@ import pyramid.response import simple_openid_connect.client import simple_openid_connect.data -from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError, HTTPUnauthorized +from pyramid.httpexceptions import ( + HTTPBadRequest, + HTTPInternalServerError, + HTTPUnauthorized, +) from pyramid.security import remember from c2cgeoportal_geoportal.lib.caching import get_region @@ -48,9 +52,7 @@ # User create on demand class DynamicUser(NamedTuple): - """ - User created dynamically. - """ + """User created dynamically.""" id: int username: str @@ -62,10 +64,7 @@ class DynamicUser(NamedTuple): @_CACHE_REGION_OBJ.cache_on_arguments() def get_oidc_client(request: pyramid.request.Request, host: str) -> simple_openid_connect.client.OpenidClient: - """ - Get the OpenID Connect client from the request settings. - """ - + """Get the OpenID Connect client from the request settings.""" del host # used for cache key authentication_settings = request.registry.settings.get("authentication", {}) @@ -83,9 +82,7 @@ def get_oidc_client(request: pyramid.request.Request, host: str) -> simple_openi class OidcRememberObject(TypedDict): - """ - The JSON object that is stored in a cookie to remember the user. - """ + """The JSON object that is stored in a cookie to remember the user.""" access_token: str access_token_expires: str @@ -146,11 +143,13 @@ def get_user_from_remember( :param settings: The OpenID Connect configuration. :param update_create_user: If the user should be updated or created if it does not exist. """ - # Those imports are here to avoid initializing the models module before the database schema are # correctly initialized. from c2cgeoportal_commons import models # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models import main, static # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + main, + static, + ) assert models.DBSession is not None @@ -213,9 +212,7 @@ def get_user_from_remember( class OidcRemember: - """ - Build the abject that we want to remember in the cookie. - """ + """Build the abject that we want to remember in the cookie.""" def __init__(self, request: pyramid.request.Request): self.request = request @@ -229,10 +226,7 @@ def remember( ), host: str, ) -> OidcRememberObject: - """ - Remember the user in the cookie. - """ - + """Remember the user in the cookie.""" del host # Used for cache key if isinstance(token_response, simple_openid_connect.data.TokenErrorResponse): @@ -297,8 +291,6 @@ def remember( def includeme(config: pyramid.config.Configurator) -> None: - """ - Pyramid includeme function. - """ + """Pyramid includeme function.""" config.add_request_method(get_remember_from_user_info, name="get_remember_from_user_info") config.add_request_method(get_user_from_remember, name="get_user_from_remember") diff --git a/geoportal/c2cgeoportal_geoportal/lib/wmstparsing.py b/geoportal/c2cgeoportal_geoportal/lib/wmstparsing.py index c6d4c1d7f1..6376d63d58 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/wmstparsing.py +++ b/geoportal/c2cgeoportal_geoportal/lib/wmstparsing.py @@ -57,10 +57,10 @@ class TimeInformation: Collect the WMS time information. Arguments: - extent: A time extent instance (``TimeExtentValue`` or ``TimeExtentInterval``) mode: The layer mode ("single", "range" or "disabled") widget: The layer mode ("slider" (default) or "datepicker") + """ def __init__(self) -> None: @@ -98,7 +98,7 @@ def merge_mode(self, mode: str) -> None: self.mode = mode def merge_widget(self, widget: str | None) -> None: - widget = "slider" if not widget else widget + widget = widget if widget else "slider" assert widget is not None if self.widget is not None: @@ -134,11 +134,11 @@ def __init__( Initialize. Arguments: - values: A set() of datetime resolution: The resolution from the mapfile time definition min_def_value: the minimum default value as a datetime max_def_value: the maximum default value as a datetime + """ self.values = values self.resolution = resolution @@ -183,13 +183,13 @@ def __init__( Initialize. Arguments: - start: The start value as a datetime end: The end value as a datetime interval: The interval as a tuple (years, months, days, seconds) resolution: The resolution from the mapfile time definition min_def_value: the minimum default value as a datetime max_def_value: the maximum default value as a datetime + """ self.start = start self.end = end @@ -311,7 +311,7 @@ def _parse_date(date: str) -> tuple[str, datetime.datetime]: try: dt = datetime.datetime.strptime(date, pattern) return resolution, dt.replace(tzinfo=isodate.UTC) - except Exception: # pylint: disable=broad-exception-caught # nosec + except Exception: # pylint: disable=broad-exception-caught pass try: diff --git a/geoportal/c2cgeoportal_geoportal/lib/xsd.py b/geoportal/c2cgeoportal_geoportal/lib/xsd.py index 96611f793e..6c2a8e6104 100644 --- a/geoportal/c2cgeoportal_geoportal/lib/xsd.py +++ b/geoportal/c2cgeoportal_geoportal/lib/xsd.py @@ -42,10 +42,8 @@ def _element_callback(tb: str, column: sqlalchemy.sql.elements.NamedColumn[Any]) -> None: if column.info.get("readonly"): - with tag(tb, "xsd:annotation"): - with tag(tb, "xsd:appinfo"): - with tag(tb, "readonly", {"value": "true"}): - pass + with tag(tb, "xsd:annotation"), tag(tb, "xsd:appinfo"), tag(tb, "readonly", {"value": "true"}): + pass class XSDGenerator(PapyrusXSDGenerator): # type: ignore @@ -105,7 +103,9 @@ def add_column_property_xsd(self, tb: str, column_property: ColumnProperty[Any]) super().add_column_property_xsd(tb, column_property) def add_association_proxy_xsd(self, tb: str, column_property: ColumnProperty[Any]) -> None: - from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models import ( # pylint: disable=import-outside-toplevel + DBSession, + ) assert DBSession is not None @@ -125,11 +125,13 @@ def add_association_proxy_xsd(self, tb: str, column_property: ColumnProperty[Any attrs["nillable"] = "true" attrs["name"] = proxy with tag(tb, "xsd:element", attrs) as tb2: - with tag(tb2, "xsd:simpleType") as tb3: - with tag(tb3, "xsd:restriction", {"base": "xsd:string"}) as tb4: - for (value,) in query: - with tag(tb4, "xsd:enumeration", {"value": value}): - pass + with ( + tag(tb2, "xsd:simpleType") as tb3, + tag(tb3, "xsd:restriction", {"base": "xsd:string"}) as tb4, + ): + for (value,) in query: + with tag(tb4, "xsd:enumeration", {"value": value}): + pass self.element_callback(tb4, column) diff --git a/geoportal/c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml b/geoportal/c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml index 1123c6f3a0..fb7d19ccf0 100644 --- a/geoportal/c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +++ b/geoportal/c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml @@ -1,30 +1,3 @@ inherits: - duplicated - - utils:no-design-checks - -max-line-length: 110 - -pycodestyle: - options: - max-line-length: 110 - disable: - - E203 - -pylint: - options: - max-line-length: 110 - disable: - - too-many-locals - - too-many-statements - - too-many-branches - - c-extension-no-member - -pyflakes: - disable: - - F401 - -mccabe: - run: false - -bandit: - run: true + - utils:base-less-strict diff --git a/geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml b/geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml index f1c3219848..354842adf7 100644 --- a/geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +++ b/geoportal/c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml @@ -1,7 +1,2 @@ -[tool.black] -line-length = 110 -target-version = ['py39'] - -[tool.isort] -profile = "black" -line_length = 110 +[tool.ruff] +target-version = 'py310' diff --git a/geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py b/geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py index f9d2305ca3..8d8080f011 100644 --- a/geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py +++ b/geoportal/c2cgeoportal_geoportal/scripts/c2cupgrade.py @@ -32,13 +32,14 @@ import os import re import shutil -import subprocess +import subprocess # nosec import sys +import tempfile from argparse import ArgumentParser, Namespace from collections.abc import Callable from json.decoder import JSONDecodeError -from subprocess import call, check_call, check_output -from typing import Any, Union, cast +from subprocess import call, check_call, check_output # nosec +from typing import Any, cast import pkg_resources import requests @@ -61,51 +62,10 @@ def fix_style() -> None: """Fix the style of all the project files using isort, Black and Prettier.""" - - file_to_clean = [] - for filename, content in ( - (".prettierignore", "*.min.js\n"), - ("pyproject.toml", "[tool.black]\nline-length = 110\ntarget-version = ['py39']\n"), - (".prettierrc.yaml", "bracketSpacing: false\nquoteProps: preserve\n"), - ( - ".editorconfig", - """root = true -[*] -max_line_length = 110 -""", - ), - ): - if not os.path.exists(filename): - file_to_clean.append(filename) - if os.path.exists(os.path.join("CONST_create_template", filename)): - shutil.copyfile(os.path.join("CONST_create_template", filename), filename) - else: - with open(filename, "w", encoding="utf8") as file_: - file_.write(content) - - if os.path.exists("ci/config.yaml"): - os.rename("ci/config.yaml", "ci/config.yaml_") if os.path.exists(".pre-commit-config.yaml"): print("Run pre-commit to fix the style.") sys.stdout.flush() subprocess.run(["pre-commit", "run", "--all-files"]) # pylint: disable=subprocess-run-check - else: - print("Run c2cciutils-checks to fix the style.") - sys.stdout.flush() - subprocess.run( # pylint: disable=subprocess-run-check - ["c2cciutils-checks", "--fix", "--check=isort"] - ) - subprocess.run( # pylint: disable=subprocess-run-check - ["c2cciutils-checks", "--fix", "--check=black"] - ) - subprocess.run( # pylint: disable=subprocess-run-check - ["c2cciutils-checks", "--fix", "--check=prettier"] - ) - if os.path.exists("ci/config.yaml_"): - os.rename("ci/config.yaml_", "ci/config.yaml") - - for filename in file_to_clean: - os.remove(filename) def main() -> None: @@ -217,7 +177,7 @@ def get_upgrade(section: str) -> list[Any] | dict[str, Any]: sys.exit(1) with open(".upgrade.yaml", encoding="utf8") as project_file: - return cast(Union[list[Any], dict[str, Any]], yaml.safe_load(project_file)[section]) + return cast(list[Any] | dict[str, Any], yaml.safe_load(project_file)[section]) def print_step( self, @@ -254,7 +214,7 @@ def test_checkers(self) -> tuple[bool, str | None]: resp = requests.get( self.project["checker_url"], headers=self.project.get("checker_headers"), - verify=False, # nosec + verify=False, # noqa: S501 timeout=120, ) except requests.exceptions.ConnectionError as exception: @@ -324,12 +284,13 @@ def step0(self, step: int) -> None: @Step(1) def step1(self, step: int) -> None: - shutil.copyfile("project.yaml", "/tmp/project.yaml") - try: - check_call(["git", "reset", "--hard"]) - check_call(["git", "clean", "--force", "-d"]) - finally: - shutil.copyfile("/tmp/project.yaml", "project.yaml") + with tempfile.NamedTemporaryFile("w") as project_temp_file: + shutil.copyfile("project.yaml", project_temp_file) + try: + check_call(["git", "reset", "--hard"]) + check_call(["git", "clean", "--force", "-d"]) + finally: + shutil.copyfile(project_temp_file, "project.yaml") self.run_step(step + 1) @@ -343,36 +304,36 @@ def step2(self, step: int) -> None: @Step(3) def step3(self, step: int) -> None: - project_path = os.path.join("/tmp", self.project["project_folder"]) - os.mkdir(project_path) - shutil.copyfile("/src/project.yaml", os.path.join(project_path, "project.yaml")) - check_call( - [ - "pcreate", - "--overwrite", - "--scaffold=update", - project_path, - ] - ) - if self.get_project().get("advance", False): + with tempfile.TemporaryDirectory() as temp_directory_name: + project_path = os.path.join(temp_directory_name, self.project["project_folder"]) + os.mkdir(project_path) + shutil.copyfile("/src/project.yaml", os.path.join(project_path, "project.yaml")) check_call( [ "pcreate", "--overwrite", - "--scaffold=advance_update", + "--scaffold=update", project_path, ] ) + if self.get_project().get("advance", False): + check_call( + [ + "pcreate", + "--overwrite", + "--scaffold=advance_update", + project_path, + ] + ) - shutil.copyfile(os.path.join(project_path, ".upgrade.yaml"), ".upgrade.yaml") - for upgrade_file in cast(list[dict[str, Any]], self.get_upgrade("upgrade_files")): - action = upgrade_file["action"] - if action == "remove": - self.files_to_remove(upgrade_file, prefix="CONST_create_template", force=True) - if action == "move": - self.files_to_move(upgrade_file, prefix="CONST_create_template", force=True) + shutil.copyfile(os.path.join(project_path, ".upgrade.yaml"), ".upgrade.yaml") + for upgrade_file in cast(list[dict[str, Any]], self.get_upgrade("upgrade_files")): + action = upgrade_file["action"] + if action == "remove": + self.files_to_remove(upgrade_file, prefix="CONST_create_template", force=True) + if action == "move": + self.files_to_move(upgrade_file, prefix="CONST_create_template", force=True) - shutil.rmtree(project_path) os.remove(".upgrade.yaml") check_call(["git", "add", "--all", "--force", "CONST_create_template/"]) @@ -385,26 +346,26 @@ def step4(self, step: int) -> None: if os.path.exists("CONST_create_template"): check_call(["git", "rm", "-r", "--force", "CONST_create_template/"]) - project_path = os.path.join("/tmp", self.project["project_folder"]) - check_call(["ln", "-s", "/src", project_path]) - check_call( - [ - "pcreate", - "--overwrite", - "--scaffold=update", - project_path, - ] - ) - if self.get_project().get("advance", False): + with tempfile.TemporaryDirectory() as temp_directory_name: + project_path = os.path.join(temp_directory_name, self.project["project_folder"]) + check_call(["ln", "-s", "/src", project_path]) check_call( [ "pcreate", "--overwrite", - "--scaffold=advance_update", + "--scaffold=update", project_path, ] ) - os.remove(project_path) + if self.get_project().get("advance", False): + check_call( + [ + "pcreate", + "--overwrite", + "--scaffold=advance_update", + project_path, + ] + ) check_call(["git", "add", "--all", "CONST_create_template/"]) @@ -644,10 +605,7 @@ def is_managed(self, file_: str, files_to_get: bool = False) -> bool: if not managed: for files in self.project["managed_files"]: - if isinstance(files, str): - pattern = files - else: - pattern = files["pattern"] + pattern = files if isinstance(files, str) else files["pattern"] if re.match(pattern + "$", file_): print(f"File '{file_}' included by project config pattern `managed_files` '{pattern}'.") print("managed", file_, pattern) diff --git a/geoportal/c2cgeoportal_geoportal/scripts/manage_users.py b/geoportal/c2cgeoportal_geoportal/scripts/manage_users.py index 9890e2ed82..de184a6b93 100644 --- a/geoportal/c2cgeoportal_geoportal/scripts/manage_users.py +++ b/geoportal/c2cgeoportal_geoportal/scripts/manage_users.py @@ -37,7 +37,6 @@ def get_argparser() -> argparse.ArgumentParser: """Get the argument parser for this script.""" - usage = """Reset a user password. The username is used as password if the password is not provided with the corresponding option. User can be created if it does not exist yet.""" @@ -66,7 +65,6 @@ def main() -> None: to get the options list, do: docker compose exec geoportal manage-users --help """ - parser = get_argparser() options = parser.parse_args() username = options.user @@ -76,8 +74,12 @@ def main() -> None: session = get_session(settings, transaction.manager) # Must be done only once we have loaded the project config - from c2cgeoportal_commons.models.main import Role # pylint: disable=import-outside-toplevel - from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Role, + ) + from c2cgeoportal_commons.models.static import ( # pylint: disable=import-outside-toplevel + User, + ) print("\n") diff --git a/geoportal/c2cgeoportal_geoportal/scripts/pcreate.py b/geoportal/c2cgeoportal_geoportal/scripts/pcreate.py index c292b002d9..978475a448 100644 --- a/geoportal/c2cgeoportal_geoportal/scripts/pcreate.py +++ b/geoportal/c2cgeoportal_geoportal/scripts/pcreate.py @@ -29,10 +29,10 @@ import json import os import re -import subprocess +import subprocess # nosec import sys from argparse import ArgumentParser -from typing import Any, Union, cast +from typing import Any, cast import pkg_resources import requests @@ -46,7 +46,6 @@ def get_argparser() -> ArgumentParser: """Get the argument parser for this script.""" - parser = ArgumentParser( prog=sys.argv[0], add_help=True, @@ -183,9 +182,7 @@ def get_context(self) -> dict[str, str | int]: } context.update(self.read_project_file()) if os.environ.get("CI") == "true": - context["authtkt_secret"] = ( # nosec - "io7heoDui8xaikie1rushaeGeiph8Bequei6ohchaequob6viejei0xooWeuvohf" - ) + context["authtkt_secret"] = "io7heoDui8xaikie1rushaeGeiph8Bequei6ohchaequob6viejei0xooWeuvohf" # noqa: S105 self.get_var(context, "srid", "Spatial Reference System Identifier (e.g. 2056): ", int) srid = cast(int, context["srid"]) @@ -246,7 +243,7 @@ def read_project_file(self) -> dict[str, str | int]: if os.path.exists(project_file): with open(project_file, encoding="utf8") as f: project = yaml.safe_load(f) - return cast(dict[str, Union[str, int]], project.get("template_vars", {})) + return cast(dict[str, str | int], project.get("template_vars", {})) else: return {} @@ -280,16 +277,12 @@ def epsg2bbox(srid: int) -> list[str] | None: r = requests.get(f"https://epsg.io/?format=json&q={srid}", timeout=60) bbox = r.json()["results"][0]["bbox"] r = requests.get( - "https://epsg.io/trans?s_srs=4326&t_srs={srid}&data={bbox[1]},{bbox[0]}".format( - srid=srid, bbox=bbox - ), + f"https://epsg.io/trans?s_srs=4326&t_srs={srid}&data={bbox[1]},{bbox[0]}", timeout=60, ) r1 = r.json()[0] r = requests.get( - "https://epsg.io/trans?s_srs=4326&t_srs={srid}&data={bbox[3]},{bbox[2]}".format( - srid=srid, bbox=bbox - ), + f"https://epsg.io/trans?s_srs=4326&t_srs={srid}&data={bbox[3]},{bbox[2]}", timeout=60, ) r2 = r.json()[0] diff --git a/geoportal/c2cgeoportal_geoportal/scripts/theme2fts.py b/geoportal/c2cgeoportal_geoportal/scripts/theme2fts.py index 3256786eff..04c8951f99 100644 --- a/geoportal/c2cgeoportal_geoportal/scripts/theme2fts.py +++ b/geoportal/c2cgeoportal_geoportal/scripts/theme2fts.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Camptocamp SA +# Copyright (c) 2014-2024, Camptocamp SA # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -95,7 +95,6 @@ def get_argparser() -> ArgumentParser: def main() -> None: """Run the command.""" - options = get_argparser().parse_args() settings = get_appsettings(options) @@ -180,7 +179,9 @@ def _add_fts( action: str, role: Optional["c2cgeoportal_commons.models.main.Role"], ) -> None: - from c2cgeoportal_commons.models.main import FullTextSearch # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + FullTextSearch, + ) key = ( item.name if self.options.name else item.id, @@ -248,7 +249,9 @@ def _add_group( export: bool, role: Optional["c2cgeoportal_commons.models.main.Role"], ) -> bool: - from c2cgeoportal_commons.models.main import LayerGroup # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + LayerGroup, + ) fill = False for child in group.children: diff --git a/geoportal/c2cgeoportal_geoportal/scripts/urllogin.py b/geoportal/c2cgeoportal_geoportal/scripts/urllogin.py index bedf0e6153..a1fb840214 100644 --- a/geoportal/c2cgeoportal_geoportal/scripts/urllogin.py +++ b/geoportal/c2cgeoportal_geoportal/scripts/urllogin.py @@ -56,7 +56,6 @@ def create_token(aeskey: str, user: str, password: str, valid: bool) -> str: def get_argparser() -> argparse.ArgumentParser: """Get the argument parser for this script.""" - parser = argparse.ArgumentParser(description="Generate an auth token") fill_arguments(parser, use_attribute=True) parser.add_argument("user", help="The username") @@ -67,7 +66,6 @@ def get_argparser() -> argparse.ArgumentParser: def main() -> None: """Run the command.""" - args = get_argparser().parse_args() settings = get_appsettings(args) urllogin = settings.get("urllogin", {}) diff --git a/geoportal/c2cgeoportal_geoportal/views/dev.py b/geoportal/c2cgeoportal_geoportal/views/dev.py index dc11dad84f..d61f78bef7 100644 --- a/geoportal/c2cgeoportal_geoportal/views/dev.py +++ b/geoportal/c2cgeoportal_geoportal/views/dev.py @@ -1,4 +1,4 @@ -# Copyright (c) 2011-2021, Camptocamp SA +# Copyright (c) 2011-2024, Camptocamp SA # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -31,10 +31,10 @@ import pyramid.request import pyramid.response +from c2cgeoportal_commons.lib.url import Url from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config -from c2cgeoportal_commons.lib.url import Url from c2cgeoportal_geoportal.views.proxy import Proxy logger = logging.getLogger(__name__) diff --git a/geoportal/c2cgeoportal_geoportal/views/dynamic.py b/geoportal/c2cgeoportal_geoportal/views/dynamic.py index 7441ab71ab..b12813a7a2 100644 --- a/geoportal/c2cgeoportal_geoportal/views/dynamic.py +++ b/geoportal/c2cgeoportal_geoportal/views/dynamic.py @@ -31,12 +31,12 @@ from typing import Any, cast import pyramid.request +from c2cgeoportal_commons import models +from c2cgeoportal_commons.models import main from pyramid.httpexceptions import HTTPNotFound from pyramid.view import view_config from sqlalchemy import func -from c2cgeoportal_commons import models -from c2cgeoportal_commons.models import main from c2cgeoportal_geoportal import is_allowed_host from c2cgeoportal_geoportal.lib.cacheversion import get_cache_version from c2cgeoportal_geoportal.lib.caching import get_region @@ -78,13 +78,12 @@ def _interface( Get the interface configuration. Arguments: - interface_config: Current interface configuration interface_name: Interface name (we use in the configuration) original_interface_name: Original interface name (directly for the query string) dynamic: The values that's dynamically generated - """ + """ if "extends" in interface_config: constants = self._interface( self.interfaces_config[interface_config["extends"]], diff --git a/geoportal/c2cgeoportal_geoportal/views/entry.py b/geoportal/c2cgeoportal_geoportal/views/entry.py index ec566170c3..444008d203 100644 --- a/geoportal/c2cgeoportal_geoportal/views/entry.py +++ b/geoportal/c2cgeoportal_geoportal/views/entry.py @@ -114,7 +114,6 @@ def _get_ngeo_resources(pattern: str) -> list[str]: def canvas_view(request: pyramid.request.Request, interface_config: dict[str, Any]) -> dict[str, Any]: """Get view used as entry point of a canvas interface.""" - js_files = _get_ngeo_resources(f"{interface_config.get('layout', interface_config['name'])}*.js") css_files = _get_ngeo_resources(f"{interface_config.get('layout', interface_config['name'])}*.css") css = "\n ".join( @@ -149,7 +148,6 @@ def custom_view( request: pyramid.request.Request, interface_config: dict[str, Any] ) -> pyramid.response.Response: """Get view used as entry point of a canvas interface.""" - set_common_headers(request, "index", Cache.PUBLIC_NO, content_type="text/html") html_filename = interface_config.get("html_filename", f"{interface_config['name']}.html") diff --git a/geoportal/c2cgeoportal_geoportal/views/fulltextsearch.py b/geoportal/c2cgeoportal_geoportal/views/fulltextsearch.py index d2f1f8cbd1..ee041b8e21 100644 --- a/geoportal/c2cgeoportal_geoportal/views/fulltextsearch.py +++ b/geoportal/c2cgeoportal_geoportal/views/fulltextsearch.py @@ -29,14 +29,14 @@ import re import pyramid.request +from c2cgeoportal_commons.models import DBSession +from c2cgeoportal_commons.models.main import FullTextSearch, Interface from geoalchemy2.shape import to_shape from geojson import Feature, FeatureCollection from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError from pyramid.view import view_config from sqlalchemy import ColumnElement, and_, desc, func, or_ -from c2cgeoportal_commons.models import DBSession -from c2cgeoportal_commons.models.main import FullTextSearch, Interface from c2cgeoportal_geoportal import locale_negotiator from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers diff --git a/geoportal/c2cgeoportal_geoportal/views/geometry_processing.py b/geoportal/c2cgeoportal_geoportal/views/geometry_processing.py index 47f66abdc7..de7620570d 100644 --- a/geoportal/c2cgeoportal_geoportal/views/geometry_processing.py +++ b/geoportal/c2cgeoportal_geoportal/views/geometry_processing.py @@ -27,6 +27,7 @@ import pyramid.request +from c2cgeoportal_commons.models import DBSession from geoalchemy2.shape import from_shape, to_shape from geojson import loads from pyramid.httpexceptions import HTTPBadRequest @@ -35,8 +36,6 @@ from shapely.geometry.base import BaseGeometry from sqlalchemy import func -from c2cgeoportal_commons.models import DBSession - class GeometryProcessing: """ diff --git a/geoportal/c2cgeoportal_geoportal/views/i18n.py b/geoportal/c2cgeoportal_geoportal/views/i18n.py index aa21d76eed..5e7cdb1e78 100644 --- a/geoportal/c2cgeoportal_geoportal/views/i18n.py +++ b/geoportal/c2cgeoportal_geoportal/views/i18n.py @@ -68,7 +68,6 @@ def locale(request: pyramid.request.Request) -> pyramid.response.Response: @view_config(route_name="localepot") # type: ignore def localepot(request: pyramid.request.Request) -> pyramid.response.Response: """Get the pot from an HTTP request.""" - # Build the list of files to be processed sources = [] sources += glob.glob(f"/app/{request.registry.package_name}/static-ngeo/js/apps/*.html.ejs") diff --git a/geoportal/c2cgeoportal_geoportal/views/layers.py b/geoportal/c2cgeoportal_geoportal/views/layers.py index 491d2a8e14..13328badbe 100644 --- a/geoportal/c2cgeoportal_geoportal/views/layers.py +++ b/geoportal/c2cgeoportal_geoportal/views/layers.py @@ -40,6 +40,7 @@ import sqlalchemy.ext.declarative import sqlalchemy.orm import sqlalchemy.orm.query +from c2cgeoportal_commons import models from geoalchemy2 import Geometry from geoalchemy2.shape import from_shape, to_shape from geojson.feature import Feature, FeatureCollection @@ -56,19 +57,27 @@ from shapely import unary_union from shapely.errors import TopologicalError from sqlalchemy import Enum, Numeric, String, Text, Unicode, UnicodeText, exc, func -from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound # type: ignore[attr-defined] +from sqlalchemy.orm.exc import ( # type: ignore[attr-defined] + MultipleResultsFound, + NoResultFound, +) from sqlalchemy.orm.properties import ColumnProperty from sqlalchemy.orm.util import class_mapper from sqlalchemy.sql import and_, or_ -from c2cgeoportal_commons import models from c2cgeoportal_geoportal.lib import get_roles_id from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers -from c2cgeoportal_geoportal.lib.dbreflection import _AssociationProxy, get_class, get_table +from c2cgeoportal_geoportal.lib.dbreflection import ( + _AssociationProxy, + get_class, + get_table, +) if TYPE_CHECKING: - from c2cgeoportal_commons.models import main # pylint: disable=ungrouped-imports.useless-suppression + from c2cgeoportal_commons.models import ( + main, # pylint: disable=ungrouped-imports.useless-suppression + ) _LOG = logging.getLogger(__name__) @@ -222,7 +231,9 @@ def _get_geom_col_info(layer: "main.Layer") -> tuple[str, int]: @staticmethod def _get_layer(layer_id: int) -> "main.Layer": """Return a ``Layer`` object for ``layer_id``.""" - from c2cgeoportal_commons.models.main import Layer # pylint: disable=import-outside-toplevel + from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel + Layer, + ) assert models.DBSession is not None @@ -281,7 +292,6 @@ def _get_protocol_for_request(self) -> Protocol: def _proto_read(self, layer: "main.Layer") -> FeatureCollection: """Read features for the layer based on the self.request.""" - from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel Layer, RestrictionArea, @@ -495,7 +505,7 @@ def enumerate_attribute_values(self) -> dict[str, Any]: raise HTTPInternalServerError("Missing configuration") layername = self.request.matchdict["layer_name"] fieldname = self.request.matchdict["field_name"] - # TODO check if layer is public or not + # TODO check if layer is public or not # pylint: disable=fixme return cast(dict[str, Any], self._enumerate_attribute_values(layername, fieldname)) @@ -541,15 +551,14 @@ def get_layer_class(layer: "main.Layer", with_last_update_columns: bool = False) Get the SQLAlchemy class to edit a GeoMapFish layer. Keyword Arguments: - layer: The GeoMapFish layer with_last_update_columns: False to just have a class to access to the table and be able to modify the last_update_columns, True to have a correct class to build the UI (without the hidden column). Returns: SQLAlchemy class - """ + """ assert layer.geo_table is not None # Exclude the columns used to record the last features update @@ -616,7 +625,6 @@ class ColumnProperties(TypedDict, total=False): def get_layer_metadata(layer: "main.Layer") -> list[ColumnProperties]: """Get the metadata related to a layer.""" - assert models.DBSession is not None cls = get_layer_class(layer, with_last_update_columns=True) @@ -690,7 +698,7 @@ def _convert_column_type(column_type: object) -> ColumnProperties: return restriction # String type - if isinstance(column_type, (String, Text, Unicode, UnicodeText)): + if isinstance(column_type, String | Text | Unicode | UnicodeText): if column_type.length is None: return {"type": "xsd:string"} return {"type": "xsd:string", "maxLength": int(column_type.length)} diff --git a/geoportal/c2cgeoportal_geoportal/views/login.py b/geoportal/c2cgeoportal_geoportal/views/login.py index 2d812a3c94..fd061bf6a0 100644 --- a/geoportal/c2cgeoportal_geoportal/views/login.py +++ b/geoportal/c2cgeoportal_geoportal/views/login.py @@ -38,6 +38,9 @@ import pyotp import pyramid.request import pyramid.response +from c2cgeoportal_commons import models +from c2cgeoportal_commons.lib.email_ import send_email_config +from c2cgeoportal_commons.models import static from pyramid.httpexceptions import ( HTTPBadRequest, HTTPForbidden, @@ -50,9 +53,6 @@ from pyramid.view import forbidden_view_config, view_config from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined] -from c2cgeoportal_commons import models -from c2cgeoportal_commons.lib.email_ import send_email_config -from c2cgeoportal_commons.models import static from c2cgeoportal_geoportal import is_allowed_url, is_valid_referrer from c2cgeoportal_geoportal.lib import get_setting, is_intranet, oauth2, oidc from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers @@ -126,9 +126,7 @@ def loginform(self) -> dict[str, Any]: @staticmethod def _validate_2fa_totp(user: static.User, otp: str) -> bool: - if pyotp.TOTP(user.tech_data.get("2fa_totp_secret", "")).verify(otp): - return True - return False + return bool(pyotp.TOTP(user.tech_data.get("2fa_totp_secret", "")).verify(otp)) @view_config(route_name="login") # type: ignore def login(self) -> pyramid.response.Response: @@ -374,10 +372,9 @@ def change_password(self) -> pyramid.response.Response: _LOG.info("The login '%s' does not exist.", login) raise HTTPUnauthorized("See server logs for details") - if self.two_factor_auth: - if not self._validate_2fa_totp(user, otp): - _LOG.info("The second factor is wrong for user '%s'.", login) - raise HTTPUnauthorized("See server logs for details") + if self.two_factor_auth and not self._validate_2fa_totp(user, otp): + _LOG.info("The second factor is wrong for user '%s'.", login) + raise HTTPUnauthorized("See server logs for details") else: user = self.request.user @@ -662,7 +659,7 @@ def oidc_callback(self) -> pyramid.response.Response: "login", Cache.PRIVATE_NO, response=Response( - # TODO respect the user interface... + # TODO respect the user interface... # pylint: disable=fixme json.dumps( { "username": user.display_name, diff --git a/geoportal/c2cgeoportal_geoportal/views/mapserverproxy.py b/geoportal/c2cgeoportal_geoportal/views/mapserverproxy.py index 474530112f..3e9875a416 100644 --- a/geoportal/c2cgeoportal_geoportal/views/mapserverproxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/mapserverproxy.py @@ -29,13 +29,18 @@ import logging from typing import Any -from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPUnauthorized +from c2cgeoportal_commons.lib.url import Url +from c2cgeoportal_commons.models import main +from pyramid.httpexceptions import ( + HTTPForbidden, + HTTPFound, + HTTPInternalServerError, + HTTPUnauthorized, +) from pyramid.request import Request from pyramid.response import Response from pyramid.view import view_config -from c2cgeoportal_commons.lib.url import Url -from c2cgeoportal_commons.models import main from c2cgeoportal_geoportal.lib import get_roles_id, get_roles_name from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache diff --git a/geoportal/c2cgeoportal_geoportal/views/ogcproxy.py b/geoportal/c2cgeoportal_geoportal/views/ogcproxy.py index ca0298e3de..dd1d12c2ca 100644 --- a/geoportal/c2cgeoportal_geoportal/views/ogcproxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/ogcproxy.py @@ -28,11 +28,11 @@ import logging import pyramid.request +from c2cgeoportal_commons.lib.url import Url, get_url2 +from c2cgeoportal_commons.models import DBSession, main from pyramid.httpexceptions import HTTPBadRequest from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined] -from c2cgeoportal_commons.lib.url import Url, get_url2 -from c2cgeoportal_commons.models import DBSession, main from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.views.proxy import Proxy diff --git a/geoportal/c2cgeoportal_geoportal/views/pdfreport.py b/geoportal/c2cgeoportal_geoportal/views/pdfreport.py index 4342daf0cd..3d5d2b9505 100644 --- a/geoportal/c2cgeoportal_geoportal/views/pdfreport.py +++ b/geoportal/c2cgeoportal_geoportal/views/pdfreport.py @@ -31,12 +31,12 @@ import pyramid.request import pyramid.response -from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden -from pyramid.view import view_config - from c2cgeoportal_commons import models from c2cgeoportal_commons.lib.url import Url from c2cgeoportal_commons.models import main +from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden +from pyramid.view import view_config + from c2cgeoportal_geoportal.lib.common_headers import Cache from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy @@ -121,7 +121,7 @@ def get_report(self) -> pyramid.response.Response: ) if layer_config["check_credentials"]: - # FIXME: support of mapserver groups + # FIXME: support of mapserver groups # pylint: disable=fixme ogc_server = ( models.DBSession.query(main.OGCServer) .filter(main.OGCServer.name == layer_config["ogc_server"]) diff --git a/geoportal/c2cgeoportal_geoportal/views/printproxy.py b/geoportal/c2cgeoportal_geoportal/views/printproxy.py index a46f2736f8..2cd8bc1384 100644 --- a/geoportal/c2cgeoportal_geoportal/views/printproxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/printproxy.py @@ -33,10 +33,10 @@ import pyramid.request import pyramid.response import requests +from c2cgeoportal_commons.lib.url import Url from pyramid.httpexceptions import HTTPBadGateway, HTTPFound from pyramid.view import view_config -from c2cgeoportal_commons.lib.url import Url from c2cgeoportal_geoportal.lib import is_intranet from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache diff --git a/geoportal/c2cgeoportal_geoportal/views/proxy.py b/geoportal/c2cgeoportal_geoportal/views/proxy.py index f18a2c8c80..427308ebcf 100644 --- a/geoportal/c2cgeoportal_geoportal/views/proxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/proxy.py @@ -33,9 +33,9 @@ import pyramid.request import pyramid.response import requests +from c2cgeoportal_commons.lib.url import Url from pyramid.httpexceptions import HTTPBadGateway, exception_response -from c2cgeoportal_commons.lib.url import Url from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers from c2cgeoportal_geoportal.views import restrict_headers diff --git a/geoportal/c2cgeoportal_geoportal/views/raster.py b/geoportal/c2cgeoportal_geoportal/views/raster.py index 5f476cc2b6..5c742dcf95 100644 --- a/geoportal/c2cgeoportal_geoportal/views/raster.py +++ b/geoportal/c2cgeoportal_geoportal/views/raster.py @@ -36,11 +36,11 @@ import numpy import pyramid.request import zope.event.classhandler +from c2cgeoportal_commons.models import InvalidateCacheEvent from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound from pyramid.view import view_config from rasterio.io import DatasetReader -from c2cgeoportal_commons.models import InvalidateCacheEvent from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers if TYPE_CHECKING: @@ -108,7 +108,9 @@ def _get_data(self, layer: dict[str, Any], name: str) -> "fiona.collection.Colle path = layer["file"] if layer.get("type", "shp_index") == "shp_index": # Avoid loading if not needed - from fiona.collection import Collection # pylint: disable=import-outside-toplevel + from fiona.collection import ( # pylint: disable=import-outside-toplevel + Collection, + ) self.data[name] = Collection(path) elif layer.get("type") == "gdal": diff --git a/geoportal/c2cgeoportal_geoportal/views/resourceproxy.py b/geoportal/c2cgeoportal_geoportal/views/resourceproxy.py index 3ccef3c067..460c9423d1 100644 --- a/geoportal/c2cgeoportal_geoportal/views/resourceproxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/resourceproxy.py @@ -65,7 +65,7 @@ def proxy(self) -> pyramid.response.Response: response = self._build_response( response, response.content, cache_control, "externalresource", content_type=content_type ) - for header in response.headers.keys(): + for header in response.headers: if header not in self.settings["allowed_headers"]: response.headers.pop(header) return response diff --git a/geoportal/c2cgeoportal_geoportal/views/shortener.py b/geoportal/c2cgeoportal_geoportal/views/shortener.py index 2480734a62..ba2aaf2d43 100644 --- a/geoportal/c2cgeoportal_geoportal/views/shortener.py +++ b/geoportal/c2cgeoportal_geoportal/views/shortener.py @@ -34,11 +34,16 @@ from urllib.parse import urlparse import pyramid.request -from pyramid.httpexceptions import HTTPBadRequest, HTTPFound, HTTPInternalServerError, HTTPNotFound -from pyramid.view import view_config - from c2cgeoportal_commons.lib.email_ import send_email_config from c2cgeoportal_commons.models import DBSession, static +from pyramid.httpexceptions import ( + HTTPBadRequest, + HTTPFound, + HTTPInternalServerError, + HTTPNotFound, +) +from pyramid.view import view_config + from c2cgeoportal_geoportal import is_allowed_url from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers @@ -107,7 +112,7 @@ def create(self) -> dict[str, str]: tries = 0 while not shortened: ref = "".join( - random.choice(string.ascii_letters + string.digits) # nosec + random.choice(string.ascii_letters + string.digits) # noqa: S311 for i in range(self.settings.get("length", 4)) ) test_url = DBSession.query(static.Shorturl).filter(static.Shorturl.ref == ref).all() diff --git a/geoportal/c2cgeoportal_geoportal/views/theme.py b/geoportal/c2cgeoportal_geoportal/views/theme.py index 42f2b2c5fb..2fa7060a2b 100644 --- a/geoportal/c2cgeoportal_geoportal/views/theme.py +++ b/geoportal/c2cgeoportal_geoportal/views/theme.py @@ -35,7 +35,7 @@ import time from collections import Counter from math import sqrt -from typing import Any, Optional, Union, cast +from typing import Any, cast import dogpile.cache.api import pyramid.httpexceptions @@ -43,6 +43,9 @@ import requests import sqlalchemy import sqlalchemy.orm.query +from c2cgeoportal_commons import models +from c2cgeoportal_commons.lib.url import Url, get_url2 +from c2cgeoportal_commons.models import cache_invalidate_cb, main from c2cwsgiutils.auth import auth_view from defusedxml import lxml from lxml import etree # nosec @@ -51,11 +54,13 @@ from sqlalchemy.orm import subqueryload from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined] -from c2cgeoportal_commons import models -from c2cgeoportal_commons.lib.url import Url, get_url2 -from c2cgeoportal_commons.models import cache_invalidate_cb, main from c2cgeoportal_geoportal import is_allowed_host, is_allowed_url -from c2cgeoportal_geoportal.lib import get_roles_id, get_typed, get_types_map, is_intranet +from c2cgeoportal_geoportal.lib import ( + get_roles_id, + get_typed, + get_types_map, + is_intranet, +) from c2cgeoportal_geoportal.lib.caching import get_region from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers from c2cgeoportal_geoportal.lib.functionality import get_mapserver_substitution_params @@ -72,7 +77,7 @@ _CACHE_OGC_SERVER_REGION = get_region("ogc-server") _TIMEOUT = int(os.environ.get("C2CGEOPORTAL_THEME_TIMEOUT", "300")) -Metadata = Union[str, int, float, bool, list[Any], dict[str, Any]] +Metadata = str | int | float | bool | list[Any] | dict[str, Any] async def get_http_cached( @@ -323,7 +328,6 @@ async def _wms_getcap_cached( def _create_layer_query(self, interface: str) -> sqlalchemy.orm.query.RowReturningQuery[tuple[str]]: """Create an SQLAlchemy query for Layer and for the role identified to by ``role_id``.""" - assert models.DBSession is not None query: sqlalchemy.orm.query.RowReturningQuery[tuple[str]] = models.DBSession.query( @@ -596,7 +600,6 @@ def _layer_included(tree_item: main.TreeItem) -> bool: def _get_ogc_servers(self, group: main.LayerGroup, depth: int) -> set[str | bool]: """Get unique identifier for each child by recursing on all the children.""" - ogc_servers: set[str | bool] = set() # escape loop @@ -713,15 +716,14 @@ async def _group( ) group_theme["mixed"] = mixed - if org_depth == 1: - if not mixed: - assert time_ is not None - assert dim is not None - group_theme["ogcServer"] = cast(list[Any], ogc_servers)[0] - if time_.has_time() and time_.layer is None: - group_theme["time"] = time_.to_dict() + if org_depth == 1 and not mixed: + assert time_ is not None + assert dim is not None + group_theme["ogcServer"] = cast(list[Any], ogc_servers)[0] + if time_.has_time() and time_.layer is None: + group_theme["time"] = time_.to_dict() - group_theme["dimensions"] = dim.get_dimensions() + group_theme["dimensions"] = dim.get_dimensions() return group_theme, errors return None, errors @@ -775,7 +777,6 @@ async def _themes( self, interface: str = "desktop", filter_themes: bool = True, min_levels: int = 1 ) -> tuple[list[dict[str, Any]], set[str]]: """Return theme information for the role identified by ``role_id``.""" - assert models.DBSession is not None self._load_tree_items() @@ -894,7 +895,7 @@ def _get_role_ids(self) -> set[int] | None: async def _wfs_get_features_type( self, wfs_url: Url, ogc_server: main.OGCServer, preload: bool = False, cache: bool = True - ) -> tuple[Optional[etree.Element], set[str]]: # pylint: disable=c-extension-no-member + ) -> tuple[etree.Element | None, set[str]]: # pylint: disable=c-extension-no-member errors = set() wfs_url.add_query( @@ -987,7 +988,8 @@ async def _preload(self, errors: set[str]) -> None: for ogc_server, nb_layers in ( models.DBSession.query( - main.OGCServer, sqlalchemy.func.count(main.LayerWMS.id) # pylint: disable=not-callable + main.OGCServer, + sqlalchemy.func.count(main.LayerWMS.id), # pylint: disable=not-callable ) .filter(main.LayerWMS.ogc_server_id == main.OGCServer.id) .group_by(main.OGCServer.id) @@ -1037,7 +1039,7 @@ def _get_features_attributes_cache( "available namespaces: %s (OGC server: %s)", type_namespace, name, - ", ".join([str(k) for k in child.nsmap.keys()]), + ", ".join([str(k) for k in child.nsmap]), ogc_server_name, ) elif child.nsmap[type_namespace] != namespace: @@ -1069,7 +1071,7 @@ def _get_features_attributes_cache( "available namespaces: %s (OGC server: %s)", type_namespace, name, - ", ".join([str(k) for k in child.nsmap.keys()]), + ", ".join([str(k) for k in child.nsmap]), ogc_server_name, ) for key, value in children.attrib.items(): @@ -1114,7 +1116,6 @@ def _get_features_attributes_cache( @view_config(route_name="themes", renderer="json") # type: ignore[misc] def themes(self) -> dict[str, dict[str, dict[str, Any]] | list[str]]: - is_allowed_host(self.request) interface = self.request.params.get("interface", "desktop") @@ -1145,7 +1146,8 @@ async def get_theme() -> dict[str, dict[str, Any] | list[str]]: result["ogcServers"] = {} for ogc_server, nb_layers in ( models.DBSession.query( - main.OGCServer, sqlalchemy.func.count(main.LayerWMS.id) # pylint: disable=not-callable + main.OGCServer, + sqlalchemy.func.count(main.LayerWMS.id), # pylint: disable=not-callable ) .filter(main.LayerWMS.ogc_server_id == main.OGCServer.id) .group_by(main.OGCServer.id) @@ -1239,7 +1241,7 @@ def get_theme_anonymous( if self.request.user is None: return cast( - dict[str, Union[dict[str, dict[str, Any]], list[str]]], + dict[str, dict[str, dict[str, Any]] | list[str]], get_theme_anonymous( is_intranet(self.request), interface, diff --git a/geoportal/c2cgeoportal_geoportal/views/tinyowsproxy.py b/geoportal/c2cgeoportal_geoportal/views/tinyowsproxy.py index 9edd8efb1f..c33ca1c3c2 100644 --- a/geoportal/c2cgeoportal_geoportal/views/tinyowsproxy.py +++ b/geoportal/c2cgeoportal_geoportal/views/tinyowsproxy.py @@ -30,13 +30,18 @@ from typing import Any import pyramid.request -from defusedxml import ElementTree -from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPInternalServerError, HTTPUnauthorized -from pyramid.view import view_config - from c2cgeoportal_commons import models from c2cgeoportal_commons.lib.url import Url from c2cgeoportal_commons.models import main +from defusedxml import ElementTree +from pyramid.httpexceptions import ( + HTTPBadRequest, + HTTPForbidden, + HTTPInternalServerError, + HTTPUnauthorized, +) +from pyramid.view import view_config + from c2cgeoportal_geoportal.lib.common_headers import Cache from c2cgeoportal_geoportal.lib.filter_capabilities import ( filter_wfst_capabilities, @@ -97,11 +102,10 @@ def proxy(self) -> pyramid.response.Response: if operation is None or operation == "": operation = "getcapabilities" - if operation == "describefeaturetype": - # for DescribeFeatureType we require that exactly one type-name - # is given, otherwise we would have to filter the result - if len(typenames) != 1: - raise HTTPBadRequest("Exactly one type-name must be given for DescribeFeatureType requests") + # for DescribeFeatureType we require that exactly one type-name + # is given, otherwise we would have to filter the result + if operation == "describefeaturetype" and len(typenames) != 1: + raise HTTPBadRequest("Exactly one type-name must be given for DescribeFeatureType requests") if not self._is_allowed(typenames): raise HTTPForbidden("No access rights for at least one of the given type-names") diff --git a/geoportal/c2cgeoportal_geoportal/views/vector_tiles.py b/geoportal/c2cgeoportal_geoportal/views/vector_tiles.py index 9fd929ba9e..78c8062bd4 100644 --- a/geoportal/c2cgeoportal_geoportal/views/vector_tiles.py +++ b/geoportal/c2cgeoportal_geoportal/views/vector_tiles.py @@ -28,6 +28,7 @@ import logging import sqlalchemy +from c2cgeoportal_commons.models import DBSession, main from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.response import Response @@ -35,7 +36,6 @@ from tilecloud import TileCoord from tilecloud.grid.free import FreeTileGrid -from c2cgeoportal_commons.models import DBSession, main from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers _LOG = logging.getLogger(__name__) diff --git a/poetry.lock b/poetry.lock index 1c52cffe09..4f8508aeb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "affine" @@ -2640,6 +2640,7 @@ pylint-flask = "0.6" pyroma = {version = ">=2.4", optional = true, markers = "extra == \"with-pyroma\" or extra == \"with_everything\""} PyYAML = "*" requirements-detector = ">=1.3.2" +ruff = {version = "*", optional = true, markers = "extra == \"with-ruff\" or extra == \"with_everything\""} setoptconf-tmp = ">=0.3.1,<0.4.0" toml = ">=0.10.2,<0.11.0" @@ -2665,13 +2666,13 @@ files = [ [[package]] name = "prospector-profile-utils" -version = "1.13.0" +version = "1.14.0" description = "Some utility Prospector profiles." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "prospector_profile_utils-1.13.0-py3-none-any.whl", hash = "sha256:f991f8f2709c5a2c693d0e0a672a8f3ce2db3f1767b4dc2fbe167395c7dbdbb3"}, - {file = "prospector_profile_utils-1.13.0.tar.gz", hash = "sha256:2f98ddda31c0fa024134fe687e9828855f3597ea52de7a710d1aa508de8fda0a"}, + {file = "prospector_profile_utils-1.14.0-py3-none-any.whl", hash = "sha256:15853e984314f90d688052c7dc09c9206779fdfb7f0606f6a4a617d167c6e693"}, + {file = "prospector_profile_utils-1.14.0.tar.gz", hash = "sha256:258bbe70c7411c7c11a7a7fc202c9a021db3d775a3bf45e7e8bd9f96206bcc48"}, ] [package.dependencies] @@ -3967,6 +3968,33 @@ files = [ {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, ] +[[package]] +name = "ruff" +version = "0.8.1" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"}, + {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"}, + {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"}, + {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"}, + {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"}, + {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"}, + {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"}, + {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"}, + {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"}, +] + [[package]] name = "semver" version = "3.0.2" @@ -4865,4 +4893,4 @@ test = ["zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "7ccfc30f32b7d2dd4599a350c35aea8bda0321ed0831225da7fb247ce3dfa11c" +content-hash = "c7b2c55f651c86d93025cc7ad272652af3e1c7361ecf56e459c6071dbeaebdb0" diff --git a/pyproject.toml b/pyproject.toml index e22e1c6c84..85bb6a8ee3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,5 @@ -[tool.mypy] -python_version = "3.10" -warn_redundant_casts = true -warn_unused_ignores = true -warn_return_any = true -ignore_missing_imports = true -disallow_untyped_defs = true -strict_optional = true -strict = true - -[[tool.mypy.overrides]] -module = "c2cgeoportal_admin.*" -disallow_untyped_defs = false - -[tool.black] -line-length = 110 -target-version = ['py310'] - -[tool.isort] -profile = "black" -line_length = 110 -known_local_folder = ["c2cgeoportal_commons", "c2cgeoportal_geoportal", "c2cgeoportal_admin", "geomapfish_qgisserver", "{{cookiecutter.package}}_geoportal"] +[tool.ruff] +target-version = 'py310' [tool.poetry] name = "c2cgeoportal" @@ -75,9 +55,9 @@ azure-storage-blob = "12.24.0" simple_openid_connect = { git = "https://github.com/sbrunner/py_simple_openid_connect.git", branch = "allows-pkce" } # geoportal pkce = '1.0.3' # geoportal basicauth = "1.0.0" -prospector = { extras = ["with_mypy", "with_bandit", "with_pyroma"], version = "1.13.3" } +prospector = { version = "1.13.3", extras = ["with_mypy", "with_bandit", "with_pyroma", "with_ruff"] } prospector-profile-duplicated = "1.8.0" -prospector-profile-utils = "1.13.0" +prospector-profile-utils = "1.14.0" beautifulsoup4 = "4.12.3" [tool.poetry.group.dev.dependencies] diff --git a/scripts/get-version b/scripts/get-version index 827ea37c3a..0f1eca4af9 100755 --- a/scripts/get-version +++ b/scripts/get-version @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright (c) 2018-2023, Camptocamp SA +# Copyright (c) 2018-2024, Camptocamp SA # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ import argparse import os import re -import subprocess +import subprocess # nosec import sys import requests diff --git a/scripts/updated_version b/scripts/updated_version index 02707db4d3..11dffcd58d 100755 --- a/scripts/updated_version +++ b/scripts/updated_version @@ -29,7 +29,7 @@ import argparse import json -import subprocess +import subprocess # nosec from packaging.version import Version diff --git a/scripts/upgrade b/scripts/upgrade index c523410956..53801b87aa 100755 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright (c) 2019-2023, Camptocamp SA +# Copyright (c) 2019-2024, Camptocamp SA # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ import argparse import os import platform import re -import subprocess +import subprocess # nosec import sys parser = argparse.ArgumentParser(description="Upgrade the project")