Skip to content

Commit a29196f

Browse files
author
sheeek
committed
fix: resolve all linting errors and update CHANGELOG for v0.1.1
- Fix isinstance calls to use modern union syntax (dict | list) - Simplify ternary operators where applicable - Remove unused variables - Add ruff ignore rules for test-specific warnings (S105, S106, A004) - Add ignore rule for module name 'site' (A005) - Fix unused parameter in render_table function - Update CHANGELOG.md with v0.1.1 release notes (Homebrew support) All 55 tests passing, all linting checks pass.
1 parent 1bf5120 commit a29196f

8 files changed

Lines changed: 52 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.1] - 2026-01-08
9+
10+
### Added
11+
- **Homebrew installation support** via `pasogott/tap`
12+
- Stable release installation: `brew install pasogott/tap/frappecli`
13+
- HEAD installation option: `brew install --HEAD pasogott/tap/frappecli`
14+
- Automatic Homebrew tap notification workflow on new releases
15+
- Auto-update workflow in homebrew-tap for new releases
16+
17+
### Changed
18+
- Updated README with Homebrew installation instructions (recommended method for macOS)
19+
- Improved installation documentation with stable vs HEAD options
20+
21+
### Fixed
22+
- GitHub Actions CI dependency installation
23+
824
## [0.1.0] - 2026-01-08
925

1026
### Added
@@ -98,5 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98114
- [Repository](https://github.com/pasogott/frappecli)
99115
- [Issues](https://github.com/pasogott/frappecli/issues)
100116
- [Releases](https://github.com/pasogott/frappecli/releases)
117+
- [Homebrew Tap](https://github.com/pasogott/homebrew-tap)
101118

119+
[0.1.1]: https://github.com/pasogott/frappecli/releases/tag/v0.1.1
102120
[0.1.0]: https://github.com/pasogott/frappecli/releases/tag/v0.1.0

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,18 @@ ignore = [
8989
[tool.ruff.lint.per-file-ignores]
9090
"tests/**/*.py" = [
9191
"S101", # assert allowed in tests
92+
"S105", # hardcoded passwords ok in tests
93+
"S106", # hardcoded passwords ok in tests
9294
"ARG", # unused arguments ok in tests (fixtures)
9395
"PLR2004", # magic values ok in tests
96+
"A004", # shadowing builtins ok in tests
9497
]
9598
"src/frappecli/cli.py" = [
9699
"T201", # print allowed in CLI
97100
]
101+
"src/frappecli/commands/site.py" = [
102+
"A005", # module name 'site' is intentional
103+
]
98104

99105
[tool.ruff.lint.isort]
100106
known-first-party = ["frappecli"]

src/frappecli/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _build_url(self, path: str, params: dict[str, Any] | None = None) -> str:
8686
# Convert dict/list params to JSON strings for Frappe API
8787
encoded_params = {}
8888
for key, value in params.items():
89-
if isinstance(value, (dict, list)):
89+
if isinstance(value, dict | list):
9090
# Serialize as JSON string
9191
encoded_params[key] = json.dumps(value)
9292
else:

src/frappecli/commands/files.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ def upload_file(
5353
# Open file and prepare multipart data
5454
with file_path.open("rb") as f:
5555
files = {"file": (file_path.name, f, "application/octet-stream")}
56-
56+
5757
# Remove Content-Type header for multipart upload
5858
# requests will set it automatically with boundary
5959
original_content_type = client.session.headers.pop("Content-Type", None)
60-
60+
6161
try:
6262
# Use raw session request for multipart upload
6363
response = client.session.post(
@@ -83,7 +83,7 @@ def upload_file(
8383
error_msg = error_data.get("exception", response.text)
8484
except Exception:
8585
error_msg = response.text
86-
86+
8787
console.print(f"[red]✗ Upload failed (HTTP {response.status_code}):[/red]")
8888
console.print(f"[red]{error_msg}[/red]")
8989
except Exception as e:
@@ -155,7 +155,7 @@ def render_table(data: list[dict] | list[str] | dict) -> None:
155155
if isinstance(data, dict):
156156
# Response might be wrapped in a dict
157157
data = data.get("files", data.get("data", [data]))
158-
158+
159159
if not data:
160160
console.print("[yellow]No files found[/yellow]")
161161
return
@@ -232,7 +232,7 @@ def bulk_upload(
232232

233233
# Parse pattern - support both absolute and relative paths
234234
pattern_path = Path(pattern)
235-
235+
236236
if pattern_path.is_absolute():
237237
# Absolute path with glob pattern
238238
base_dir = pattern_path.parent
@@ -241,12 +241,9 @@ def bulk_upload(
241241
# Relative path
242242
base_dir = Path.cwd()
243243
pattern_name = pattern
244-
244+
245245
# Find files
246-
if recursive:
247-
files = list(base_dir.rglob(pattern_name))
248-
else:
249-
files = list(base_dir.glob(pattern_name))
246+
files = list(base_dir.rglob(pattern_name)) if recursive else list(base_dir.glob(pattern_name))
250247

251248
if not files:
252249
console.print(f"[yellow]No files found matching pattern: {pattern}[/yellow]")
@@ -282,7 +279,9 @@ def bulk_upload(
282279
success += 1
283280
else:
284281
failed += 1
285-
console.print(f"[red]✗ Failed: {file_path.name} (HTTP {response.status_code})[/red]")
282+
console.print(
283+
f"[red]✗ Failed: {file_path.name} (HTTP {response.status_code})[/red]"
284+
)
286285

287286
except Exception as e:
288287
failed += 1

src/frappecli/commands/reports.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ def execute_report(
101101
for row in data:
102102
all_fieldnames.update(row.keys())
103103
fieldnames = sorted(all_fieldnames)
104-
105-
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
104+
105+
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
106106
writer.writeheader()
107107
writer.writerows(data)
108108
else:
@@ -145,7 +145,7 @@ def render_table(data: any) -> None:
145145
console.print(f"\n[bold cyan]Result from {method}:[/bold cyan]\n")
146146
if data is None:
147147
console.print("[yellow]No return value[/yellow]")
148-
elif isinstance(data, (dict, list)):
148+
elif isinstance(data, dict | list):
149149
console.print(json.dumps(data, indent=2))
150150
else:
151151
console.print(str(data))

src/frappecli/commands/site.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def status(ctx: click.Context, detailed: bool) -> None:
205205

206206
# Test connectivity by fetching doctypes count
207207
try:
208-
result = client.post(
208+
client.post(
209209
"/api/method/frappe.client.get_list",
210210
data={
211211
"doctype": "DocType",
@@ -214,18 +214,18 @@ def status(ctx: click.Context, detailed: bool) -> None:
214214
},
215215
)
216216
is_reachable = True
217-
doctype_count = len(result) if result else 0
218217
except Exception as e:
219218
is_reachable = False
220219
error_msg = str(e)
221220

222-
def render_table(data: dict | list) -> None:
221+
def render_table(_data: dict | list) -> None:
222+
"""Render site status as table (data parameter unused but required by output_data)."""
223223
console.print("\n[bold cyan]Site Status[/bold cyan]\n")
224224

225225
if is_reachable:
226226
console.print(f"[green]✓[/green] Site is reachable at: {client.base_url}")
227-
console.print(f"[green]Status:[/green] Online")
228-
227+
console.print("[green]Status:[/green] Online")
228+
229229
if detailed:
230230
console.print("\n[dim]Note: Version info not available via API[/dim]")
231231
else:

tests/test_client.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import responses
88
from requests.exceptions import ConnectionError, Timeout
99

10-
from frappecli.client import FrappeClient, FrappeAPIError, FrappeConnectionError
10+
from frappecli.client import FrappeAPIError, FrappeClient, FrappeConnectionError
1111

1212

1313
@pytest.fixture
@@ -96,9 +96,7 @@ def test_put_request(self, client, api_responses):
9696
status=200,
9797
)
9898

99-
result = client.put(
100-
"/api/resource/User/test@example.com", data={"enabled": 0}
101-
)
99+
result = client.put("/api/resource/User/test@example.com", data={"enabled": 0})
102100
assert result == api_responses["success_response"]["message"]
103101

104102
@responses.activate
@@ -205,9 +203,7 @@ def test_connection_error(self, client):
205203
@responses.activate
206204
def test_timeout_error(self, client):
207205
"""Test timeout error handling."""
208-
responses.add(
209-
responses.GET, "https://test.example.com/api/test", body=Timeout("Timeout")
210-
)
206+
responses.add(responses.GET, "https://test.example.com/api/test", body=Timeout("Timeout"))
211207

212208
with pytest.raises(FrappeConnectionError, match="Request timed out"):
213209
client.get("/api/test")

tests/test_config.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Tests for configuration management."""
22

3-
import os
43
from pathlib import Path
54

65
import pytest
@@ -56,7 +55,7 @@ def test_get_site_config(self, test_config_path):
5655
"""Test retrieving site configuration."""
5756
config = Config(test_config_path)
5857
site_config = config.get_site_config("production")
59-
58+
6059
assert site_config is not None
6160
assert site_config["url"] == "https://erp.example.com"
6261
assert site_config["api_key"] == "prod_key_123"
@@ -72,15 +71,15 @@ def test_get_default_site_config(self, test_config_path):
7271
"""Test retrieving default site configuration."""
7372
config = Config(test_config_path)
7473
site_config = config.get_default_site_config()
75-
74+
7675
assert site_config is not None
7776
assert site_config["url"] == "https://erp.example.com"
7877

7978
def test_list_sites(self, test_config_path):
8079
"""Test listing all configured sites."""
8180
config = Config(test_config_path)
8281
sites = config.list_sites()
83-
82+
8483
assert len(sites) == 3
8584
assert "production" in sites
8685
assert "staging" in sites
@@ -94,18 +93,18 @@ def test_env_var_substitution(self, test_config_path, monkeypatch):
9493
"""Test environment variable substitution."""
9594
monkeypatch.setenv("FRAPPE_STAGING_KEY", "staging_key_from_env")
9695
monkeypatch.setenv("FRAPPE_STAGING_SECRET", "staging_secret_from_env")
97-
96+
9897
config = Config(test_config_path)
9998
site_config = config.get_site_config("staging")
100-
99+
101100
assert site_config["api_key"] == "staging_key_from_env"
102101
assert site_config["api_secret"] == "staging_secret_from_env"
103102

104103
def test_env_var_missing(self, test_config_path, monkeypatch):
105104
"""Test missing environment variable."""
106105
monkeypatch.delenv("FRAPPE_STAGING_KEY", raising=False)
107106
monkeypatch.delenv("FRAPPE_STAGING_SECRET", raising=False)
108-
107+
109108
config = Config(test_config_path)
110109
with pytest.raises(ConfigError, match="Environment variable.*not set"):
111110
config.get_site_config("staging")

0 commit comments

Comments
 (0)