Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require python 3.9 or newer #67

Merged
merged 2 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
TOX_PARALLEL_NO_SPINNER: 1

steps:
- name: Switch to using Python 3.8 by default
- name: Switch to using Python 3.9 by default
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.9
- name: Install tox
run: >-
python3 -m
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ jobs:
id: generate_matrix
uses: coactions/dynamic-matrix@v1
with:
min_python: "3.9"
platforms: linux,macos
macos: minmax
other_names: |
lint
pkg
Expand Down
33 changes: 12 additions & 21 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,42 @@ exclude: |
repos:
- repo: https://github.com/pycontribs/mirrors-prettier
# keep it before yamllint
rev: v3.3.1
rev: v3.3.2
hooks:
- id: prettier
always_run: true
additional_dependencies:
- prettier
- prettier-plugin-toml
- prettier-plugin-sort-json
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: check-byte-order-marker
- id: fix-byte-order-marker
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: debug-statements
language_version: python3
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies:
- pydocstyle>=5.1.1
- flake8-absolute-import
- flake8-black>=0.1.1
- flake8-docstrings>=1.5.0
language_version: python3
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
files: \.(yaml|yml)$
types: [file, yaml]
entry: yamllint --strict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.4.7"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
language_version: python3
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
Expand Down
25 changes: 22 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"

[project]
# https://peps.python.org/pep-0621/#readme
requires-python = ">=3.8"
requires-python = ">=3.9"
dynamic = ["version", "dependencies", "optional-dependencies"]
name = "enrich"
description = "enrich"
Expand All @@ -29,7 +29,6 @@ classifiers = [
"Operating System :: POSIX :: Linux",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand All @@ -52,7 +51,7 @@ profile = "black"
known_first_party = "subprocess_tee"

[tool.mypy]
python_version = 3.8
python_version = 3.9
color_output = true
error_summary = true
disallow_any_generics = true
Expand All @@ -73,6 +72,26 @@ filterwarnings = [
# ignore::UserWarning
]

[tool.ruff]
target-version = "py39"
# Same as Black.
line-length = 88

lint.ignore = [
"D203", # incompatible with D211
"D213", # incompatible with D212
"E501", # we use black
"ANN",
"FBT001",
"FBT002",
"FBT003",
"PGH",
]
lint.select = ["ALL"]

[tool.ruff.lint.per-file-ignores]
"test/**/*.py" = ["D", "ERA", "S"]

[tool.setuptools.dynamic]
dependencies = { file = [".config/requirements.in"] }
optional-dependencies.test = { file = [".config/requirements-test.in"] }
Expand Down
1 change: 1 addition & 0 deletions src/enrich/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Enrich module."""
7 changes: 4 additions & 3 deletions src/enrich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class Console(rich_console.Console):
"""Extends rich Console class."""

def __init__(self, *args: str, redirect: bool = True, **kwargs: Any) -> None:
"""
enrich console does soft-wrapping by default and this diverge from
"""Enrich constructor.

Enrich console does soft-wrapping by default and this diverge from
original rich console which does not, creating hard-wraps instead.
"""
self.redirect = redirect
Expand All @@ -26,7 +27,7 @@ def __init__(self, *args: str, redirect: bool = True, **kwargs: Any) -> None:
# heuristic to make an informed decision.
if "force_terminal" not in kwargs:
kwargs["force_terminal"] = should_do_markup(
stream=kwargs.get("file", sys.stdout)
stream=kwargs.get("file", sys.stdout),
)

super().__init__(*args, **kwargs)
Expand Down
40 changes: 24 additions & 16 deletions src/enrich/logging.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Implements enriched RichHandler"""
"""Implements enriched RichHandler."""

from datetime import datetime
from typing import TYPE_CHECKING, Any, Iterable, Optional
from __future__ import annotations

from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any

from rich.logging import RichHandler as OriginalRichHandler
from rich.text import Text, TextType

if TYPE_CHECKING:
from collections.abc import Iterable

from rich.console import Console, ConsoleRenderable


Expand All @@ -15,36 +19,38 @@ class FluidLogRender: # pylint: disable=too-few-public-methods
"""Renders log by not using columns and avoiding any wrapping."""

# pylint: disable=too-many-arguments
def __init__(
def __init__( # noqa: PLR0913
self,
show_time: bool = False,
show_level: bool = False,
show_path: bool = True,
time_format: str = "[%x %X]",
omit_repeated_times: bool = True,
) -> None:
"""Construcs instance."""
self.show_time = show_time
self.show_level = show_level
self.show_path = show_path
self.time_format = time_format
self.omit_repeated_times = omit_repeated_times
self._last_time: Optional[str] = None
self._last_time: str | None = None

def __call__( # pylint: disable=too-many-arguments
def __call__( # pylint: disable=too-many-arguments # noqa: PLR0913
self,
console: "Console",
renderables: Iterable["ConsoleRenderable"],
log_time: Optional[datetime] = None,
time_format: Optional[str] = None,
console: Console, # noqa: ARG002
renderables: Iterable[ConsoleRenderable],
log_time: datetime | None = None,
time_format: str | None = None,
level: TextType = "",
path: Optional[str] = None,
line_no: Optional[int] = None,
link_path: Optional[str] = None,
path: str | None = None,
line_no: int | None = None,
link_path: str | None = None,
) -> Text:
"""Call."""
result = Text()
if self.show_time:
if log_time is None:
log_time = datetime.now()
log_time = datetime.now(tz=timezone.utc)
log_time_display = log_time.strftime(time_format or self.time_format) + " "
if self.omit_repeated_times and log_time_display == self._last_time:
result += Text(" " * len(log_time_display))
Expand All @@ -55,7 +61,7 @@ def __call__( # pylint: disable=too-many-arguments
if not isinstance(level, Text):
level = Text(level)
# CRITICAL is the longest identifier from default set.
if len(level) < 9:
if len(level) < 9: # noqa: PLR2004
level += " " * (9 - len(level))
result += level

Expand All @@ -65,7 +71,8 @@ def __call__( # pylint: disable=too-many-arguments
if self.show_path and path:
path_text = Text(" ", style="repr.filename")
path_text.append(
path, style=f"link file://{link_path}" if link_path else ""
path,
style=f"link file://{link_path}" if link_path else "",
)
if line_no:
path_text.append(f":{line_no}")
Expand All @@ -78,6 +85,7 @@ class RichHandler(OriginalRichHandler):
"""Enriched handler that does not wrap."""

def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Create the handler."""
super().__init__(*args, **kwargs)
# RichHandler constructor does not allow custom renderer
# https://github.com/willmcgugan/rich/issues/438
Expand Down
15 changes: 9 additions & 6 deletions test/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@
import sys

import pytest
from pytest_mock import MockFixture

from enrich.console import Console, should_do_markup
from pytest_mock import MockFixture # pylint: disable=wrong-import-order


def test_rich_console_ex() -> None:
"""Validate that ConsoleEx can capture output from print() calls."""
console = Console(record=True, redirect=True)
console.print("alpha")
print("beta")
print("beta") # noqa: T201
sys.stdout.write("gamma\n")
sys.stderr.write("delta\n")
# While not supposed to happen we want to be sure that this will not raise
# an exception. Some libraries may still sometimes send bytes to the
# streams, notable example being click.
# sys.stdout.write(b"epsilon\n") # type: ignore
# sys.stdout.write(b"epsilon\n")
text = console.export_text()
assert text == "alpha\nbeta\ngamma\ndelta\n"


def test_rich_console_ex_ansi() -> None:
"""Validate that ANSI sent to sys.stdout does not become garbage in record."""
print()
print() # noqa: T201
console = Console(force_terminal=True, record=True, redirect=True)
console.print("[green]this from Console.print()[/green]", style="red")

Expand All @@ -40,7 +39,11 @@ def test_rich_console_ex_ansi() -> None:
def test_console_soft_wrap() -> None:
"""Assures long prints on console are not wrapped when requested."""
console = Console(
file=io.StringIO(), width=20, record=True, soft_wrap=True, redirect=False
file=io.StringIO(),
width=20,
record=True,
soft_wrap=True,
redirect=False,
)
text = 21 * "x"
console.print(text, end="")
Expand Down
16 changes: 9 additions & 7 deletions test/test_logging.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Tests related to enriched RichHandler"""

from __future__ import annotations

import io
import logging
import re
from typing import Tuple, Union

import pytest

from enrich.console import Console
from enrich.logging import RichHandler


def strip_ansi_escape(text: Union[str, bytes]) -> str:
def strip_ansi_escape(text: str | bytes) -> str:
"""Remove all ANSI escapes from string or bytes.

If bytes is passed instead of string, it will be converted to string
Expand All @@ -24,7 +24,7 @@ def strip_ansi_escape(text: Union[str, bytes]) -> str:


@pytest.fixture(name="rich_logger")
def rich_logger_fixture() -> Tuple[logging.Logger, RichHandler]:
def rich_logger_fixture() -> tuple[logging.Logger, RichHandler]:
"""Returns tuple with logger and handler to be tested."""
rich_handler = RichHandler(
console=Console(
Expand All @@ -38,16 +38,18 @@ def rich_logger_fixture() -> Tuple[logging.Logger, RichHandler]:
)

logging.basicConfig(
level="NOTSET", format="%(message)s", datefmt="[DATE]", handlers=[rich_handler]
level="NOTSET",
format="%(message)s",
datefmt="[DATE]",
handlers=[rich_handler],
)
rich_log = logging.getLogger("rich")
rich_log.addHandler(rich_handler)
return (rich_log, rich_handler)


def test_logging(rich_logger: Tuple[logging.Logger, RichHandler]) -> None:
def test_logging(rich_logger: tuple[logging.Logger, RichHandler]) -> None:
"""Test that logger does not wrap."""

(logger, rich_handler) = rich_logger

text = 10 * "x" # a long text that would likely wrap on a normal console
Expand Down
Loading