diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 397dba2..e276205 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
- python-version: "3.12"
+ python-version: "3.14"
cache: pip
cache-dependency-path: backend/pyproject.toml
- run: pip install -e 'backend[dev]'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 230f6b7..f44ee9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ All notable changes are documented here. The format follows [Keep a Changelog](h
### Added
- First public version: about 4,100 problems (2,830 runnable against 274k hidden tests), company frequency lists for 423 companies, reference solutions with explanations.
-- Judging in Python 3.12, Java 21, C++23 (Clang 19, as LeetCode uses) and Kotlin 2.0 in a sandboxed runner with no network access.
+- Judging in Python 3.14, Java 25, C++23 (Clang 19) and Kotlin 2.4 in a sandboxed runner with no network access.
- Problem list, company pages, workspace with Run/Submit and custom cases, submission history, progress dashboard and export.
- One-command setup with Docker Compose; content imported from open datasets at first run, with mirror fallbacks.
- Repo-kept content in `content/`: new problems and extra solutions, tests or corrections for existing ones, checked in CI by `make validate-content` and marked "local" in the app.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f71a85b..ad59702 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ Everyone taking part is expected to follow the [Code of Conduct](CODE_OF_CONDUCT
## Getting set up
-You need Docker (with Compose v2), Python 3.12, Node 22 and `make`.
+You need Docker (with Compose v2), Python 3.14, Node 22 and `make`.
```bash
git clone https://github.com/RyPope/localcode.git && cd localcode
diff --git a/Makefile b/Makefile
index de835bb..0bbd250 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ logs: ## Follow app and runner logs
docker compose logs -f
setup: ## Install local dev dependencies (Python venv + web node_modules)
- python3.12 -m venv backend/.venv
+ python3.14 -m venv backend/.venv
$(PY)/pip install -e 'backend[dev]'
npm --prefix web ci
diff --git a/README.md b/README.md
index f1bea81..b5ba4d7 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
About 4,100 problems · 2,830 judged against 274k hidden tests · company frequency lists for 423 companies · Python, Java, C++ and Kotlin
[](LICENSE)
-
+


[](CONTRIBUTING.md)
@@ -95,7 +95,7 @@ flowchart LR
B["Browser
localhost:4173"] --> A
subgraph Docker["Docker Compose"]
A["app
FastAPI + React UI"]
- R["runner
Python 3.12 · JDK 21 · Clang 19 · Kotlin 2.0
no network"]
+ R["runner
Python 3.14 · JDK 25 · Clang 19 · Kotlin 2.4
no network"]
D[("localcode-data volume
content.db · user.db")]
A -- "code + test inputs
(Unix socket)" --> R
R -- "raw outputs" --> A
diff --git a/backend/Dockerfile b/backend/Dockerfile
index b670e70..c1d68e0 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -6,7 +6,7 @@ RUN npm ci --no-audit --no-fund
COPY web/ ./
RUN npm run build
-FROM python:3.12.15-slim-trixie
+FROM python:3.14.10-slim-trixie
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1000 localcode \
diff --git a/backend/localcode/importer/dataset.py b/backend/localcode/importer/dataset.py
index cdccf2c..6932283 100644
--- a/backend/localcode/importer/dataset.py
+++ b/backend/localcode/importer/dataset.py
@@ -100,7 +100,7 @@ def parse_cases(test_src: str, params: list[dict], ret: str = "") -> list[dict]:
names = [p["name"] for p in params]
try:
fn = ast.parse(test_src).body[0]
- except (SyntaxError, IndexError):
+ except SyntaxError, IndexError:
return []
cases = []
for st in getattr(fn, "body", []):
@@ -131,7 +131,7 @@ def parse_cases(test_src: str, params: list[dict], ret: str = "") -> list[dict]:
expected = []
json.dumps(expected) # must be JSON-representable
cases.append({"args": args, "expected": expected})
- except (ValueError, TypeError, SyntaxError):
+ except ValueError, TypeError, SyntaxError:
continue
return cases
diff --git a/backend/localcode/judging/runner_client.py b/backend/localcode/judging/runner_client.py
index d648444..ab64d76 100644
--- a/backend/localcode/judging/runner_client.py
+++ b/backend/localcode/judging/runner_client.py
@@ -73,6 +73,6 @@ def versions() -> dict[str, str] | None:
with _client(endpoint, 60) as client:
_versions = client.get("/health").json()["versions"]
break
- except (httpx.HTTPError, KeyError, ValueError):
+ except httpx.HTTPError, KeyError, ValueError:
continue
return _versions
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index e169ca5..dab12e9 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "localcode"
version = "0.1.0"
-requires-python = ">=3.12"
+requires-python = ">=3.14"
dependencies = ["fastapi==0.162.0", "uvicorn==0.54.0", "httpx==0.28.1", "PyYAML==6.0.3", "markdown-it-py==4.2.0"]
[project.optional-dependencies]
diff --git a/docs/architecture.md b/docs/architecture.md
index 01f8993..043f7ac 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -4,7 +4,7 @@
| Service | Image | Network | Responsibility |
|---|---|---|---|
-| `app` | `backend/Dockerfile` (Python 3.12 + built web UI) | default (for downloads); reaches the runner via `/run/localcode/runner-*.sock` | HTTP API, static UI, content import, judging, user data |
+| `app` | `backend/Dockerfile` (Python 3.14 + built web UI) | default (for downloads); reaches the runner via `/run/localcode/runner-*.sock` | HTTP API, static UI, content import, judging, user data |
| `runner` | `runner/Dockerfile` (pinned toolchains) | none (`network_mode: none`); serves on a Unix socket in the `runner-socket` volume | Compile and execute submissions; return raw per-case results |
Data lives in the `localcode-data` volume mounted at `/data`:
diff --git a/ruff.toml b/ruff.toml
index 533f05a..a008eb8 100644
--- a/ruff.toml
+++ b/ruff.toml
@@ -1,5 +1,5 @@
line-length = 120
-target-version = "py312"
+target-version = "py314"
extend-exclude = ["web", "**/.venv"]
[lint]
diff --git a/runner/Dockerfile b/runner/Dockerfile
index 5a33d6c..14bd41e 100644
--- a/runner/Dockerfile
+++ b/runner/Dockerfile
@@ -1,16 +1,16 @@
# Pinned toolchains: every localcode user judges against exactly these versions.
-FROM python:3.12.15-slim-trixie AS runtime
+FROM python:3.14.10-slim-trixie AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends clang g++ nlohmann-json3-dev curl unzip ca-certificates \
&& rm -rf /var/lib/apt/lists/*
-COPY --from=eclipse-temurin:21.0.5_11-jdk /opt/java/openjdk /opt/java
+COPY --from=eclipse-temurin:25.0.5_8-jdk /opt/java/openjdk /opt/java
# Downloads are verified against pinned SHA-256 checksums (Kotlin's matches JetBrains' published
# .sha256; Gson's was computed from the jar matching Maven Central's published SHA-1).
-ARG KOTLIN_VERSION=2.0.21
-ARG KOTLIN_SHA256=0352c0a45bd22f80f6b26e485cd04da8047baa5de54865281fb9f89a4a7bcf2a
+ARG KOTLIN_VERSION=2.4.21
+ARG KOTLIN_SHA256=7cc140e76daf416a0424a5557f99afd7ca89ebd463ea1101261e3e01c33e75cd
RUN curl -fsSL -o /tmp/k.zip https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip \
&& echo "${KOTLIN_SHA256} /tmp/k.zip" | sha256sum -c - \
&& unzip -q /tmp/k.zip -d /opt && rm /tmp/k.zip
@@ -44,7 +44,7 @@ CMD ["python", "-u", "server.py"]
# Test image: the runtime plus pytest and the test suite (never shipped).
FROM runtime AS test
-RUN pip install --no-cache-dir pytest==8.3.4
+RUN pip install --no-cache-dir pytest==9.2.0
COPY pytest.ini ./
COPY tests/ tests/
CMD ["pytest", "-q", "tests/"]
diff --git a/runner/server.py b/runner/server.py
index 7d3577f..40104c1 100644
--- a/runner/server.py
+++ b/runner/server.py
@@ -29,7 +29,7 @@ def first(cmd):
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return (p.stdout or p.stderr).strip().splitlines()[0]
- except (OSError, subprocess.TimeoutExpired, IndexError):
+ except OSError, subprocess.TimeoutExpired, IndexError:
return "unavailable"
_versions = {