Skip to content

Commit dba43ff

Browse files
Refactor project setup: Remove setup.py, Optimize Dockerfile, Simplify Config
- Removed redundant `setup.py` as `pyproject.toml` is sufficient. - Updated `Dockerfile` to use `python:3.11-slim-bookworm` for smaller size and better security. - Removed `sudo` from Docker image and run as non-root user. - Simplified `find_claude_binary` in `config.py` to rely on PATH and env var. - Updated `Makefile` to be more robust and consistent. - Added `requests` to test dependencies. - Fixed `black` configuration in `pyproject.toml`. Co-authored-by: Mehdi-Bl <8613869+Mehdi-Bl@users.noreply.github.com>
1 parent 9c072d0 commit dba43ff

7 files changed

Lines changed: 30 additions & 163 deletions

File tree

Makefile

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ kill:
7070
echo "Error: PORT parameter is required. Usage: make kill PORT=8001"; \
7171
else \
7272
echo "Looking for processes on port $(PORT)..."; \
73-
if [ "$$(uname)" = "Darwin" ] || [ "$$(uname)" = "Linux" ]; then \
73+
if command -v lsof >/dev/null 2>&1; then \
7474
PID=$$(lsof -iTCP:$(PORT) -sTCP:LISTEN -t); \
7575
if [ -n "$$PID" ]; then \
7676
echo "Found process(es) with PID(s): $$PID"; \
@@ -79,7 +79,12 @@ kill:
7979
echo "No process found listening on port $(PORT)."; \
8080
fi; \
8181
else \
82-
echo "This command is only supported on Unix-like systems (Linux/macOS)."; \
82+
echo "lsof not found. Trying fuser..."; \
83+
if command -v fuser >/dev/null 2>&1; then \
84+
fuser -k $(PORT)/tcp && echo "Process(es) killed successfully."; \
85+
else \
86+
echo "Neither lsof nor fuser found. Please kill the process manually."; \
87+
fi; \
8388
fi; \
8489
fi
8590

@@ -163,15 +168,6 @@ help:
163168
@echo " make start - Start Python API server (development with reload)"
164169
@echo " make start-prod - Start Python API server (production)"
165170
@echo ""
166-
@echo "TypeScript API:"
167-
@echo " make install-js - Install TypeScript dependencies"
168-
@echo " make test-js - Run TypeScript unit tests"
169-
@echo " make test-js-real - Run Python test suite against TypeScript API"
170-
@echo " make start-js - Start TypeScript API server (production)"
171-
@echo " make start-js-dev - Start TypeScript API server (development with reload)"
172-
@echo " make start-js-prod - Build and start TypeScript API server (production)"
173-
@echo " make build-js - Build TypeScript project"
174-
@echo ""
175171
@echo "Quality & Security:"
176172
@echo " make sonar - Run SonarQube analysis (generates coverage + scans)"
177173
@echo " make sonar-cloud - Run SonarCloud scanner (uses SONAR_CLOUD_TOKEN)"
@@ -186,6 +182,3 @@ help:
186182
@echo "General:"
187183
@echo " make clean - Clean up Python cache files"
188184
@echo " make kill PORT=X - Kill process on specific port"
189-
@echo ""
190-
@echo "IMPORTANT: Both implementations are functionally equivalent!"
191-
@echo "Use Python or TypeScript - both provide the same OpenAI-compatible API."

claude_code_api/core/config.py

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import shutil
5+
from pathlib import Path
56
from typing import List
67

78
from pydantic import Field, field_validator
@@ -11,62 +12,33 @@
1112
def find_claude_binary() -> str:
1213
"""Find Claude binary path automatically."""
1314
# First check environment variable
14-
if "CLAUDE_BINARY_PATH" in os.environ:
15-
claude_path = os.environ["CLAUDE_BINARY_PATH"]
16-
if os.path.exists(claude_path):
17-
return claude_path
15+
env_path = os.environ.get("CLAUDE_BINARY_PATH")
16+
if env_path:
17+
path = Path(env_path)
18+
if path.exists():
19+
return str(path)
1820

19-
# Try to find claude in PATH - this should work for npm global installs
21+
# Try to find claude in PATH
2022
claude_path = shutil.which("claude")
2123
if claude_path:
2224
return claude_path
2325

24-
# Import npm environment if needed
25-
try:
26-
import subprocess
27-
28-
# Try to get npm global bin path
29-
result = subprocess.run(["npm", "bin", "-g"], capture_output=True, text=True)
30-
if result.returncode == 0:
31-
npm_bin_path = result.stdout.strip()
32-
claude_npm_path = os.path.join(npm_bin_path, "claude")
33-
if os.path.exists(claude_npm_path):
34-
return claude_npm_path
35-
except Exception:
36-
pass
37-
38-
# Fallback to common npm/nvm locations
39-
import glob
40-
41-
common_patterns = [
42-
"/usr/local/bin/claude",
43-
"/usr/local/share/nvm/versions/node/*/bin/claude",
44-
"~/.nvm/versions/node/*/bin/claude",
45-
]
46-
47-
for pattern in common_patterns:
48-
expanded_pattern = os.path.expanduser(pattern)
49-
matches = glob.glob(expanded_pattern)
50-
if matches:
51-
# Return the most recent version
52-
return sorted(matches)[-1]
53-
5426
return "claude" # Final fallback
5527

5628

5729
def default_project_root() -> str:
5830
"""Default project root under the current working directory."""
59-
return os.path.join(os.getcwd(), "claude_projects")
31+
return str(Path.cwd() / "claude_projects")
6032

6133

6234
def default_session_map_path() -> str:
6335
"""Default path for CLI-to-API session mapping."""
64-
return os.path.join(os.getcwd(), "claude_sessions", "session_map.json")
36+
return str(Path.cwd() / "claude_sessions" / "session_map.json")
6537

6638

6739
def default_log_file_path() -> str:
6840
"""Default path for application logs."""
69-
return os.path.join(os.getcwd(), "dist", "logs", "claude-code-api.log")
41+
return str(Path.cwd() / "dist" / "logs" / "claude-code-api.log")
7042

7143

7244
def _is_shell_script_line(line: str) -> bool:

claude_code_api/core/security.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,19 @@ def resolve_path_within_base(path: str, base_path: str) -> str:
7878
path_value = os.fspath(path)
7979
normalized_path = os.path.normpath(path_value)
8080
if not os.path.isabs(normalized_path):
81-
if normalized_path == ".." or normalized_path.startswith(f"..{os.path.sep}"):
81+
if normalized_path == ".." or normalized_path.startswith(
82+
f"..{os.path.sep}"
83+
):
8284
raise HTTPException(
8385
status_code=status.HTTP_400_BAD_REQUEST,
8486
detail=PATH_TRAVERSAL_MSG,
8587
)
8688
if os.path.isabs(normalized_path):
8789
resolved_path = os.path.realpath(normalized_path)
8890
else:
89-
resolved_path = os.path.realpath(os.path.join(abs_base_path, normalized_path))
91+
resolved_path = os.path.realpath(
92+
os.path.join(abs_base_path, normalized_path)
93+
)
9094

9195
try:
9296
common_path = os.path.commonpath([abs_base_path, resolved_path])

docker/Dockerfile

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
FROM ubuntu:24.04
1+
FROM python:3.11-slim-bookworm
22

33
ENV DEBIAN_FRONTEND=noninteractive
4+
ENV PYTHONUNBUFFERED=1
45

5-
RUN apt-get update && apt-get install -y \
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
67
bash \
78
ca-certificates \
89
curl \
910
git \
1011
jq \
11-
python3 \
12-
python3-pip \
13-
python3-venv \
14-
sudo \
1512
&& rm -rf /var/lib/apt/lists/*
1613

1714
# Create non-root user
18-
RUN useradd -m -s /bin/bash claudeuser && \
19-
echo "claudeuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
15+
RUN useradd -m -s /bin/bash claudeuser
2016

2117
# Set up application directory
2218
WORKDIR /home/claudeuser/app
@@ -25,16 +21,15 @@ RUN chown -R claudeuser:claudeuser /home/claudeuser/app
2521

2622
USER claudeuser
2723

28-
# Install Claude CLI using the official installer (no npm required)
24+
# Install Claude CLI using the official installer
2925
RUN curl -fsSL https://claude.ai/install.sh | bash
3026

3127
# Create virtualenv and install dependencies
3228
RUN python3 -m venv /home/claudeuser/venv && \
3329
/home/claudeuser/venv/bin/pip install --upgrade pip setuptools wheel && \
34-
/home/claudeuser/venv/bin/pip install -e . --use-pep517 || \
3530
/home/claudeuser/venv/bin/pip install -e .
3631

37-
ENV PATH="/home/claudeuser/venv/bin:/home/claudeuser/.local/bin:/home/claudeuser/.bun/bin:${PATH}"
32+
ENV PATH="/home/claudeuser/venv/bin:/home/claudeuser/.local/bin:${PATH}"
3833

3934
# Create Claude config and workspace directories
4035
RUN mkdir -p /home/claudeuser/.config/claude /home/claudeuser/app/workspace

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ test = [
4949
"pytest-cov>=4.1.0",
5050
"httpx>=0.25.0",
5151
"pytest-mock>=3.12.0",
52+
"requests>=2.31.0",
5253
]
5354
dev = [
5455
"black>=23.0.0",
@@ -108,7 +109,6 @@ exclude_lines = [
108109
[tool.black]
109110
line-length = 88
110111
target-version = ['py311']
111-
include = '\\.pyi?$'
112112
extend-exclude = '''
113113
/(
114114
# directories

setup.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

tests/test_config.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
"""Tests for configuration helpers."""
22

3-
import glob
43
import os
5-
import subprocess
64

75
from claude_code_api.core import config as config_module
86

@@ -22,39 +20,9 @@ def test_find_claude_binary_shutil(monkeypatch):
2220
assert config_module.find_claude_binary() == "/usr/local/bin/claude"
2321

2422

25-
def test_find_claude_binary_npm(monkeypatch, tmp_path):
26-
monkeypatch.delenv("CLAUDE_BINARY_PATH", raising=False)
27-
monkeypatch.setattr(config_module.shutil, "which", lambda _name: None)
28-
29-
npm_bin = tmp_path / "bin"
30-
npm_bin.mkdir()
31-
(npm_bin / "claude").write_text("bin")
32-
33-
class Result:
34-
returncode = 0
35-
stdout = str(npm_bin)
36-
37-
monkeypatch.setattr(subprocess, "run", lambda *_a, **_k: Result())
38-
assert config_module.find_claude_binary() == str(npm_bin / "claude")
39-
40-
41-
def test_find_claude_binary_glob(monkeypatch):
42-
monkeypatch.delenv("CLAUDE_BINARY_PATH", raising=False)
43-
monkeypatch.setattr(config_module.shutil, "which", lambda _name: None)
44-
monkeypatch.setattr(
45-
subprocess, "run", lambda *_a, **_k: (_ for _ in ()).throw(OSError("no"))
46-
)
47-
monkeypatch.setattr(glob, "glob", lambda _pattern: ["/a/claude", "/b/claude"])
48-
assert config_module.find_claude_binary() == "/b/claude"
49-
50-
5123
def test_find_claude_binary_fallback(monkeypatch):
5224
monkeypatch.delenv("CLAUDE_BINARY_PATH", raising=False)
5325
monkeypatch.setattr(config_module.shutil, "which", lambda _name: None)
54-
monkeypatch.setattr(
55-
subprocess, "run", lambda *_a, **_k: (_ for _ in ()).throw(OSError("no"))
56-
)
57-
monkeypatch.setattr(glob, "glob", lambda _pattern: [])
5826
assert config_module.find_claude_binary() == "claude"
5927

6028

0 commit comments

Comments
 (0)