Skip to content

Commit

Permalink
Feature/18 handle being offline (#47)
Browse files Browse the repository at this point in the history
* Implement `--offline` flag

* Fix incorrect is_offline check
  • Loading branch information
nathanjmcdougall committed Oct 28, 2024
1 parent 13bf71e commit b62bb81
Show file tree
Hide file tree
Showing 15 changed files with 440 additions and 213 deletions.
3 changes: 3 additions & 0 deletions src/usethis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import typer
from rich.console import Console

console = Console()

offline_opt = typer.Option(False, "--offline", help="Disable network access")
Empty file removed src/usethis/_errors.py
Empty file.
2 changes: 1 addition & 1 deletion src/usethis/_github/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_github_latest_tag(owner: str, repo: str) -> str:
try:
response = requests.get(api_url, timeout=1)
response.raise_for_status() # Raise an error for HTTP issues
except requests.exceptions.HTTPError as err:
except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as err:
raise GitHubTagError(f"Failed to fetch tags from GitHub API: {err}")

tags = response.json()
Expand Down
14 changes: 2 additions & 12 deletions src/usethis/_pyproject/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Literal, assert_never
from typing import Any

import mergedeep

Expand Down Expand Up @@ -97,8 +97,6 @@ def remove_config_value(id_keys: list[str], *, missing_ok: bool = False) -> None
def append_config_list(
id_keys: list[str],
values: list[Any],
*,
order: Literal["sorted", "preserved"] = "sorted",
) -> list[str]:
"""Append values to a list in the pyproject.toml configuration file."""
pyproject = read_pyproject_toml()
Expand All @@ -115,15 +113,7 @@ def append_config_list(
contents = {key: contents}
pyproject = mergedeep.merge(pyproject, contents)
else:
# Append to the existing configuration.
if order == "sorted":
new_values = sorted(p + values)
elif order == "preserved":
new_values = p + values
else:
assert_never(order)

p_parent[id_keys[-1]] = new_values
p_parent[id_keys[-1]] = p + values

write_pyproject_toml(pyproject)

Expand Down
18 changes: 12 additions & 6 deletions src/usethis/_pytest/core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import shutil
from pathlib import Path

from usethis import console


def add_pytest_dir():
def add_pytest_dir() -> None:
tests_dir = Path.cwd() / "tests"

if not tests_dir.exists():
Expand All @@ -20,14 +21,19 @@ def add_pytest_dir():
)


def remove_pytest_dir():
def remove_pytest_dir() -> None:
tests_dir = Path.cwd() / "tests"

if not tests_dir.exists():
# Early exit; tests directory does not exist
return

console.print(
"☐ Reconfigure the /tests directory to run without pytest", style="blue"
)
# Note we don't actually remove the directory, just explain what needs to be done.
if set(tests_dir.iterdir()) <= {tests_dir / "conftest.py"}:
# The only file in the directory is conftest.py
console.print("✔ Removing '/tests'.", style="green")
shutil.rmtree(tests_dir)
else:
console.print(
"☐ Reconfigure the /tests directory to run without pytest", style="blue"
)
# Note we don't actually remove the directory, just explain what needs to be done.
1 change: 0 additions & 1 deletion src/usethis/_ruff/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

def select_ruff_rules(rules: list[str]) -> None:
"""Add ruff rules to the project."""

rules = sorted(set(rules) - set(get_ruff_rules()))

if not rules:
Expand Down
11 changes: 11 additions & 0 deletions src/usethis/_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import socket
from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
Expand All @@ -13,3 +14,13 @@ def change_cwd(new_dir: Path) -> Generator[None, None, None]:
yield
finally:
os.chdir(old_dir)


def is_offline() -> bool:
try:
# Connect to Google's DNS server
socket.create_connection(("8.8.8.8", 53), timeout=3)
except OSError:
return True
else:
return False
42 changes: 2 additions & 40 deletions src/usethis/_tool.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import re
import subprocess
from abc import abstractmethod
from pathlib import Path
from typing import Protocol
Expand All @@ -20,7 +18,7 @@
set_config_value,
)
from usethis._pyproject.io import read_pyproject_toml
from usethis._uv.deps import get_dev_deps
from usethis._uv.deps import is_dep_used


class Tool(Protocol):
Expand Down Expand Up @@ -63,7 +61,7 @@ def get_associated_ruff_rules(self) -> list[str]:

def is_used(self) -> bool:
"""Whether the tool is being used in the current project."""
return any(_strip_extras(dep) in get_dev_deps() for dep in self.dev_deps)
return any(is_dep_used(dep) for dep in self.dev_deps)

def add_pre_commit_repo_config(self) -> None:
"""Add the tool's pre-commit configuration."""
Expand Down Expand Up @@ -154,42 +152,6 @@ def remove_pyproject_configs(self) -> None:
)
first_removal = False

def add_dev_deps(self) -> None:
"""Add the tool's development dependencies, if not already added."""
existing_dev_deps = get_dev_deps()

for dep in self.dev_deps:
if _strip_extras(dep) in existing_dev_deps:
# Early exit; the tool is already a dev dependency.
continue

console.print(
f"✔ Adding '{dep}' as a development dependency.", style="green"
)
subprocess.run(["uv", "add", "--dev", "--quiet", dep], check=True)

def remove_dev_deps(self) -> None:
"""Remove the tool's development dependencies, if present."""
existing_dev_deps = get_dev_deps()

for dep in self.dev_deps:
if _strip_extras(dep) not in existing_dev_deps:
# Early exit; the tool is already not a dev dependency.
continue

console.print(
f"✔ Removing '{dep}' as a development dependency.",
style="green",
)
subprocess.run(
["uv", "remove", "--dev", "--quiet", _strip_extras(dep)], check=True
)


def _strip_extras(dep: str) -> str:
"""Remove extras from a dependency string."""
return re.sub(r"\[.*\]", "", dep)


class PreCommitTool(Tool):
@property
Expand Down
59 changes: 59 additions & 0 deletions src/usethis/_uv/deps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import re
import subprocess

from packaging.requirements import Requirement
from pydantic import TypeAdapter

from usethis import console
from usethis._pyproject.io import read_pyproject_toml


Expand All @@ -14,3 +18,58 @@ def get_dev_deps() -> list[str]:
req_strs = TypeAdapter(list[str]).validate_python(dev_deps_section)
reqs = [Requirement(req_str) for req_str in req_strs]
return [req.name for req in reqs]


def add_dev_deps(pypi_names: list[str], *, offline: bool) -> None:
"""Add a package as a development dependency, if not already added."""
existing_dev_deps = get_dev_deps()

for dep in pypi_names:
if _strip_extras(dep) in existing_dev_deps:
# Early exit; the tool is already a dev dependency.
continue

console.print(f"✔ Adding '{dep}' as a development dependency.", style="green")
if not offline:
subprocess.run(
["uv", "add", "--dev", "--quiet", dep],
check=True,
)
else:
subprocess.run(
["uv", "add", "--dev", "--quiet", "--offline", dep],
check=True,
)


def remove_dev_deps(pypi_names: list[str], *, offline: bool) -> None:
"""Remove the tool's development dependencies, if present."""
existing_dev_deps = get_dev_deps()

for dep in pypi_names:
if _strip_extras(dep) not in existing_dev_deps:
# Early exit; the tool is already not a dev dependency.
continue

console.print(
f"✔ Removing '{dep}' as a development dependency.",
style="green",
)
if not offline:
subprocess.run(
["uv", "remove", "--dev", "--quiet", _strip_extras(dep)], check=True
)
else:
subprocess.run(
["uv", "remove", "--dev", "--quiet", "--offline", _strip_extras(dep)],
check=True,
)


def is_dep_used(dep: str) -> bool:
return _strip_extras(dep) in get_dev_deps()


def _strip_extras(dep: str) -> str:
"""Remove extras from a dependency string."""
return re.sub(r"\[.*\]", "", dep)
12 changes: 10 additions & 2 deletions src/usethis/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import typer

from usethis import console, offline_opt
from usethis._bitbucket.config import (
add_bitbucket_pipeline_config,
remove_bitbucket_pipeline_config,
Expand All @@ -18,11 +19,14 @@ def bitbucket(
remove: bool = typer.Option(
False, "--remove", help="Remove Bitbucket pipelines CI instead of adding it."
),
offline: bool = offline_opt,
) -> None:
_bitbucket(remove=remove)
_bitbucket(remove=remove, offline=offline)


def _bitbucket(*, remove: bool = False) -> None:
def _bitbucket(*, remove: bool = False, offline: bool = False) -> None:
_ = offline # Already offline

config_yaml_path = Path.cwd() / "bitbucket-pipelines.yml"

if config_yaml_path.exists():
Expand Down Expand Up @@ -70,4 +74,8 @@ def _bitbucket(*, remove: bool = False) -> None:
)
)

console.print("☐ Populate the placeholder step in 'bitbucket-pipelines.yml'.")

add_steps(steps, is_parallel=True)

console.print("☐ Run your first pipeline on the Bitbucket website.")
Loading

0 comments on commit b62bb81

Please sign in to comment.