diff --git a/.bandit.yaml b/.bandit.yaml deleted file mode 100644 index 6db076a13..000000000 --- a/.bandit.yaml +++ /dev/null @@ -1,3 +0,0 @@ -skips: - - B101 # Use of assert detected. - - B113 # Requests call without timeout. Done internally diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4258dba25..39c94a874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,39 +61,24 @@ repos: rev: v0.1.8 hooks: - id: ripsecrets - - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 hooks: - - id: autoflake - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 - hooks: - - id: pyupgrade - args: - - --py39-plus - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - # isort issue: https://github.com/PyCQA/isort/issues/1889 + - id: ruff-format args: - - --project=c2cwsgiutils - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - exclude: .*\.html + - --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-utils==1.12.2 # 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 730e625d3..8a509aaaf 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -1,11 +1,11 @@ inherits: - utils:base - utils:no-design-checks + - utils:c2cwsgiutils - utils:fix + - utils:unsafe - duplicated -doc-warnings: true - ignore-paths: - docs - acceptance_tests @@ -16,24 +16,3 @@ pylint: extension-pkg-allow-list: - ujson - lxml - disable: - - no-else-return - - no-else-raise - - missing-module-docstring - - missing-timeout # A default timeout is set - -pydocstyle: - disable: - - D104 # Missing docstring in public package - - D105 # Missing docstring in magic method - - D107 # Missing docstring in __init__ - -pycodestyle: - disable: - # Buggy checks with Python 3.12 - - E221 # multiple spaces before operator - - E702 # multiple statements on one line (semicolon) - -bandit: - options: - config: .bandit.yaml diff --git a/c2cwsgiutils/acceptance/__init__.py b/c2cwsgiutils/acceptance/__init__.py index 2a08e32e7..d3f33963f 100644 --- a/c2cwsgiutils/acceptance/__init__.py +++ b/c2cwsgiutils/acceptance/__init__.py @@ -20,6 +20,7 @@ def retry( tries: number of times to try (not retry) before giving up delay: initial delay between retries in seconds backoff: backoff multiplier e.g. value of 2 will double the delay each retry + """ def deco_retry(f: typing.Callable[..., typing.Any]) -> typing.Callable[..., typing.Any]: diff --git a/c2cwsgiutils/acceptance/connection.py b/c2cwsgiutils/acceptance/connection.py index b6938701e..5dce89e93 100644 --- a/c2cwsgiutils/acceptance/connection.py +++ b/c2cwsgiutils/acceptance/connection.py @@ -20,6 +20,7 @@ class Connection: """The connection.""" def __init__(self, base_url: str, origin: str) -> None: + """Initialize the connection.""" self.base_url = base_url if not self.base_url.endswith("/"): self.base_url += "/" @@ -93,10 +94,10 @@ def get_xml( check_response(r, expected_status, cache_expected=cache_expected) self._check_cors(cors, r) r.raw.decode_content = True - doc = etree.parse(r.raw) # nosec + doc = etree.parse(r.raw) # noqa: S320 if schema is not None: with open(schema, encoding="utf-8") as schema_file: - xml_schema = etree.XMLSchema(etree.parse(schema_file)) # nosec + xml_schema = etree.XMLSchema(etree.parse(schema_file)) # noqa: S320 xml_schema.assertValid(doc) return doc diff --git a/c2cwsgiutils/acceptance/image.py b/c2cwsgiutils/acceptance/image.py index 6bb123b5a..b3a745f2e 100644 --- a/c2cwsgiutils/acceptance/image.py +++ b/c2cwsgiutils/acceptance/image.py @@ -89,6 +89,7 @@ def check_image( level: The minimum similarity level (between 0.0 and 1.0), default to 1.0 generate_expected_image: If `True` generate the expected image instead of checking it use_mask: If `False` don't use the mask event if the file exists + """ assert image_to_check is not None, "Image required" image_file_basename = os.path.splitext(os.path.basename(expected_filename))[0] @@ -122,7 +123,7 @@ def check_image( if np.issubdtype(mask.dtype, np.floating): mask = (mask * 255).astype("uint8") - assert ((0 < mask) & (mask < 255)).sum() == 0, "Mask should be only black and white image" + assert ((mask > 0) & (mask < 255)).sum() == 0, "Mask should be only black and white image" # Convert to boolean mask = mask == 0 @@ -139,7 +140,7 @@ def check_image( return if not os.path.isfile(expected_filename): skimage.io.imsave(expected_filename, image_to_check) - assert False, "Expected image not found: " + expected_filename + raise AssertionError("Expected image not found: " + expected_filename) expected = skimage.io.imread(expected_filename) assert expected is not None, "Wrong image: " + expected_filename expected = normalize_image(expected) @@ -201,6 +202,7 @@ def check_screenshot( level: See `check_image` generate_expected_image: See `check_image` use_mask: See `check_image` + """ if headers is None: headers = {} diff --git a/c2cwsgiutils/acceptance/print.py b/c2cwsgiutils/acceptance/print.py index e3b59618a..9ea32898d 100644 --- a/c2cwsgiutils/acceptance/print.py +++ b/c2cwsgiutils/acceptance/print.py @@ -21,6 +21,7 @@ def __init__(self, base_url: str, origin: str) -> None: base_url: The base URL to the print server (including the /print) app: The name of the application to use origin: The origin and referrer to include in the requests + """ super().__init__(base_url=base_url, origin=origin) self.session.headers["Referrer"] = origin diff --git a/c2cwsgiutils/acceptance/utils.py b/c2cwsgiutils/acceptance/utils.py index 60f9b2eff..05af5830e 100644 --- a/c2cwsgiutils/acceptance/utils.py +++ b/c2cwsgiutils/acceptance/utils.py @@ -32,6 +32,7 @@ def retry_timeout(what: Callable[[], Any], timeout: float = _DEFAULT_TIMEOUT, in what: the function to try timeout: the timeout to get a success interval: the interval between try + """ timeout = time.perf_counter() + timeout while True: @@ -46,7 +47,7 @@ def retry_timeout(what: Callable[[], Any], timeout: float = _DEFAULT_TIMEOUT, in error = str(e) _LOG.info(" Failed: %s", e) if time.perf_counter() > timeout: - assert False, "Timeout: " + error + raise AssertionError("Timeout: " + error) time.sleep(interval) diff --git a/c2cwsgiutils/auth.py b/c2cwsgiutils/auth.py index 9e6909d8b..1bc7dafe6 100644 --- a/c2cwsgiutils/auth.py +++ b/c2cwsgiutils/auth.py @@ -12,32 +12,32 @@ from c2cwsgiutils.config_utils import config_bool, env_or_config, env_or_settings _COOKIE_AGE = 7 * 24 * 3600 -SECRET_PROP = "c2c.secret" # nosec # noqa -SECRET_ENV = "C2C_SECRET" # nosec # noqa +SECRET_PROP = "c2c.secret" # noqa: S105 +SECRET_ENV = "C2C_SECRET" # noqa: S105 _GITHUB_REPOSITORY_PROP = "c2c.auth.github.repository" _GITHUB_REPOSITORY_ENV = "C2C_AUTH_GITHUB_REPOSITORY" _GITHUB_ACCESS_TYPE_PROP = "c2c.auth.github.access_type" _GITHUB_ACCESS_TYPE_ENV = "C2C_AUTH_GITHUB_ACCESS_TYPE" GITHUB_AUTH_URL_PROP = "c2c.auth.github.auth_url" GITHUB_AUTH_URL_ENV = "C2C_AUTH_GITHUB_AUTH_URL" -GITHUB_TOKEN_URL_PROP = "c2c.auth.github.token_url" # nosec -GITHUB_TOKEN_URL_ENV = "C2C_AUTH_GITHUB_TOKEN_URL" # nosec +GITHUB_TOKEN_URL_PROP = "c2c.auth.github.token_url" # noqa: S105 +GITHUB_TOKEN_URL_ENV = "C2C_AUTH_GITHUB_TOKEN_URL" # noqa: S105 GITHUB_USER_URL_PROP = "c2c.auth.github.user_url" GITHUB_USER_URL_ENV = "C2C_AUTH_GITHUB_USER_URL" _GITHUB_REPO_URL_PROP = "c2c.auth.github.repo_url" _GITHUB_REPO_URL_ENV = "C2C_AUTH_GITHUB_REPO_URL" GITHUB_CLIENT_ID_PROP = "c2c.auth.github.client_id" GITHUB_CLIENT_ID_ENV = "C2C_AUTH_GITHUB_CLIENT_ID" -GITHUB_CLIENT_SECRET_PROP = "c2c.auth.github.client_secret" # nosec # noqa -GITHUB_CLIENT_SECRET_ENV = "C2C_AUTH_GITHUB_CLIENT_SECRET" # nosec # noqa +GITHUB_CLIENT_SECRET_PROP = "c2c.auth.github.client_secret" # noqa: S105 +GITHUB_CLIENT_SECRET_ENV = "C2C_AUTH_GITHUB_CLIENT_SECRET" # noqa: S105 GITHUB_SCOPE_PROP = "c2c.auth.github.scope" GITHUB_SCOPE_ENV = "C2C_AUTH_GITHUB_SCOPE" # To be able to use private repository GITHUB_SCOPE_DEFAULT = "repo" GITHUB_AUTH_COOKIE_PROP = "c2c.auth.github.auth.cookie" GITHUB_AUTH_COOKIE_ENV = "C2C_AUTH_GITHUB_COOKIE" -GITHUB_AUTH_SECRET_PROP = "c2c.auth.github.auth.secret" # nosec # noqa -GITHUB_AUTH_SECRET_ENV = "C2C_AUTH_GITHUB_SECRET" # nosec # noqa +GITHUB_AUTH_SECRET_PROP = "c2c.auth.github.auth.secret" # noqa: S105 +GITHUB_AUTH_SECRET_ENV = "C2C_AUTH_GITHUB_SECRET" # noqa: S105 GITHUB_AUTH_PROXY_URL_PROP = "c2c.auth.github.auth.proxy_url" GITHUB_AUTH_PROXY_URL_ENV = "C2C_AUTH_GITHUB_PROXY_URL" USE_SESSION_PROP = "c2c.use_session" @@ -209,6 +209,7 @@ def check_access( request: is the request object. repo: is the repository to check access to (/). access_type: is the type of access to check (admin|push|pull). + """ if not is_auth(request): return False diff --git a/c2cwsgiutils/broadcast/__init__.py b/c2cwsgiutils/broadcast/__init__.py index aa8350cef..a84aa1ff4 100644 --- a/c2cwsgiutils/broadcast/__init__.py +++ b/c2cwsgiutils/broadcast/__init__.py @@ -19,7 +19,7 @@ def init(config: Optional[pyramid.config.Configurator] = None) -> None: """Initialize the broadcaster with Redis, if configured, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/broadcast/local.py b/c2cwsgiutils/broadcast/local.py index 6b4dbd837..4032f7f77 100644 --- a/c2cwsgiutils/broadcast/local.py +++ b/c2cwsgiutils/broadcast/local.py @@ -9,17 +9,21 @@ class LocalBroadcaster(interface.BaseBroadcaster): """Fake implementation of broadcasting messages (will just answer locally).""" def __init__(self) -> None: + """Initialize the broadcaster.""" self._subscribers: MutableMapping[str, Callable[..., Any]] = {} def subscribe(self, channel: str, callback: Callable[..., Any]) -> None: + """Subscribe to a channel.""" self._subscribers[channel] = callback def unsubscribe(self, channel: str) -> None: + """Unsubscribe from a channel.""" del self._subscribers[channel] def broadcast( self, channel: str, params: Mapping[str, Any], expect_answers: bool, timeout: float ) -> Optional[list[Any]]: + """Broadcast a message to all the listeners.""" subscriber = self._subscribers.get(channel, None) answers = [utils.add_host_info(subscriber(**params))] if subscriber is not None else [] return answers if expect_answers else None diff --git a/c2cwsgiutils/broadcast/redis.py b/c2cwsgiutils/broadcast/redis.py index 173b4add6..fdb8e6151 100644 --- a/c2cwsgiutils/broadcast/redis.py +++ b/c2cwsgiutils/broadcast/redis.py @@ -23,6 +23,7 @@ def __init__( master: "redis.client.Redis[str]", slave: "redis.client.Redis[str]", ) -> None: + """Initialize the broadcaster.""" from c2cwsgiutils import redis_utils # pylint: disable=import-outside-toplevel self._master = master @@ -40,6 +41,8 @@ def _get_channel(self, channel: str) -> str: return self._broadcast_prefix + channel def subscribe(self, channel: str, callback: Callable[..., Any]) -> None: + """Subscribe to a channel.""" + def wrapper(message: Mapping[str, Any]) -> None: _LOG.debug("Received a broadcast on %s: %s", message["channel"], repr(message["data"])) data = json.loads(message["data"]) @@ -58,6 +61,7 @@ def wrapper(message: Mapping[str, Any]) -> None: self._pub_sub.subscribe(**{actual_channel: wrapper}) def unsubscribe(self, channel: str) -> None: + """Unsubscribe from a channel.""" _LOG.debug("Unsubscribing from %s") actual_channel = self._get_channel(channel) self._pub_sub.unsubscribe(actual_channel) @@ -65,6 +69,7 @@ def unsubscribe(self, channel: str) -> None: def broadcast( self, channel: str, params: Mapping[str, Any], expect_answers: bool, timeout: float ) -> Optional[list[Any]]: + """Broadcast a message to all the listeners.""" if expect_answers: return self._broadcast_with_answer(channel, params, timeout) else: @@ -85,7 +90,8 @@ def callback(msg: Mapping[str, Any]) -> None: cond.notify() answer_channel = self._get_channel(channel) + "".join( - random.choice(string.ascii_uppercase + string.digits) for _ in range(10) # nosec + random.choice(string.ascii_uppercase + string.digits) # noqa: S311 + for _ in range(10) ) _LOG.debug("Subscribing for broadcast answers on %s", answer_channel) self._pub_sub.subscribe(**{answer_channel: callback}) @@ -98,7 +104,7 @@ def callback(msg: Mapping[str, Any]) -> None: with cond: while len(answers) < nb_received: to_wait = timeout_time - time.perf_counter() - if to_wait <= 0.0: # pragma: no cover + if to_wait <= 0.0: _LOG.warning( "timeout waiting for %d/%d answers on %s", len(answers), diff --git a/c2cwsgiutils/client_info.py b/c2cwsgiutils/client_info.py index de8c0b014..8d4e3c723 100644 --- a/c2cwsgiutils/client_info.py +++ b/c2cwsgiutils/client_info.py @@ -16,9 +16,11 @@ class Filter: """ def __init__(self, application: Callable[[dict[str, str], Any], Any]): + """Initialize the filter.""" self._application = application def __call__(self, environ: dict[str, str], start_response: Any) -> Any: + """Update the environ with the headers.""" # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded if "HTTP_FORWARDED" in environ: _handle_forwarded(environ) diff --git a/c2cwsgiutils/coverage_setup.py b/c2cwsgiutils/coverage_setup.py index 59df37037..c9a03409a 100644 --- a/c2cwsgiutils/coverage_setup.py +++ b/c2cwsgiutils/coverage_setup.py @@ -10,7 +10,7 @@ def init() -> None: """Initialize the code coverage, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme() @@ -22,7 +22,7 @@ def includeme(config: Optional[pyramid.config.Configurator] = None) -> None: import coverage # pylint: disable=import-outside-toplevel _LOG.warning("Setting up code coverage") - report_dir = "/tmp/coverage/api" # nosec + report_dir = "/tmp/coverage/api" # noqa: S108 os.makedirs(report_dir, exist_ok=True) cov = coverage.Coverage( data_file=os.path.join(report_dir, "coverage"), diff --git a/c2cwsgiutils/db.py b/c2cwsgiutils/db.py index 7b6c3a818..310db15b6 100644 --- a/c2cwsgiutils/db.py +++ b/c2cwsgiutils/db.py @@ -66,8 +66,11 @@ def setup_session( force_slave: The method/paths that needs to use the slave Returns: The SQLAlchemy session, the R/W engine and the R/O engine + """ - warnings.warn("setup_session function is deprecated; use init and request.dbsession instead") + warnings.warn( + "setup_session function is deprecated; use init and request.dbsession instead", stacklevel=2 + ) if slave_prefix is None: slave_prefix = master_prefix settings = config.registry.settings @@ -122,8 +125,11 @@ def create_session( engine_config: The rest of the parameters are passed as is to the sqlalchemy.create_engine function Returns: The SQLAlchemy session + """ - warnings.warn("create_session function is deprecated; use init and request.dbsession instead") + warnings.warn( + "create_session function is deprecated; use init and request.dbsession instead", stacklevel=2 + ) if slave_url is None: slave_url = url @@ -215,6 +221,7 @@ def __init__( ro_engine: sqlalchemy.engine.Engine, rw_engine: sqlalchemy.engine.Engine, ): + """Initialize the session factory.""" super().__init__() self.master_paths: Iterable[Pattern[str]] = ( list(map(_RE_COMPILE, force_master)) if force_master else [] @@ -232,6 +239,7 @@ def engine_name(self, readwrite: bool) -> str: def __call__( # type: ignore self, request: Optional[pyramid.request.Request], readwrite: Optional[bool] = None, **local_kw: Any ) -> _scoped_session: + """Set the engine based on the request.""" if readwrite is not None: if readwrite and not FORCE_READONLY: _LOG.debug("Using %s database", self.rw_engine.c2c_name) # type: ignore @@ -374,6 +382,7 @@ def init( force_slave: The method/paths that needs to use the slave Returns: The SQLAlchemy session + """ settings = config.get_settings() settings["tm.manager_hook"] = "pyramid_tm.explicit_manager" diff --git a/c2cwsgiutils/db_maintenance_view.py b/c2cwsgiutils/db_maintenance_view.py index 21822025f..b9f92e743 100644 --- a/c2cwsgiutils/db_maintenance_view.py +++ b/c2cwsgiutils/db_maintenance_view.py @@ -15,7 +15,7 @@ def install_subscriber(config: pyramid.config.Configurator) -> None: """Install the view to configure the loggers, if configured to do so, for backward compatibility.""" - warnings.warn("install_subscriber function is deprecated; use includeme instead") + warnings.warn("install_subscriber function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/debug/__init__.py b/c2cwsgiutils/debug/__init__.py index 7b3b1b97e..b88f9585a 100644 --- a/c2cwsgiutils/debug/__init__.py +++ b/c2cwsgiutils/debug/__init__.py @@ -16,7 +16,7 @@ def init(config: pyramid.config.Configurator) -> None: """Initialize the debug tools, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) @@ -37,6 +37,8 @@ def init_daemon(config: Optional[pyramid.config.Configurator] = None) -> None: those requests. """ if config_utils.env_or_config(config, ENV_KEY, CONFIG_KEY, type_=config_utils.config_bool): - from c2cwsgiutils.debug import _listeners # pylint: disable=import-outside-toplevel + from c2cwsgiutils.debug import ( + _listeners, # pylint: disable=import-outside-toplevel + ) _listeners.init() diff --git a/c2cwsgiutils/debug/_views.py b/c2cwsgiutils/debug/_views.py index 8f16f4f5d..f52ae8653 100644 --- a/c2cwsgiutils/debug/_views.py +++ b/c2cwsgiutils/debug/_views.py @@ -79,7 +79,7 @@ def _dump_memory_diff(request: pyramid.request.Request) -> list[Any]: try: if request.params.get("no_warmup", "0").lower() in ("1", "true", "on"): request.invoke_subrequest(sub_request) - except Exception: # nosec # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except pass _LOG.debug("checking memory growth for %s", path) diff --git a/c2cwsgiutils/errors.py b/c2cwsgiutils/errors.py index 0dd69b212..0f147200b 100644 --- a/c2cwsgiutils/errors.py +++ b/c2cwsgiutils/errors.py @@ -9,7 +9,12 @@ import pyramid.request import sqlalchemy.exc from cornice import cors -from pyramid.httpexceptions import HTTPError, HTTPException, HTTPRedirection, HTTPSuccessful +from pyramid.httpexceptions import ( + HTTPError, + HTTPException, + HTTPRedirection, + HTTPSuccessful, +) from webob.request import DisconnectionError from c2cwsgiutils import auth, config_utils @@ -152,7 +157,7 @@ def _passthrough(exception: HTTPException, request: pyramid.request.Request) -> def init(config: pyramid.config.Configurator) -> None: """Initialize the error views, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/health_check.py b/c2cwsgiutils/health_check.py index 643e87c01..5cfb23333 100644 --- a/c2cwsgiutils/health_check.py +++ b/c2cwsgiutils/health_check.py @@ -67,6 +67,7 @@ class JsonCheckException(Exception): """Checker exception used to add some structured content to a failure.""" def __init__(self, message: str, json: Any): + """Initialize the exception.""" super().__init__() self.message = message self.json = json @@ -199,6 +200,7 @@ class HealthCheck: """ def __init__(self, config: pyramid.config.Configurator) -> None: + """Initialize the health check view.""" config.add_route( "c2c_health_check", config_utils.get_base_path(config) + r"/health_check", request_method="GET" ) @@ -235,6 +237,7 @@ def add_db_session_check( engine_type: whether to check only the RW, RO or both engines rw_engin: the RW engine to use (if None, use the session one) ro_engin: the RO engine to use (if None, use the session one) + """ if query_cb is None: query_cb = self._at_least_one(at_least_one_model) @@ -265,6 +268,7 @@ def add_alembic_check( version_table: override the table name for the version rw_engin: the RW engine to use (if None, use the session one) ro_engin: the RO engine to use (if None, use the session one) + """ version_ = _get_alembic_version(alembic_ini_path, name) @@ -287,26 +291,28 @@ def __call__(self, request: pyramid.request.Request) -> str: assert version_schema assert version_table for binding in _get_bindings(self.session, EngineType.READ_AND_WRITE): - with binding as binded_session: - with _PROMETHEUS_DB_SUMMARY.labels( + with ( + binding as binded_session, + _PROMETHEUS_DB_SUMMARY.labels( configuration=alembic_ini_path, connection=binding.name(), check="alembic" - ).time(): - result = binded_session.execute( - sqlalchemy.text( - "SELECT version_num FROM " # nosec - f"{sqlalchemy.sql.quoted_name(version_schema, True)}." - f"{sqlalchemy.sql.quoted_name(version_table, True)}" - ) - ).fetchone() - assert result is not None - (actual_version,) = result - _PROMETHEUS_ALEMBIC_VERSION.labels( - version=actual_version, name=name, configuration=alembic_ini_path - ).set(1) - if actual_version != version_: - raise Exception( # pylint: disable=broad-exception-raised - f"Invalid alembic version (db: {actual_version}, code: {version_})" - ) + ).time(), + ): + result = binded_session.execute( + sqlalchemy.text( + "SELECT version_num FROM " # noqa: S608 + f"{sqlalchemy.sql.quoted_name(version_schema, True)}." + f"{sqlalchemy.sql.quoted_name(version_table, True)}" + ) + ).fetchone() + assert result is not None + (actual_version,) = result + _PROMETHEUS_ALEMBIC_VERSION.labels( + version=actual_version, name=name, configuration=alembic_ini_path + ).set(1) + if actual_version != version_: + raise Exception( # pylint: disable=broad-exception-raised + f"Invalid alembic version (db: {actual_version}, code: {version_})" + ) return version_ self._checks.append( @@ -321,9 +327,8 @@ def add_url_check( Mapping[str, str], Callable[[pyramid.request.Request], Mapping[str, str]], None ] = None, name: Optional[str] = None, - check_cb: Callable[ - [pyramid.request.Request, requests.Response], Any - ] = lambda request, response: None, + check_cb: Callable[[pyramid.request.Request, requests.Response], Any] = lambda request, + response: None, timeout: float = 3, level: int = 1, ) -> None: @@ -339,6 +344,7 @@ def add_url_check( response as parameters) timeout: the timeout level: the level of the health check + """ def check(request: pyramid.request.Request) -> Any: @@ -365,6 +371,7 @@ def add_redis_check(self, name: Optional[str] = None, level: int = 1) -> None: Arguments: name: the name of the check (defaults to url) level: the level of the health check + """ def check(request: pyramid.request.Request) -> Any: @@ -413,6 +420,7 @@ def add_version_check(self, name: str = "version", level: int = 2) -> None: Arguments: name: the name of the check (defaults to "version") level: the level of the health check + """ def check(request: pyramid.request.Request) -> dict[str, Any]: @@ -441,6 +449,7 @@ def add_custom_check( name: the name of the check check_cb: the callback to call (takes the request as parameter) level: the level of the health check + """ assert name self._checks.append((name, check_cb, level)) @@ -453,9 +462,8 @@ def _view(self, request: pyramid.request.Request) -> Mapping[str, Any]: "successes": {}, } checks = None - if "checks" in request.params: - if request.params["checks"] != "": - checks = request.params["checks"].split(",") + if "checks" in request.params and request.params["checks"] != "": + checks = request.params["checks"].split(",") for name, check, level in self._checks: if level <= max_level and (checks is None or name in checks): self._run_one(check, is_auth, level, name, request, results) @@ -498,11 +506,13 @@ def _create_db_engine_check( ) -> tuple[str, Callable[[pyramid.request.Request], None]]: def check(request: pyramid.request.Request) -> None: del request # unused - with binding as session: - with _PROMETHEUS_DB_SUMMARY.labels( + with ( + binding as session, + _PROMETHEUS_DB_SUMMARY.labels( connection=binding.name(), check="database", configuration="" - ).time(): - return query_cb(session) + ).time(), + ): + return query_cb(session) return "db_engine_" + binding.name(), check diff --git a/c2cwsgiutils/index.py b/c2cwsgiutils/index.py index 688f46cdc..62cfdb38f 100644 --- a/c2cwsgiutils/index.py +++ b/c2cwsgiutils/index.py @@ -501,7 +501,7 @@ def _github_logout(request: pyramid.request.Request) -> dict[str, Any]: def init(config: pyramid.config.Configurator) -> None: """Initialize the index page, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/logging_view.py b/c2cwsgiutils/logging_view.py index 3fa968f39..75888624e 100644 --- a/c2cwsgiutils/logging_view.py +++ b/c2cwsgiutils/logging_view.py @@ -15,7 +15,7 @@ def install_subscriber(config: pyramid.config.Configurator) -> None: """Install the view to configure the loggers, if configured to do so, for backward compatibility.""" - warnings.warn("install_subscriber function is deprecated; use includeme instead") + warnings.warn("install_subscriber function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/models_graph.py b/c2cwsgiutils/models_graph.py index fc92e580f..de5c248d0 100644 --- a/c2cwsgiutils/models_graph.py +++ b/c2cwsgiutils/models_graph.py @@ -44,7 +44,7 @@ def _generate_model_graph(module: Any, base: Any) -> None: def _print_node(symbol: Any, interesting: set[Any]) -> None: print(f'{symbol.__name__} [label="{_get_table_desc(symbol)}", shape=box];') for parent in symbol.__bases__: - if parent != object: + if parent is not object: if parent not in interesting: _print_node(parent, interesting) interesting.add(parent) diff --git a/c2cwsgiutils/pretty_json.py b/c2cwsgiutils/pretty_json.py index 67bdede45..a407b1a4c 100644 --- a/c2cwsgiutils/pretty_json.py +++ b/c2cwsgiutils/pretty_json.py @@ -28,7 +28,7 @@ def __call__(self, v: Any, **_kargv: Any) -> str: def init(config: pyramid.config.Configurator) -> None: """Initialize json and fast_json renderer, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/profiler.py b/c2cwsgiutils/profiler.py index 9095552d3..aa00a8a26 100644 --- a/c2cwsgiutils/profiler.py +++ b/c2cwsgiutils/profiler.py @@ -9,6 +9,7 @@ class Profile(contextlib.ContextDecorator): """Used to profile a function with a decorator or with a with statement.""" def __init__(self, path: str, print_number: int = 0) -> None: + """Initialize the profiler.""" self.path = path self.print_number = print_number self.pr = cProfile.Profile() diff --git a/c2cwsgiutils/prometheus.py b/c2cwsgiutils/prometheus.py index 5b654eade..8e16d32f6 100644 --- a/c2cwsgiutils/prometheus.py +++ b/c2cwsgiutils/prometheus.py @@ -115,6 +115,7 @@ class MultiProcessCustomCollector(prometheus_client.registry.Collector): """Get the metrics from the custom collectors.""" def collect(self) -> Generator[prometheus_client.core.Metric, None, None]: + """Get the metrics from the custom collectors.""" results: list[list[SerializedMetric]] = [] for channel in MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS: result = broadcast.broadcast(channel, expect_answers=True) @@ -159,6 +160,7 @@ def __init__(self, memory_type: str = "pss", pids: Optional[list[str]] = None): Arguments: memory_type: can be rss, pss or size pids: the list of pids or none + """ super().__init__() self.memory_type = memory_type diff --git a/c2cwsgiutils/pyramid.py b/c2cwsgiutils/pyramid.py index 7c3bdf7b2..42efa038b 100644 --- a/c2cwsgiutils/pyramid.py +++ b/c2cwsgiutils/pyramid.py @@ -31,6 +31,7 @@ def includeme(config: pyramid.config.Configurator) -> None: Arguments: config: The pyramid Configuration + """ logging.captureWarnings(True) config.include(coverage_setup.includeme) diff --git a/c2cwsgiutils/pyramid_logging.py b/c2cwsgiutils/pyramid_logging.py index fb6d3da42..045835aa8 100644 --- a/c2cwsgiutils/pyramid_logging.py +++ b/c2cwsgiutils/pyramid_logging.py @@ -87,10 +87,12 @@ class PyramidCeeSysLogHandler(cee_syslog_handler.CeeSysLogHandler): # type: ign """A CEE (JSON format) log handler with additional information about the current request.""" def __init__(self, *args: Any, **kargv: Any) -> None: + """Initialize the handler.""" super().__init__(*args, **kargv) self.addFilter(_PYRAMID_FILTER) def format(self, record: Any) -> str: + """Format the record into a CEE string.""" message = _make_message_dict( record, self._fqdn, @@ -112,11 +114,13 @@ class JsonLogHandler(Base): """Log to stdout in JSON.""" def __init__(self, stream: Optional[TextIO] = None): + """Initialize the handler.""" super().__init__(stream) self.addFilter(_PYRAMID_FILTER) self._fqdn = socket.getfqdn() def format(self, record: Any) -> str: + """Format the record into a JSON string.""" message = _make_message_dict( record, self._fqdn, debugging_fields=True, extra_fields=True, facility=None, static_fields={} ) diff --git a/c2cwsgiutils/redis_stats.py b/c2cwsgiutils/redis_stats.py index 552005b2d..a5a73c778 100644 --- a/c2cwsgiutils/redis_stats.py +++ b/c2cwsgiutils/redis_stats.py @@ -26,7 +26,7 @@ def _execute_command_patch(self: Any, command: str, *args: Any, **options: Any) def init(config: Optional[pyramid.config.Configurator] = None) -> None: """Initialize the Redis tracking, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/redis_utils.py b/c2cwsgiutils/redis_utils.py index 0675b1fe0..182aefa57 100644 --- a/c2cwsgiutils/redis_utils.py +++ b/c2cwsgiutils/redis_utils.py @@ -100,11 +100,13 @@ class PubSubWorkerThread(threading.Thread): """A clone of redis.client.PubSubWorkerThread that doesn't die when the connections are broken.""" def __init__(self, pubsub: redis.client.PubSub, name: Optional[str] = None) -> None: + """Initialize the PubSubWorkerThread.""" super().__init__(name=name, daemon=True) self.pubsub = pubsub self._running = False def run(self) -> None: + """Run the worker.""" if self._running: return self._running = True diff --git a/c2cwsgiutils/request_tracking/__init__.py b/c2cwsgiutils/request_tracking/__init__.py index 4a86c7ca7..a5369cc33 100644 --- a/c2cwsgiutils/request_tracking/__init__.py +++ b/c2cwsgiutils/request_tracking/__init__.py @@ -83,7 +83,7 @@ def send_wrapper( def init(config: Optional[pyramid.config.Configurator] = None) -> None: """Initialize the request tracking, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/scripts/genversion.py b/c2cwsgiutils/scripts/genversion.py index 84669847c..2f1614f75 100755 --- a/c2cwsgiutils/scripts/genversion.py +++ b/c2cwsgiutils/scripts/genversion.py @@ -24,7 +24,7 @@ def _get_package_version(comp: str) -> tuple[Optional[str], Optional[str]]: if matcher: return cast(tuple[str, str], matcher.groups()) else: - if len(comp) > 0 and not comp[:3] == "-e ": + if len(comp) > 0 and comp[:3] != "-e ": print("Cannot parse package version: " + comp) return None, None @@ -46,7 +46,9 @@ def _get_packages_version() -> dict[str, str]: def deprecated() -> None: """Run the command and print a deprecated notice.""" - warnings.warn("c2cwsgiutils_genversion.py is deprecated; use c2cwsgiutils-genversion instead") + warnings.warn( + "c2cwsgiutils_genversion.py is deprecated; use c2cwsgiutils-genversion instead", stacklevel=2 + ) return main() diff --git a/c2cwsgiutils/scripts/stats_db.py b/c2cwsgiutils/scripts/stats_db.py index 28d55fca1..a9d88e23d 100755 --- a/c2cwsgiutils/scripts/stats_db.py +++ b/c2cwsgiutils/scripts/stats_db.py @@ -62,6 +62,7 @@ class Reporter: """The stats reporter.""" def __init__(self, args: argparse.Namespace) -> None: + """Initialize the reporter.""" self._error: Optional[Exception] = None self.registry = CollectorRegistry() self.prometheus_push = args.prometheus_url is not None diff --git a/c2cwsgiutils/scripts/test_print.py b/c2cwsgiutils/scripts/test_print.py index bcdab60ca..9d8cfdcd5 100755 --- a/c2cwsgiutils/scripts/test_print.py +++ b/c2cwsgiutils/scripts/test_print.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Test a MapfishPrint server.""" + import argparse import logging import pprint @@ -23,7 +24,9 @@ def _parse_args() -> argparse.Namespace: def deprecated() -> None: """Run the command and print a deprecated notice.""" - warnings.warn("c2cwsgiutils_test_print.py is deprecated; use c2cwsgiutils-test-print instead") + warnings.warn( + "c2cwsgiutils_test_print.py is deprecated; use c2cwsgiutils-test-print instead", stacklevel=2 + ) return main() diff --git a/c2cwsgiutils/sentry.py b/c2cwsgiutils/sentry.py index e6213cdde..ca51b01a6 100644 --- a/c2cwsgiutils/sentry.py +++ b/c2cwsgiutils/sentry.py @@ -33,7 +33,7 @@ def do_filter(event: Any, hint: Any) -> Any: def init(config: Optional[pyramid.config.Configurator] = None) -> None: """Initialize the Sentry integration, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/setup_process.py b/c2cwsgiutils/setup_process.py index b0ccec6b7..e099b90f5 100644 --- a/c2cwsgiutils/setup_process.py +++ b/c2cwsgiutils/setup_process.py @@ -55,7 +55,9 @@ def init(config_file: str = "c2c:///app/production.ini") -> None: def init_logging(config_file: str = "c2c:///app/production.ini") -> None: """Initialize the non-WSGI application.""" - warnings.warn("init_logging function is deprecated; use init instead so that all features are enabled") + warnings.warn( + "init_logging function is deprecated; use init instead so that all features are enabled", stacklevel=2 + ) loader = get_config_loader(config_file) loader.setup_logging(None) diff --git a/c2cwsgiutils/sql_profiler/__init__.py b/c2cwsgiutils/sql_profiler/__init__.py index c52d77764..a12ca4f9b 100644 --- a/c2cwsgiutils/sql_profiler/__init__.py +++ b/c2cwsgiutils/sql_profiler/__init__.py @@ -18,7 +18,7 @@ def init(config: pyramid.config.Configurator) -> None: """Install a pyramid event handler that adds the request information, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/c2cwsgiutils/sql_profiler/_impl.py b/c2cwsgiutils/sql_profiler/_impl.py index a05754f8c..aa7ad18fa 100644 --- a/c2cwsgiutils/sql_profiler/_impl.py +++ b/c2cwsgiutils/sql_profiler/_impl.py @@ -56,7 +56,7 @@ def profile( ] ) _LOG.info(output) - except Exception: # nosec # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # noqa: S110 pass diff --git a/c2cwsgiutils/sqlalchemylogger/handlers.py b/c2cwsgiutils/sqlalchemylogger/handlers.py index e5318c407..074e05ec0 100644 --- a/c2cwsgiutils/sqlalchemylogger/handlers.py +++ b/c2cwsgiutils/sqlalchemylogger/handlers.py @@ -11,7 +11,10 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy_utils import create_database, database_exists -from c2cwsgiutils.sqlalchemylogger._filters import ContainsExpression, DoesNotContainExpression +from c2cwsgiutils.sqlalchemylogger._filters import ( + ContainsExpression, + DoesNotContainExpression, +) from c2cwsgiutils.sqlalchemylogger._models import Base, create_log_class _LOG = logging.getLogger(__name__) @@ -29,12 +32,13 @@ def __init__( does_not_contain_expression: str = "", contains_expression: str = "", ) -> None: + """Initialize the SQLAlchemyHandler.""" super().__init__() # Initialize DB session self.engine = create_engine(sqlalchemy_url["url"]) self.Log = create_log_class( # pylint: disable=invalid-name tablename=sqlalchemy_url.get("tablename", "logs"), - tableargs=sqlalchemy_url.get("tableargs", None), # type: ignore + tableargs=sqlalchemy_url.get("tableargs"), # type: ignore ) Base.metadata.bind = self.engine self.session = sessionmaker(bind=self.engine)() # noqa @@ -61,15 +65,16 @@ def _processor(self) -> None: if not self.log_queue.empty(): logs.append(self.log_queue.get()) self.log_queue.task_done() - if logs: - # try to reduce the number of INSERT requests to the DB - # by writing chunks of self.MAX_NB_LOGS size, - # but also do not wait forever before writing stuff (self.MAX_TIMOUT) - if (len(logs) >= self.MAX_NB_LOGS) or ( - time.perf_counter() >= (time_since_last + self.MAX_TIMEOUT) - ): - self._write_logs(logs) - break + # try to reduce the number of INSERT requests to the DB + # by writing chunks of self.MAX_NB_LOGS size, + # but also do not wait forever before writing stuff (self.MAX_TIMOUT) + if ( + logs + and (len(logs) >= self.MAX_NB_LOGS) + or (time.perf_counter() >= (time_since_last + self.MAX_TIMEOUT)) + ): + self._write_logs(logs) + break _LOG.debug("%s: stopping processor thread", __name__) def _write_logs(self, logs: list[Any]) -> None: @@ -107,6 +112,7 @@ def create_db(self) -> None: Base.metadata.create_all(self.engine) def emit(self, record: Any) -> None: + """Emit the log.""" trace = None exc = record.__dict__["exc_info"] if exc: diff --git a/c2cwsgiutils/stats_pyramid/__init__.py b/c2cwsgiutils/stats_pyramid/__init__.py index 2a25a7bce..f721139e8 100644 --- a/c2cwsgiutils/stats_pyramid/__init__.py +++ b/c2cwsgiutils/stats_pyramid/__init__.py @@ -10,7 +10,7 @@ def init(config: pyramid.config.Configurator) -> None: """Initialize the whole stats module, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) @@ -20,6 +20,7 @@ def includeme(config: pyramid.config.Configurator) -> None: Arguments: config: The Pyramid config + """ _pyramid_spy.init(config) init_db_spy() diff --git a/c2cwsgiutils/stats_pyramid/_pyramid_spy.py b/c2cwsgiutils/stats_pyramid/_pyramid_spy.py index c95c66a45..c2b844ca4 100644 --- a/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +++ b/c2cwsgiutils/stats_pyramid/_pyramid_spy.py @@ -99,6 +99,7 @@ def init(config: pyramid.config.Configurator) -> None: # pragma: nocover Arguments: config: The Pyramid config + """ config.add_subscriber(_request_callback, pyramid.events.NewRequest) config.add_subscriber(_before_rendered_callback, pyramid.events.BeforeRender) diff --git a/c2cwsgiutils/version.py b/c2cwsgiutils/version.py index 1536236eb..25aafbf68 100644 --- a/c2cwsgiutils/version.py +++ b/c2cwsgiutils/version.py @@ -37,7 +37,7 @@ def _sanitize_label(label: str) -> str: def init(config: pyramid.config.Configurator) -> None: """Initialize the versions view, for backward compatibility.""" - warnings.warn("init function is deprecated; use includeme instead") + warnings.warn("init function is deprecated; use includeme instead", stacklevel=2) includeme(config) diff --git a/poetry.lock b/poetry.lock index 7cda8b66f..519d20e4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1582,6 +1582,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" @@ -1596,13 +1597,13 @@ with-vulture = ["vulture (>=1.5)"] [[package]] name = "prospector-profile-duplicated" -version = "1.8.0" +version = "1.8.1" description = "Profile that can be used to disable the duplicated or conflict rules between Prospector and other tools" optional = false python-versions = "*" files = [ - {file = "prospector_profile_duplicated-1.8.0-py2.py3-none-any.whl", hash = "sha256:c36ca647848739e1ca52cf220b00ba1fbdf7105217d5534a5bf2e65279ad6011"}, - {file = "prospector_profile_duplicated-1.8.0.tar.gz", hash = "sha256:1565109b016fac05c61dbce38b952bfce3ff054d0c5493d1acad1fc8e9909704"}, + {file = "prospector_profile_duplicated-1.8.1-py2.py3-none-any.whl", hash = "sha256:a3c23517b9749ea0e61cc9b23d1526752c1eeb6c899cd0d9ec1fa77b1f373a49"}, + {file = "prospector_profile_duplicated-1.8.1.tar.gz", hash = "sha256:a08fa1f5aa77bdbc908b0c4f66b2bdb306aba10c44c5db51e389cd5cf79bda04"}, ] [[package]] @@ -2167,6 +2168,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 = "scikit-image" version = "0.24.0" @@ -3044,4 +3072,4 @@ webserver = ["SQLAlchemy", "SQLAlchemy-Utils", "cornice", "gunicorn", "prometheu [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "b2092bc0272300c7b2e64d8b6d7487ca6c67b889fb2b881f0c459440d8350849" +content-hash = "8b22329076a7b97a870ddc5101b89a5b9edf4e322c94e3320b1e59d40a9dc84d" diff --git a/pyproject.toml b/pyproject.toml index 64208d42b..49e76f197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -172,8 +172,8 @@ test_images = ["scikit-image"] [tool.poetry.group.dev.dependencies] # pylint = { version = "2.15.6" } -prospector = { extras = ["with_bandit", "with_mypy", "with_pyroma"], version = "1.13.3" } -prospector-profile-duplicated = "1.8.0" +prospector = { version = "1.13.3", extras = ["with_bandit", "with_mypy", "with_pyroma", "with_ruff"] } +prospector-profile-duplicated = "1.8.1" prospector-profile-utils = "1.14.0" coverage = "7.6.8" junit2html = "31.0.2"