diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e8a884b02..000000000 --- a/.flake8 +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -exclude = build, doc/source/conf.py -select = W191, W291, W293, W391, E115, E117, E122, E124, E125, E225, E231, E301, E303, E501, F401, F403 -count = True -max-complexity = 10 -max-line-length = 100 -statistics = True \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index cb9b5d3e5..b528780fd 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -3,7 +3,7 @@ documentation: - any-glob-to-any-file: ['doc/source/**/*'] maintenance: - changed-files: - - any-glob-to-any-file: ['.github/**/*', '.flake8', 'pyproject.toml', 'docker/**/*'] + - any-glob-to-any-file: ['.github/**/*', 'ruff.toml', 'pyproject.toml', 'docker/**/*'] dependencies: - changed-files: - any-glob-to-any-file: ['requirements/*'] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3043a17e4..bae664488 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,11 @@ repos: -- repo: https://github.com/psf/black - rev: 25.1.0 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.8 hooks: - - id: black - args: [ - "doc/source/conf.py", - "examples" - ] + - id: ruff + - id: ruff-format - repo: https://github.com/adamchainz/blacken-docs rev: 1.19.1 @@ -16,21 +13,6 @@ repos: - id: blacken-docs additional_dependencies: [black==24.8.0] -- repo: https://github.com/pycqa/isort - rev: 6.0.1 - hooks: - - id: isort - args: [ - "--profile", "black", - "--force-sort-within-sections", - "--line-length", "100", - ] - -- repo: https://github.com/PyCQA/flake8 - rev: 7.2.0 - hooks: - - id: flake8 - - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: diff --git a/doc/source/doc-style/code/sample_func.py b/doc/source/doc-style/code/sample_func.py index 69ef2c056..0d42b25cd 100644 --- a/doc/source/doc-style/code/sample_func.py +++ b/doc/source/doc-style/code/sample_func.py @@ -1,4 +1,4 @@ -def func(arg1, arg2): +def func(arg1, arg2): # noqa: D100 """Summary line . Extended description of the function. The extended description, @@ -25,7 +25,7 @@ def func(arg1, arg2): Examples -------- - >>> func(1, 'foo') + >>> func(1, "foo") True """ diff --git a/doc/source/how-to/code/pyansys_logging.py b/doc/source/how-to/code/pyansys_logging.py index fffd38fb3..aa30e9690 100644 --- a/doc/source/how-to/code/pyansys_logging.py +++ b/doc/source/how-to/code/pyansys_logging.py @@ -1,3 +1,5 @@ +"""Module for PyAnsys logging.""" + from copy import copy from datetime import datetime import logging @@ -10,9 +12,7 @@ # Formatting -STDOUT_MSG_FORMAT = ( - "%(levelname)s - %(instance_name)s - %(module)s - %(funcName)s - %(message)s" -) +STDOUT_MSG_FORMAT = "%(levelname)s - %(instance_name)s - %(module)s - %(funcName)s - %(message)s" FILE_MSG_FORMAT = STDOUT_MSG_FORMAT DEFAULT_STDOUT_HEADER = """ @@ -59,6 +59,7 @@ def __init__(self, logger, extra=None): self.std_out_handler = logger.std_out_handler def process(self, msg, kwargs): + """Get instance_name for logging.""" kwargs["extra"] = {} # These are the extra parameters sent to log # here self.extra is the argument pass to the log records. @@ -77,7 +78,6 @@ def log_to_file(self, filename=FILE_NAME, level=LOG_LEVEL): Level of logging, for example ``'DEBUG'``. By default ``logging.DEBUG``. """ - self.logger = add_file_handler( self.logger, filename=filename, level=level, write_headers=True ) @@ -106,6 +106,8 @@ def setLevel(self, level="DEBUG"): class PyAnsysPercentStyle(logging.PercentStyle): + """Log message formatting.""" + def __init__(self, fmt, *, defaults=None): self._fmt = fmt or self.default_format self._defaults = defaults @@ -154,6 +156,7 @@ class InstanceFilter(logging.Filter): """Ensures that instance_name record always exists.""" def filter(self, record): + """If record had no attribute instance_name, create it and populate with empty string.""" if not hasattr(record, "instance_name"): record.instance_name = "" return True @@ -193,14 +196,11 @@ def __init__( cleanup=True, ): """Initialize Logger class.""" - - self.logger = logging.getLogger( - "pyproject_global" - ) # Creating default main logger. + self.logger = logging.getLogger("pyproject_global") # Creating default main logger. self.logger.addFilter(InstanceFilter()) self.logger.setLevel(level) self.logger.propagate = True - self.level = self.logger.level # TODO: TO REMOVE + self.level = self.logger.level # noqa: TD002, TD003 # TODO: TO REMOVE # Writing logging methods. self.debug = self.logger.debug @@ -233,10 +233,7 @@ def log_to_file(self, filename=FILE_NAME, level=LOG_LEVEL): level : str, optional Level of logging. E.x. 'DEBUG'. By default LOG_LEVEL """ - - self = add_file_handler( - self, filename=filename, level=level, write_headers=True - ) + self = add_file_handler(self, filename=filename, level=level, write_headers=True) def log_to_stdout(self, level=LOG_LEVEL): """Add standard output handler to the logger. @@ -246,7 +243,6 @@ def log_to_stdout(self, level=LOG_LEVEL): level : str, optional Level of logging record. By default LOG_LEVEL """ - self = add_stdout_handler(self, level=level) def setLevel(self, level="DEBUG"): @@ -333,9 +329,7 @@ def _add_product_instance_logger(self, name, product_instance, level): self._make_child_logger("NO_NAMED_YET", level), product_instance ) else: - raise TypeError( - f"``name`` parameter must be a string or None, not f{type(name)}" - ) + raise TypeError(f"``name`` parameter must be a string or None, not f{type(name)}") return instance_logger @@ -379,21 +373,20 @@ def add_instance_logger(self, name, product_instance, level=None): return self._instances[new_name] def __getitem__(self, key): + """Define custom KeyError message.""" if key in self._instances.keys(): return self._instances[key] else: raise KeyError(f"There are no instances with name {key}") def add_handling_uncaught_expections(self, logger): - """This just redirects the output of an exception to the logger.""" + """Redirect the output of an exception to the logger.""" def handle_exception(exc_type, exc_value, exc_traceback): if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return - logger.critical( - "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) - ) + logger.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) sys.excepthook = handle_exception @@ -405,7 +398,7 @@ def __del__(self): for handler in self.logger.handlers: handler.close() self.logger.removeHandler(handler) - except Exception as e: + except Exception: try: if self.logger is not None: self.logger.error("The logger was not deleted properly.") @@ -434,7 +427,6 @@ def add_file_handler(logger, filename=FILE_NAME, level=LOG_LEVEL, write_headers= logger Return the logger or Logger object. """ - file_handler = logging.FileHandler(filename) file_handler.setLevel(level) file_handler.setFormatter(logging.Formatter(FILE_MSG_FORMAT)) @@ -471,7 +463,6 @@ def add_stdout_handler(logger, level=LOG_LEVEL, write_headers=False): logger The logger or Logger object. """ - std_out_handler = logging.StreamHandler(sys.stdout) std_out_handler.setLevel(level) std_out_handler.setFormatter(PyProjectFormatter(STDOUT_MSG_FORMAT)) diff --git a/doc/source/how-to/code/test_pyansys_logging.py b/doc/source/how-to/code/test_pyansys_logging.py index ece1a4c06..9bdfbd5aa 100644 --- a/doc/source/how-to/code/test_pyansys_logging.py +++ b/doc/source/how-to/code/test_pyansys_logging.py @@ -1,6 +1,8 @@ +"""Test for PyAnsys logging.""" + import io import logging -import os +from pathlib import Path import sys import weakref @@ -9,25 +11,24 @@ def test_default_logger(): """Create a logger with default options. - Only stdout logger must be used.""" + Only stdout logger must be used. + """ capture = CaptureStdOut() with capture: test_logger = pyansys_logging.Logger() test_logger.info("Test stdout") - assert ( - "INFO - - test_pyansys_logging - test_default_logger - Test stdout" - in capture.content - ) + assert "INFO - - test_pyansys_logging - test_default_logger - Test stdout" in capture.content # File handlers are not activated. - assert os.path.exists(os.path.exists(os.path.join(os.getcwd(), "PyProject.log"))) + assert (Path.cwd() / "PyProject.log").exists() def test_level_stdout(): """Create a logger with default options. - Only stdout logger must be used.""" + Only stdout logger must be used. + """ capture = CaptureStdOut() with capture: test_logger = pyansys_logging.Logger(level=logging.INFO) @@ -85,23 +86,20 @@ def test_level_stdout(): ) # File handlers are not activated. - assert os.path.exists(os.path.exists(os.path.join(os.getcwd(), "PyProject.log"))) + assert (Path.cwd() / "PyProject.log").exists() def test_file_handlers(tmpdir): """Activate a file handler different from `PyProject.log`.""" - file_logger = tmpdir.mkdir("sub").join("test_logger.txt") test_logger = pyansys_logging.Logger(to_file=True, filename=file_logger) test_logger.info("Test Misc File") - with open(file_logger, "r") as f: + with Path.open(file_logger, "r") as f: content = f.readlines() - assert os.path.exists( - file_logger - ) # The file handler is not the default PyProject.Log + assert Path.exists(file_logger) # The file handler is not the default PyProject.Log assert len(content) == 6 assert "NEW SESSION" in content[2] assert ( @@ -109,10 +107,7 @@ def test_file_handlers(tmpdir): in content[3] ) assert "LEVEL - INSTANCE NAME - MODULE - FUNCTION - MESSAGE" in content[4] - assert ( - "INFO - - test_pyansys_logging - test_file_handlers - Test Misc File" - in content[5] - ) + assert "INFO - - test_pyansys_logging - test_file_handlers - Test Misc File" in content[5] # Delete the logger and its file handler. test_logger_ref = weakref.ref(test_logger) @@ -127,9 +122,11 @@ def __init__(self): self._stream = io.StringIO() def __enter__(self): + """Runtime context is entered.""" sys.stdout = self._stream def __exit__(self, type, value, traceback): + """Runtime context is exited.""" sys.stdout = sys.__stdout__ @property diff --git a/examples/pyvista_example.py b/examples/pyvista_example.py index 75345a197..3e976632f 100644 --- a/examples/pyvista_example.py +++ b/examples/pyvista_example.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +# ruff: noqa: D400 """ .. _adding_a_new_gallery_example: diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..39e0c33a5 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,37 @@ +exclude = [ + "build", + "doc/source/conf.py", +] + +line-length = 100 + +[format] +quote-style = "double" +indent-style = "space" +docstring-code-format = true + +[lint] +select = [ + "D", # pydocstyle, see https://docs.astral.sh/ruff/rules/#pydocstyle-d + "E", # pycodestyle, see https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "F", # pyflakes, see https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # isort, see https://docs.astral.sh/ruff/rules/#isort-i + "N", # pep8-naming, see https://docs.astral.sh/ruff/rules/#pep8-naming-n + "PTH", # flake8-use-pathlib, https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + "TD", # flake8-todos, https://docs.astral.sh/ruff/rules/#flake8-todos-td + "W", # pycodestyle, see https://docs.astral.sh/ruff/rules/#pycodestyle-e-w +] +ignore = [] + +[lint.pydocstyle] +convention = "numpy" + +[lint.isort] +combine-as-imports = true +force-sort-within-sections = true + +[lint.mccabe] +max-complexity = 10 + +[lint.pep8-naming] +ignore-names = ["setLevel"] \ No newline at end of file