Skip to content

Commit

Permalink
Merge pull request #162 from lsst/tickets/DM-39785
Browse files Browse the repository at this point in the history
DM-39785: Package modernization and ruff
  • Loading branch information
timj committed Jun 26, 2023
2 parents 87fa34d + 7607e8a commit 3943c6b
Show file tree
Hide file tree
Showing 39 changed files with 322 additions and 200 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ on:
jobs:
call-workflow:
uses: lsst/rubin_workflows/.github/workflows/lint.yaml@main
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1
7 changes: 4 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ repos:
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.275
hooks:
- id: flake8
- id: ruff
40 changes: 40 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,43 @@ exclude_lines = [
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]

[tool.ruff]
exclude = [
"__init__.py",
]
ignore = [
"N802",
"N803",
"N806",
"N812",
"N815",
"N816",
"N999",
"D107",
"D105",
"D102",
"D104",
"D100",
"D200",
"D205",
"D400",
]
line-length = 110
select = [
"E", # pycodestyle
"F", # pyflakes
"N", # pep8-naming
"W", # pycodestyle
"D", # pydocstyle
]
target-version = "py310"
extend-select = [
"RUF100", # Warn about unused noqa
]

[tool.ruff.pycodestyle]
max-doc-length = 79

[tool.ruff.pydocstyle]
convention = "numpy"
2 changes: 2 additions & 0 deletions python/lsst/utils/_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

"""Functions to help find packages."""

from __future__ import annotations

__all__ = ("getPackageDir",)

import os
Expand Down
7 changes: 4 additions & 3 deletions python/lsst/utils/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
__all__ = ["Singleton", "cached_getter", "immutable"]

import functools
from typing import Any, Callable, Dict, Type, TypeVar
from collections.abc import Callable
from typing import Any, Type, TypeVar


class Singleton(type):
Expand All @@ -32,14 +33,14 @@ class Singleton(type):
adjust state of the singleton.
"""

_instances: Dict[Type, Any] = {}
_instances: dict[type, Any] = {}

# Signature is intentionally not substitutable for type.__call__ (no *args,
# **kwargs) to require classes that use this metaclass to have no
# constructor arguments.
def __call__(cls) -> Any:
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__()
cls._instances[cls] = super().__call__()
return cls._instances[cls]


Expand Down
9 changes: 6 additions & 3 deletions python/lsst/utils/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

from __future__ import annotations

__all__ = ["deprecate_pybind11", "suppress_deprecations"]

import functools
import unittest.mock
import warnings
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Any, Iterator, Type
from typing import Any

import deprecated.sphinx


def deprecate_pybind11(obj: Any, reason: str, version: str, category: Type[Warning] = FutureWarning) -> Any:
def deprecate_pybind11(obj: Any, reason: str, version: str, category: type[Warning] = FutureWarning) -> Any:
"""Deprecate a pybind11-wrapped C++ interface function, method or class.
This needs to use a pass-through Python wrapper so that
Expand Down Expand Up @@ -73,7 +76,7 @@ def internal(*args: Any, **kwargs: Any) -> Any:


@contextmanager
def suppress_deprecations(category: Type[Warning] = FutureWarning) -> Iterator[None]:
def suppress_deprecations(category: type[Warning] = FutureWarning) -> Iterator[None]:
"""Suppress warnings generated by `deprecated.sphinx.deprecated`.
Naively, one might attempt to suppress these warnings by using
Expand Down
17 changes: 8 additions & 9 deletions python/lsst/utils/doImport.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

from __future__ import annotations

__all__ = ("doImport", "doImportType")

import importlib
import types
from typing import List, Optional, Type, Union


def doImport(importable: str) -> Union[types.ModuleType, Type]:
def doImport(importable: str) -> types.ModuleType | type:
"""Import a python object given an importable string and return it.
Parameters
Expand All @@ -43,26 +44,24 @@ def doImport(importable: str) -> Union[types.ModuleType, Type]:
if not isinstance(importable, str):
raise TypeError(f"Unhandled type of importable, val: {importable}")

def tryImport(
module: str, fromlist: List[str], previousError: Optional[str]
) -> Union[types.ModuleType, Type]:
def tryImport(module: str, fromlist: list[str], previousError: str | None) -> types.ModuleType | type:
pytype = importlib.import_module(module)
# Can have functions inside classes inside modules
for f in fromlist:
try:
pytype = getattr(pytype, f)
except AttributeError:
except AttributeError as e:
extra = f"({previousError})" if previousError is not None else ""
raise ImportError(
f"Could not get attribute '{f}' from '{module}' when importing '{importable}' {extra}"
)
) from e
return pytype

# Go through the import path attempting to load the module
# and retrieve the class or function as an attribute. Shift components
# from the module list to the attribute list until something works.
moduleComponents = importable.split(".")
infileComponents: List[str] = []
infileComponents: list[str] = []
previousError = None

while moduleComponents:
Expand All @@ -88,7 +87,7 @@ def tryImport(
raise ModuleNotFoundError(f"Unable to import {importable!r} {extra}")


def doImportType(importable: str) -> Type:
def doImportType(importable: str) -> type:
"""Import a python type given an importable string and return it.
Parameters
Expand Down
2 changes: 2 additions & 0 deletions python/lsst/utils/get_caller_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

from __future__ import annotations

__all__ = ["get_caller_name"]

from deprecated.sphinx import deprecated
Expand Down
8 changes: 5 additions & 3 deletions python/lsst/utils/inheritDoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

from __future__ import annotations

__all__ = ("inheritDoc",)

from typing import Callable, Type
from collections.abc import Callable


def inheritDoc(klass: Type) -> Callable:
def inheritDoc(klass: type) -> Callable:
"""Extend existing documentation for a method that exists in another
class and extend it with any additional documentation defined.
Expand All @@ -34,7 +36,7 @@ class and extend it with any additional documentation defined.
Intermediate decorator used in the documentation process.
"""

def tmpDecorator(method: Type) -> Callable:
def tmpDecorator(method: type) -> Callable:
"""Update the documentation from a class with the same method."""
methodName = method.__name__
if not hasattr(klass, methodName):
Expand Down
13 changes: 6 additions & 7 deletions python/lsst/utils/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.
#
"""Utilities relating to introspection in python."""

from __future__ import annotations

"""Utilities relating to introspection in python."""

__all__ = [
"get_class_of",
"get_full_type_name",
Expand All @@ -25,7 +24,7 @@
import builtins
import inspect
import types
from typing import Any, Type, Union
from typing import Any

from .doImport import doImport, doImportType

Expand Down Expand Up @@ -84,7 +83,7 @@ def get_full_type_name(cls: Any) -> str:
return cleaned_name


def get_class_of(typeOrName: Union[Type, str]) -> Type:
def get_class_of(typeOrName: type | str | types.ModuleType) -> type:
"""Given the type name or a type, return the python type.
If a type name is given, an attempt will be made to import the type.
Expand Down Expand Up @@ -112,13 +111,13 @@ def get_class_of(typeOrName: Union[Type, str]) -> Type:
if isinstance(typeOrName, str):
cls = doImportType(typeOrName)
else:
cls = typeOrName
if isinstance(cls, types.ModuleType):
if isinstance(typeOrName, types.ModuleType):
raise TypeError(f"Can not get class of module {get_full_type_name(typeOrName)}")
cls = typeOrName
return cls


def get_instance_of(typeOrName: Union[Type, str], *args: Any, **kwargs: Any) -> Any:
def get_instance_of(typeOrName: type | str, *args: Any, **kwargs: Any) -> Any:
"""Given the type name or a type, instantiate an object of that type.
If a type name is given, an attempt will be made to import the type.
Expand Down
10 changes: 5 additions & 5 deletions python/lsst/utils/iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
# license that can be found in the LICENSE file.
#

from __future__ import annotations

"""Utilities relating to iterators."""

from __future__ import annotations

__all__ = ["chunk_iterable", "ensure_iterable", "isplit"]

import itertools
from collections.abc import Mapping
from typing import Any, Iterable, Iterator, Tuple, TypeVar
from collections.abc import Iterable, Iterator, Mapping
from typing import Any, TypeVar


def chunk_iterable(data: Iterable[Any], chunk_size: int = 1_000) -> Iterator[Tuple[Any, ...]]:
def chunk_iterable(data: Iterable[Any], chunk_size: int = 1_000) -> Iterator[tuple[Any, ...]]:
"""Return smaller chunks of an iterable.
Parameters
Expand Down
15 changes: 8 additions & 7 deletions python/lsst/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import logging
import sys
import time
from collections.abc import Generator
from contextlib import contextmanager
from logging import LoggerAdapter
from typing import Any, Generator, List, Optional, Union
from typing import Any, Union

try:
import lsst.log.utils as logUtils
Expand Down Expand Up @@ -172,7 +173,7 @@ class LsstLogAdapter(LoggerAdapter):
_stacklevel = _calculate_base_stacklevel(2, 1)

@contextmanager
def temporary_log_level(self, level: Union[int, str]) -> Generator:
def temporary_log_level(self, level: int | str) -> Generator:
"""Temporarily set the level of this logger.
Parameters
Expand Down Expand Up @@ -253,7 +254,7 @@ def trace(self, fmt: str, *args: Any, **kwargs: Any) -> None:
stacklevel = self._process_stacklevel(kwargs)
self.log(TRACE, fmt, *args, **kwargs, stacklevel=stacklevel)

def setLevel(self, level: Union[int, str]) -> None:
def setLevel(self, level: int | str) -> None:
"""Set the level for the logger, trapping lsst.log values.
Parameters
Expand All @@ -272,7 +273,7 @@ def setLevel(self, level: Union[int, str]) -> None:
self.logger.setLevel(level)

@property
def handlers(self) -> List[logging.Handler]:
def handlers(self) -> list[logging.Handler]:
"""Log handlers associated with this logger."""
return self.logger.handlers

Expand All @@ -288,7 +289,7 @@ def removeHandler(self, handler: logging.Handler) -> None:
self.logger.removeHandler(handler)


def getLogger(name: Optional[str] = None, logger: Optional[logging.Logger] = None) -> LsstLogAdapter:
def getLogger(name: str | None = None, logger: logging.Logger | None = None) -> LsstLogAdapter:
"""Get a logger compatible with LSST usage.
Parameters
Expand Down Expand Up @@ -323,7 +324,7 @@ def getLogger(name: Optional[str] = None, logger: Optional[logging.Logger] = Non
LsstLoggers = Union[logging.Logger, LsstLogAdapter]


def getTraceLogger(logger: Union[str, LsstLoggers], trace_level: int) -> LsstLogAdapter:
def getTraceLogger(logger: str | LsstLoggers, trace_level: int) -> LsstLogAdapter:
"""Get a logger with the appropriate TRACE name.
Parameters
Expand Down Expand Up @@ -367,7 +368,7 @@ class PeriodicLogger:
LOGGING_INTERVAL = 600.0
"""Default interval between log messages."""

def __init__(self, logger: LsstLoggers, interval: Optional[float] = None, level: int = VERBOSE):
def __init__(self, logger: LsstLoggers, interval: float | None = None, level: int = VERBOSE):
self.logger = logger
self.interval = interval if interval is not None else self.LOGGING_INTERVAL
self.level = level
Expand Down

0 comments on commit 3943c6b

Please sign in to comment.