Skip to content
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
2 changes: 2 additions & 0 deletions .github/workflows/devcontainer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ jobs:
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
with:
runCmd: |
set -e

echo "Installing test dependencies..."
pip install -e .[development,docs,casts]

Expand Down
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ repos:
entry: mypy
language: system
files: ^dfetch/
require_serial: true
types: [file, python]
- id: doc8
name: doc8
Expand Down Expand Up @@ -110,12 +111,14 @@ repos:
entry: ruff
language: python
args: [check]
exclude: ^doc/_ext/sphinxcontrib_asciinema
types: [file, python]
- id: pyright
Comment thread
coderabbitai[bot] marked this conversation as resolved.
name: pyright
description: Lint using pyright
entry: pyright
language: python
require_serial: true
types: [file, python]
- id: pyupgrade
name: pyupgrade
Expand Down
4 changes: 2 additions & 2 deletions doc/_ext/colordot.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import html
import re
from typing import Any, List
from typing import Any

from docutils import nodes
from docutils.nodes import Node, system_message
Expand Down Expand Up @@ -99,7 +99,7 @@ def _replace_emoji_for_latex(
if parent is None:
continue
idx = parent.children.index(text_node)
new_nodes: List[Node] = []
new_nodes: list[Node] = []
last = 0
for m in _EMOJI_RE.finditer(text):
if m.start() > last:
Expand Down
7 changes: 4 additions & 3 deletions doc/_ext/designguide.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"""

import html
from typing import Any
from collections.abc import Callable
from typing import Any, ClassVar

from docutils import nodes
from docutils.nodes import Node
Expand All @@ -38,7 +39,7 @@ class SwatchDirective(Directive):

required_arguments = 1
optional_arguments = 0
option_spec = {
option_spec: ClassVar[dict[str, Callable[[str], Any]] | None] = {
"token": directives.unchanged,
"label": directives.unchanged,
"usage": directives.unchanged,
Expand Down Expand Up @@ -81,7 +82,7 @@ class PaletteDirective(Directive):

required_arguments = 0
optional_arguments = 0
option_spec = {
option_spec: ClassVar[dict[str, Callable[[str], Any]] | None] = {
"columns": directives.positive_int,
}
has_content = True
Expand Down
5 changes: 3 additions & 2 deletions doc/_ext/dfetch_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import sys
import types
from typing import MutableMapping, cast
from collections.abc import Mapping, MutableMapping
from typing import Any, ClassVar, cast

import pygments.styles
from pygments.style import Style
Expand All @@ -24,7 +25,7 @@ class DfetchStyle(Style): # pylint: disable=too-few-public-methods

background_color = "#fef8f0" # --bg-tint
default_style = ""
styles = {
styles: ClassVar[Mapping[Any, str]] = {
Token: "#1c1917", # --text
Comment: "italic #78716c", # --text-muted
Comment.Preproc: "noitalic #a0510a", # --primary-dark
Expand Down
37 changes: 19 additions & 18 deletions doc/_ext/scenario_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
import os
import re
import textwrap
from typing import Dict, FrozenSet, List, Tuple
from collections.abc import Callable
from typing import Any, ClassVar

from docutils import nodes
from docutils.parsers.rst import Directive, directives
Expand Down Expand Up @@ -83,9 +84,9 @@ class ScenarioIncludePlaceholder(nodes.General, nodes.Element):
# ---------------------------------------------------------------------------


def _feature_tags(feature_path: str) -> List[str]:
def _feature_tags(feature_path: str) -> list[str]:
"""Return all behave tags declared before the ``Feature:`` line."""
tags: List[str] = []
tags: list[str] = []
with open(feature_path, encoding="utf-8") as fh:
for line in fh:
stripped = line.strip()
Expand All @@ -96,7 +97,7 @@ def _feature_tags(feature_path: str) -> List[str]:
return tags


def _group_tag(feature_path: str, non_group_tags: FrozenSet[str]) -> str:
def _group_tag(feature_path: str, non_group_tags: frozenset[str]) -> str:
"""Return the first tag not in *non_group_tags*, or ``'other'``."""
for tag in _feature_tags(feature_path):
if tag not in non_group_tags:
Expand All @@ -114,7 +115,7 @@ def _feature_title(feature_path: str) -> str:
return os.path.basename(feature_path)


def _all_scenarios(feature_path: str) -> Tuple[Tuple[str, str], ...]:
def _all_scenarios(feature_path: str) -> tuple[tuple[str, str], ...]:
"""Return (header, title) pairs for all scenarios in the feature file."""
with open(feature_path, encoding="utf-8") as fh:
return tuple(
Expand All @@ -131,7 +132,7 @@ def _full_feature_content(feature_path: str) -> str:
return fh.read()


def _selected_scenarios_content(feature_path: str, scenario_titles: List[str]) -> str:
def _selected_scenarios_content(feature_path: str, scenario_titles: list[str]) -> str:
"""Return content containing only the selected scenario blocks."""
with open(feature_path, encoding="utf-8") as fh:
content = fh.read()
Expand Down Expand Up @@ -173,7 +174,7 @@ class ScenarioIncludeDirective(Directive):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
option_spec: ClassVar[dict[str, Callable[[str], Any]] | None] = {
"scenario": str,
"inline": directives.flag, # keep inline even in PDF mode
}
Expand All @@ -192,7 +193,7 @@ def _feature_abs(self, feature_file: str) -> str:
raise self.error(f"Feature file not found: {path}")
return path

def _requested_scenarios(self, available: Tuple[Tuple[str, str], ...]) -> List[str]:
def _requested_scenarios(self, available: tuple[tuple[str, str], ...]) -> list[str]:
return [
t.strip()
for t in self.options.get("scenario", "").splitlines()
Expand All @@ -203,7 +204,7 @@ def _requested_scenarios(self, available: Tuple[Tuple[str, str], ...]) -> List[s
# Appendix entry registration (always runs, any builder)
# ------------------------------------------------------------------

def _entry_metadata(self, feature_abs: str) -> Tuple[str, str, str]:
def _entry_metadata(self, feature_abs: str) -> tuple[str, str, str]:
"""Return (label, group_tag, feature_title) for a feature file."""
env = self._env()
non_group_tags = frozenset(getattr(env.config, "scenario_non_command_tags", []))
Expand All @@ -218,7 +219,7 @@ def _register_appendix_entry(
self,
feature_file: str,
feature_abs: str,
scenario_titles: List[str],
scenario_titles: list[str],
) -> None:
"""Store entry in env.scenario_appendix_entries for any builder.

Expand Down Expand Up @@ -254,7 +255,7 @@ def _register_appendix_entry(
# Entry point
# ------------------------------------------------------------------

def run(self) -> List[nodes.Node]:
def run(self) -> list[nodes.Node]:
feature_file = self.arguments[0].strip()
feature_abs = self._feature_abs(feature_file)
available = _all_scenarios(feature_abs)
Expand Down Expand Up @@ -310,7 +311,7 @@ class ScenarioAppendixDirective(Directive):
optional_arguments = 0
has_content = False

def run(self) -> List[nodes.Node]:
def run(self) -> list[nodes.Node]:
env = self.state.document.settings.env
# Record which document hosts the appendix so that ScenarioAppendixRef
# nodes in other documents can be resolved with the correct refdocname.
Expand All @@ -329,13 +330,13 @@ def run(self) -> List[nodes.Node]:
# ---------------------------------------------------------------------------


def _build_appendix_nodes(entries: Dict) -> List[nodes.Node]:
def _build_appendix_nodes(entries: dict) -> list[nodes.Node]:
"""Build docutils section nodes for every collected appendix entry."""
by_tag: Dict[str, List] = {}
by_tag: dict[str, list] = {}
for entry in entries.values():
by_tag.setdefault(entry["group_tag"], []).append(entry)

result: List[nodes.Node] = []
result: list[nodes.Node] = []
for tag in sorted(by_tag):
tag_entries = sorted(by_tag[tag], key=lambda e: e["feature_title"])
label = f"appendix-{tag}"
Expand Down Expand Up @@ -366,10 +367,10 @@ def _build_appendix_nodes(entries: Dict) -> List[nodes.Node]:


def _render_scenario_inline(
scenario_titles: List[str], feature_abs: str
) -> List[nodes.Node]:
scenario_titles: list[str], feature_abs: str
) -> list[nodes.Node]:
"""Return docutils nodes for inline HTML rendering of *scenario_titles*."""
result: List[nodes.Node] = []
result: list[nodes.Node] = []
for title in scenario_titles:
raw_content = _selected_scenarios_content(feature_abs, [title])
content = textwrap.dedent(raw_content).strip()
Expand Down
3 changes: 1 addition & 2 deletions doc/conf.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Documentation build configuration file.
"""
Expand All @@ -18,7 +17,7 @@
ext_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))
sys.path.insert(0, ext_path)

import dfetch_style # noqa: E402
import dfetch_style

dfetch_style.register()

Expand Down
Empty file modified doc/generate-casts/interactive_helper.py
100644 → 100755
Empty file.
1 change: 0 additions & 1 deletion doc/landing-page/conf.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Documentation build configuration file.
"""
Expand Down
12 changes: 6 additions & 6 deletions doc/static/uml/generate_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import os
import pathlib
import re
from collections.abc import Sequence
from pathlib import Path
from typing import Sequence, Tuple

import_regex = re.compile(r"(import|from) dfetch\.(?P<relation>[\.\w]+)")
description_regex = re.compile(r"^\"{3}(?P<description>.*)")
Expand All @@ -17,7 +17,7 @@ class Relation:

@dataclasses.dataclass
class Module:
path: Tuple[str]
path: tuple[str]
name: str
description: str
relations: list
Expand Down Expand Up @@ -129,7 +129,7 @@ def generate_c3(relations, path: Sequence[str], blacklist=None):
f'{indent}{indent}Component(comp{name}, "{name}", "python", "{description}")'
)

print("")
print()
for name, module in modules.items():
if isinstance(module, dict):
continue
Expand All @@ -145,14 +145,14 @@ def generate_c3(relations, path: Sequence[str], blacklist=None):
print(
f'{indent}Container(cont{container}, "{container}", "python", "Something.")'
)
print("")
print()
for relation in outside_in:
print(relation)
print("")
print()

for relation in inside_out:
print(relation)
print("")
print()
print(C3_END_TEMPLATE)


Expand Down
8 changes: 5 additions & 3 deletions features/steps/add_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ def _auto_prompt(_prompt: str, **kwargs) -> str: # type: ignore[return]
return prompt_answers.popleft()
return str(kwargs.get("default", ""))

with patch("dfetch.commands.add.Prompt.ask", side_effect=_auto_prompt):
with patch("dfetch.commands.add.Confirm.ask", side_effect=_auto_confirm):
call_command(context, cmd)
with (
patch("dfetch.commands.add.Prompt.ask", side_effect=_auto_prompt),
patch("dfetch.commands.add.Confirm.ask", side_effect=_auto_confirm),
):
call_command(context, cmd)


@when('I run "dfetch {add_args}" with inputs')
Expand Down
24 changes: 12 additions & 12 deletions features/steps/generic_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import pathlib
import re
import shutil
from collections.abc import Iterable
from contextlib import contextmanager
from itertools import zip_longest
from typing import Iterable, List, Optional, Pattern, Tuple, Union
from re import Pattern
from unittest.mock import patch

from behave import given, then, when # pylint: disable=no-name-in-module
Expand Down Expand Up @@ -57,18 +58,17 @@ def remote_server_path(context) -> str:
return pathlib.Path(context.remotes_dir_path).as_uri()


def call_command(context: Context, args: list[str], path: Optional[str] = ".") -> None:
def call_command(context: Context, args: list[str], path: str | None = ".") -> None:
before = context.console.export_text()

DLogger.reset_projects()

with temporary_env("CI", "true"):
with in_directory(path or "."):
try:
run(args, context.console)
context.cmd_returncode = 0
except DfetchFatalException:
context.cmd_returncode = 1
with temporary_env("CI", "true"), in_directory(path or "."):
try:
run(args, context.console)
context.cmd_returncode = 0
except DfetchFatalException:
context.cmd_returncode = 1

after = context.console.export_text()
context.cmd_output = after[len(before) :].strip("\n")
Expand Down Expand Up @@ -186,7 +186,7 @@ def list_dir(path):

result = ""
prev_node = []
for node in list(sorted(nodes)) + [""]:
for node in sorted(nodes) + [""]:
if prev_node:
end = ""
if "".join(node).startswith("".join(prev_node)):
Expand Down Expand Up @@ -359,7 +359,7 @@ def step_impl(context, name):
check_file_exists(name)


def check_json(path: Union[str, os.PathLike], content: str, context) -> None:
def check_json(path: str | os.PathLike, content: str, context) -> None:
"""Check a JSON file for exact equality (after normalising formatting)."""
content = apply_archive_substitutions(content, context)
with open(path, "r", encoding="UTF-8") as file_to_check:
Expand Down Expand Up @@ -394,7 +394,7 @@ def step_impl(_, path, target):
assert actual == target, f"Expected {path!r} to point to {target!r}, got {actual!r}"


def multisub(patterns: List[Tuple[Pattern[str], str]], text: str) -> str:
def multisub(patterns: list[tuple[Pattern[str], str]], text: str) -> str:
"""Apply a list of tuples that each contain a regex + replace string."""
for pattern, replace in patterns:
text = pattern.sub(replace, text)
Expand Down
4 changes: 2 additions & 2 deletions features/steps/git_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def step_impl(context, name, ending):
create_repo()
subprocess.check_call(["git", "config", "core.autocrlf", "false"])
pathlib.Path("README.md").write_bytes(
f"Generated file for {name}{terminator}".encode("utf-8")
f"Generated file for {name}{terminator}".encode()
)
commit_all("Initial commit")
tag("v1")
Expand All @@ -239,7 +239,7 @@ def step_impl(context, name, ending, filename, gitattr):
subprocess.check_call(["git", "config", "core.autocrlf", "false"])
pathlib.Path(".gitattributes").write_text(gitattr + "\n", encoding="utf-8")
pathlib.Path(filename).write_bytes(
f"Generated file for {name}{terminator}".encode("utf-8")
f"Generated file for {name}{terminator}".encode()
)
commit_all("Initial commit")
tag("v1")
Expand Down
Loading
Loading