Skip to content

Commit 6ea50bc

Browse files
committed
ci: re-enable pytest, splitting offline tests from live ones
The pytest workflow was disabled because almost every test fetches from Yahoo and live fetches fail intermittently on CI IPs. But 60 of 211 tests are offline unit tests that never touch the network; conftest.py auto-marks the live ones `network`, so PRs run `pytest -m "not network"` while the live suite moves to a scheduled job. data.py also retries empty 2xx bodies (the throttling failure mode) under the existing `retries` config, enabled for tests in context.py.
1 parent ea30eee commit 6ea50bc

6 files changed

Lines changed: 88 additions & 9 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Pytest (live)
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * 1'
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
network:
13+
# Live tests against Yahoo. Flaky by nature, so scheduled-only (off PRs).
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
18+
19+
steps:
20+
- name: Checkout code
21+
# Verify: curl -s "https://api.github.com/repos/actions/checkout/commits?sha=v7&until=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')&per_page=1" | jq -r '.[0].sha'
22+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23+
with:
24+
persist-credentials: false
25+
26+
- name: Install uv
27+
# Verify: curl -s "https://api.github.com/repos/astral-sh/setup-uv/commits?sha=v9.0.0&until=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')&per_page=1" | jq -r '.[0].sha'
28+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
29+
with:
30+
version: "latest"
31+
enable-cache: true
32+
cache-dependency-glob: "pyproject.toml"
33+
34+
- name: Set up Python ${{ matrix.python-version }}
35+
run: uv python install ${{ matrix.python-version }}
36+
37+
- name: Create virtual environment
38+
run: uv venv
39+
40+
- name: Install dependencies
41+
run: uv pip install -e ".[dev]"
42+
43+
- name: Run non-cache tests
44+
run: uv run pytest -m network --ignore tests/test_cache.py --ignore tests/test_price_repair.py
45+
46+
- name: Run cache tests
47+
run: |
48+
uv run pytest tests/test_cache.py::TestCache
49+
uv run pytest tests/test_cache.py::TestCacheNoPermission
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ name: Pytest
33
on:
44
pull_request:
55
branches:
6-
- master
76
- main
87
- dev
98

9+
permissions:
10+
contents: read
11+
1012
jobs:
11-
test:
13+
offline:
14+
# Deterministic tests with no Yahoo access.
1215
runs-on: ubuntu-latest
13-
strategy:
14-
matrix:
15-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
1616

1717
steps:
1818
- name: Checkout code
@@ -29,14 +29,14 @@ jobs:
2929
enable-cache: true
3030
cache-dependency-glob: "pyproject.toml"
3131

32-
- name: Set up Python ${{ matrix.python-version }}
33-
run: uv python install ${{ matrix.python-version }}
32+
- name: Set up Python
33+
run: uv python install 3.12
3434

3535
- name: Create virtual environment
3636
run: uv venv
3737

3838
- name: Install dependencies
3939
run: uv pip install -e ".[dev]"
4040

41-
- name: Run tests
42-
run: uv run pytest tests/ -x -q --tb=short --ignore=tests/test_price_repair.py
41+
- name: Run offline tests
42+
run: uv run pytest -m "not network"

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ yfinance = ["pricing.proto", "pricing_pb2.py"]
7979

8080
[tool.pytest.ini_options]
8181
testpaths = ["tests"]
82+
markers = [
83+
"network: requires live Yahoo Finance network access",
84+
]
8285

8386
[tool.setuptools]
8487
include-package-data = true

‎tests/conftest.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import pytest
2+
3+
# Live tests import tests.context; the rest are offline unit tests. Auto-mark
4+
# the live ones so PR CI can run `-m "not network"`.
5+
_OFFLINE_MODULES = {
6+
"test_auth", "test_data", "test_http",
7+
"test_live", "test_screener", "test_utils",
8+
}
9+
10+
11+
def pytest_collection_modifyitems(config, items):
12+
for item in items:
13+
module = item.module.__name__.rsplit(".", 1)[-1]
14+
if module not in _OFFLINE_MODULES:
15+
item.add_marker(pytest.mark.network)

‎tests/context.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import sys
66
import os
77
import yfinance
8+
from yfinance.config import YfConfig
89
# from requests_ratelimiter import LimiterSession
910
# from pyrate_limiter import Duration, RequestRate, Limiter
1011

12+
# Retry throttled/empty responses from Yahoo on CI IPs.
13+
YfConfig.network.retries = 2
14+
1115
_parent_dp = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
1216
_src_dp = _parent_dp
1317
sys.path.insert(0, _src_dp)

‎yfinance/data.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def _is_transient_error(exception):
2727
}
2828
return error_type_name in transient_error_types
2929

30+
31+
def _is_empty_response(response):
32+
"""Empty 2xx body (Yahoo throttling); retryable."""
33+
return response.status_code < 400 and not response.content
34+
3035
cache_maxsize = 64
3136

3237

@@ -448,6 +453,9 @@ def _make_request(self, url, request_method, body=None, params=None, timeout=30,
448453
for attempt in range(YfConfig.network.retries + 1):
449454
try:
450455
response = request_method(**request_args)
456+
if _is_empty_response(response) and attempt < YfConfig.network.retries:
457+
_time.sleep(2 ** attempt)
458+
continue
451459
break
452460
except Exception as e:
453461
if _is_transient_error(e) and attempt < YfConfig.network.retries:

0 commit comments

Comments
 (0)