getreadystack's picture
Upload index.html with huggingface_hub
a73b6d6 verified
Raw History Blame Contribute Delete
44.1 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Pytest 8 migration check — check pytest changes free</title>
<meta name="description" content="Find pytest APIs removed in pytest 8 and ignored configuration keys, with replacement lines and removal versions.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/pytest-deprecation-lint"></head>
<body>
<div class="wrap">
<h1>Pytest 8 migration check — check pytest changes free</h1>
<p class="lede">Flags pytest APIs removed in pytest 8 and pytest config keys that are silently ignored, with the replacement line and the version that removed each one. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">"""Checkout tests — generated by an assistant, last touched before the pytest 8 bump."""
import unittest
import warnings
import pytest
from shop.checkout import Cart, apply_coupon, CouponError
@pytest.yield_fixture
def cart():
c = Cart(currency="EUR")
yield c
c.close()
@pytest.mark.skipif(not pytest.config.getoption("--slow"), reason="slow")
@pytest.fixture
def big_cart(cart):
for i in range(500):
cart.add(f"sku-{i}", 1)
return cart
def setup(self):
self.seen = []
def test_writes_receipt(cart, tmpdir):
path = tmpdir.join("receipt.txt")
cart.add("sku-1", 2)
cart.write_receipt(str(path))
assert path.read().startswith("RECEIPT")
def test_expired_coupon_warns(cart):
with pytest.warns(None) as record:
apply_coupon(cart, "SUMMER2024")
assert len(record) == 1
def test_unknown_coupon(cart):
with pytest.raises(CouponError, message="coupon should be rejected"):
apply_coupon(cart, "NOPE")
def test_skips_without_backend(cart):
if not cart.backend_available():
pytest.skip(msg="no payment backend in this environment")
async def test_async_totals(cart):
cart.add("sku-9", 3)
total = await cart.total_async()
assert total == 3
@pytest.fixture
def event_loop():
import asyncio
loop = asyncio.new_event_loop()
yield loop
loop.close()
class TestRefunds(unittest.TestCase):
@pytest.mark.parametrize("amount,expected", [(10, 10), (0, 0)])
def test_refund_amounts(self, amount, expected):
self.assertEqual(Cart(currency="EUR").refund(amount), expected)
def pytest_collection_modifyitems(items):
for item in items:
if item.get_marker("slow"):
item.add_marker(pytest.mark.skip(reason="slow"))</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Lint the pytest file you have open and see every removed API and every silently-ignored config key, each with the pytest version that removed it and the exact replacement line.. <strong>In the editor:</strong> Audit every test file, conftest.py and config file in the workspace in one pass and export the dated findings as Markdown or JSON you keep and paste into CI. — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var PYTEST_DEP_RULES = [
{"id":"warns_none","sev":"error","re":"pytest\\.warns\\s*\\(\\s*None\\s*\\)",
"msg":"pytest.warns(None) was removed in pytest 8.0 — it now raises instead of running the block.",
"fix":"pytest.warns(DeprecationWarning) for a specific class, or warnings.catch_warnings(record=True) to assert that nothing warned."},
{"id":"nose_method_setup","sev":"error","re":"^\\s+def\\s+(setup|teardown)\\s*\\(\\s*self\\s*\\)\\s*:",
"msg":"nose-style setup(self)/teardown(self) methods stopped being called in pytest 8.0 — the body never runs and the test still reports green.",
"fix":"def setup_method(self, method): / def teardown_method(self, method):"},
{"id":"nose_module_setup","sev":"error","re":"^def\\s+(setup|teardown)\\s*\\(\\s*\\)\\s*:",
"msg":"nose-style module-level setup()/teardown() stopped being called in pytest 8.0.",
"fix":"def setup_module(module): / def teardown_module(module):"},
{"id":"pyproject_tool_pytest","sev":"error","re":"^\\s*\\[tool\\.pytest\\]\\s*$",
"msg":"[tool.pytest] is not a table pytest reads — every key under it (addopts, markers, filterwarnings, testpaths) is silently ignored and the suite still exits 0.",
"fix":"[tool.pytest.ini_options]"},
{"id":"setupcfg_pytest_section","sev":"error","re":"^\\s*\\[pytest\\]\\s*$","ctx":"^\\s*\\[(metadata|options|flake8|isort|mypy|bdist_wheel)\\]",
"msg":"A [pytest] section in setup.cfg is no longer supported — pytest errors out or ignores it.",
"fix":"[tool:pytest]"},
{"id":"yield_fixture","sev":"error","re":"@pytest\\.yield_fixture",
"msg":"pytest.yield_fixture was deprecated in pytest 3.0 and removed in pytest 6.2 — collection fails with AttributeError.",
"fix":"@pytest.fixture (a plain fixture may yield)"},
{"id":"get_marker","sev":"error","re":"\\.get_marker\\s*\\(",
"msg":"Node.get_marker() was removed in pytest 4.0.",
"fix":"item.get_closest_marker(\"name\") or item.iter_markers(\"name\")"},
{"id":"pytest_config_global","sev":"error","re":"\\bpytest\\.config\\b",
"msg":"The global pytest.config object was removed in pytest 5.0.",
"fix":"the pytestconfig fixture, or request.config inside a fixture/hook"},
{"id":"raises_message","sev":"error","re":"pytest\\.raises\\([^)]*\\bmessage\\s*=",
"msg":"pytest.raises(..., message=...) was removed in pytest 5.0 — the kwarg is now a TypeError.",
"fix":"pytest.raises(Err, match=\"regex\")"},
{"id":"funcarg_prefix","sev":"error","re":"^\\s*def\\s+pytest_funcarg__",
"msg":"The pytest_funcarg__ fixture prefix was removed in pytest 4.0 — the function is collected as nothing.",
"fix":"@pytest.fixture above a normally named function"},
{"id":"pytest_namespace","sev":"error","re":"\\bpytest_namespace\\b",
"msg":"The pytest_namespace hook was removed in pytest 4.0 — the hook is never called.",
"fix":"import the helper from your own module, or expose it as a fixture"},
{"id":"parametrize_on_testcase","sev":"error","re":"@pytest\\.mark\\.parametrize","ctx":"class\\s+\\w+\\s*\\(\\s*(unittest\\.)?TestCase\\s*\\)",
"msg":"@pytest.mark.parametrize does not work on unittest.TestCase subclasses — pytest refuses to inject the arguments, so the case never runs as written.",
"fix":"drop the TestCase base (a plain class works with pytest), or use TestCase.subTest"},
{"id":"event_loop_fixture","sev":"error","re":"^\\s*def\\s+event_loop\\s*\\(",
"msg":"Redefining the event_loop fixture was deprecated in pytest-asyncio 0.23 and removed in pytest-asyncio 1.0 — the override is ignored.",
"fix":"override the event_loop_policy fixture, or set loop_scope= on the asyncio marker"},
{"id":"async_test_unmarked","sev":"warn","re":"^\\s*async\\s+def\\s+test_","prev_not":"@pytest\\.mark\\.(asyncio|anyio)|@pytest\\.mark\\.parametrize",
"msg":"An unmarked async test is skipped with a warning unless pytest-asyncio runs in auto mode (asyncio_mode = \"auto\") — the assertions inside never execute.",
"fix":"@pytest.mark.asyncio above the test, or asyncio_mode = \"auto\" under [tool.pytest.ini_options]"},
{"id":"tmpdir_fixture","sev":"warn","re":"\\btmpdir\\b",
"msg":"tmpdir hands back a py.path.local object from the unmaintained py library; pytest documents tmp_path as the replacement.",
"fix":"tmp_path (pathlib.Path) and the tmp_path_factory fixture"},
{"id":"strict_flag","sev":"warn","re":"--strict(?![-\\w])",
"msg":"--strict is the deprecated spelling and is documented for removal.",
"fix":"--strict-markers (add --strict-config for config keys)"},
{"id":"result_log","sev":"error","re":"--result-log",
"msg":"--result-log was removed in pytest 6.0 — the run aborts with \"unrecognized arguments\".",
"fix":"--junit-xml=report.xml, or the pytest-reportlog plugin"},
{"id":"msg_kwarg","sev":"warn","re":"pytest\\.(skip|fail|exit)\\s*\\(\\s*msg\\s*=",
"msg":"The msg= keyword of pytest.skip/fail/exit was deprecated in pytest 7.0 in favour of reason=.",
"fix":"pytest.skip(reason=\"...\")"},
{"id":"mark_on_fixture","sev":"warn","re":"^\\s*@pytest\\.mark\\.","next_re":"^\\s*@pytest\\.fixture",
"msg":"Marks applied to a fixture have no effect — pytest ignores them, so the skipif/parametrize you wrote there is dead.",
"fix":"move the mark onto the test, or branch inside the fixture with request.getfixturevalue"}
]
;</script>
<script>/* Pytest Deprecation Lint — one brain, used by the extension and by the free web page. */
(function (root) {
var RULES = (typeof module !== 'undefined' && module.exports)
? require('./rules.json')
: root.PYTEST_DEP_RULES;
function compiled(r) {
if (!r._re) {
r._re = new RegExp(r.re);
if (r.ctx) r._ctx = new RegExp(r.ctx, 'm');
if (r.prev_not) r._prev = new RegExp(r.prev_not);
if (r.next_re) r._next = new RegExp(r.next_re);
}
return r;
}
function codeLine(s) {
var t = s.trim();
return t !== '' && t.charAt(0) !== '#';
}
function check(text, opts) {
opts = opts || {};
var src = String(text == null ? '' : text);
var lines = src.split(/\r?\n/);
var findings = [];
for (var i = 0; i < RULES.length; i++) {
var r = compiled(RULES[i]);
if (r._ctx && !r._ctx.test(src)) continue;
for (var n = 0; n < lines.length; n++) {
var line = lines[n];
if (!codeLine(line)) continue;
if (!r._re.test(line)) continue;
if (r._prev) {
var p = n - 1;
while (p >= 0 && lines[p].trim() === '') p--;
if (p >= 0 && r._prev.test(lines[p])) continue;
}
if (r._next) {
var q = n + 1;
while (q < lines.length && lines[q].trim() === '') q++;
if (q >= lines.length || !r._next.test(lines[q])) continue;
}
findings.push({
check: r.id,
sev: r.sev,
line: n + 1,
msg: r.msg,
fix: r.fix,
text: line.trim().slice(0, 160)
});
}
}
findings.sort(function (a, b) { return a.line - b.line; });
return {
findings: findings,
scanned_lines: lines.length,
rule_count: RULES.length,
errors: findings.filter(function (f) { return f.sev === 'error'; }).length,
warnings: findings.filter(function (f) { return f.sev === 'warn'; }).length,
today: opts.today || ''
};
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
root.PYTESTDEPENGINE = api;
if (typeof module !== 'undefined' && module.exports) module.exports = api;
})(typeof window !== 'undefined' ? window : globalThis);
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var PYTESTDEPREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = PYTESTDEPREPORT;
else window.PYTESTDEPREPORT = PYTESTDEPREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.PYTESTDEPENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.PYTESTDEPREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.pytest-deprecation-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pytest-deprecation-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/pytest-deprecation-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pytest-deprecation-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/pytest-deprecation-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pytest-deprecation-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/pytest-deprecation-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pytest-deprecation-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/pytest-deprecation-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pytest-deprecation-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.pytest-deprecation-lint
npx @readystack/pytest-deprecation-lint &lt;file&gt;
npx @readystack/pytest-deprecation-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 19 rules Pytest Deprecation Lint (pytest 8 removals) checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"pytest-deprecation-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Marks the lines in your test suite that current pytest no longer runs — and the config keys it silently ignores.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A contract Python engineer reviewing the same suite for pytest 8 breakage by hand bills roughly $80-$120 an hour in the US market.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_OR0dShZDbbiVxsy2XNVTaEV9nNVJY9nD92Tgf3TsG1F" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-14">Updated 2026-09-14</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-14","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku72262_result_card.jpg" alt="Pytest Deprecation Lint (pytest 8 removals)" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Twelve findings, seven of them fatal, in a 75-line pytest file: that is what a Python team running an unpinned pytest in CI is carrying without seeing it. The file is the checkout suite shipped as _fixtures/dirty.py with Pytest Deprecation Lint. It was written against pytest 6, it reads fine, and it passed for years.</p>
<p>Then the runner installs pytest 8.</p>
<p>Seven of those lines pytest 8 will not run as written:</p>
<p>@pytest.yield_fixture -&gt; @pytest.fixture pytest.config.getoption(...) -&gt; the pytestconfig fixture pytest.warns(None) -&gt; pytest.warns(DeprecationWarning) pytest.raises(Err, message=) -&gt; pytest.raises(Err, match=&quot;...&quot;) def event_loop(): -&gt; def event_loop_policy(): @parametrize on TestCase -&gt; drop the unittest.TestCase base item.get_marker(&quot;slow&quot;) -&gt; item.get_closest_marker(&quot;slow&quot;)</p>
<p>pytest.warns(None) went away in pytest 8.0, yield_fixture in 6.2, the message= keyword and the global pytest.config object in 5.0, get_marker() in 4.0. Redefining the event_loop fixture was removed in pytest-asyncio 1.0.</p>
<p>The loud ones are the cheap ones: a TypeError in CI is visible, and somebody fixes it within the hour.</p>
<p>The expensive half is quiet.</p>
<p>@pytest.mark.parametrize on a unittest.TestCase subclass does not error in the obvious place - pytest simply will not inject the arguments, so the case you think covers two amounts covers neither. A mark stacked above @pytest.fixture is ignored outright, so the skipif guarding your slow fixture is decoration. An async def test_ with no asyncio marker and no asyncio_mode = &quot;auto&quot; is skipped with a warning, and a skipped test is a green test. Nose-style setup(self) methods stopped being called in pytest 8.0: the body never runs, and the assertions below it run against an unprepared object.</p>
<p>And the quietest one is not code at all. pytest reads [tool.pytest.ini_options] in pyproject.toml. It does not read [tool.pytest]. Put your addopts, markers, filterwarnings and testpaths under the shorter table and pytest ignores all of it without printing a word - the coverage gate you think is enforcing 80% is not running, and the suite still exits 0.</p>
<p>This is where generated test code lands hardest. An assistant trained on years of pytest 6 and 7 examples writes yield_fixture, pytest.warns(None) and message=, because that is what most of the code it read looks like. The test it produces is plausible, reviewable and stale.</p>
<p>Pytest Deprecation Lint is a VS Code extension and a browser page sharing one rule table of 19 rules. Open a test file, conftest.py, pytest.ini, pyproject.toml, setup.cfg or tox.ini, run &quot;Check this file&quot;, and each affected line shows up in the Problems panel with the version that removed it and the replacement line. On the migrated copy of that same suite, _fixtures/clean.py, it reports 0.</p>
<p>Ruff, flake8 and pylint will not tell you any of this. They model Python, not pytest&#x27;s removal history and not its config tables, and pytest itself only reports a removal on the machine that has already upgraded - usually CI, after the merge.</p>
<p>Linting the file you have open is free, all 19 rules, no key and no account: https://getreadystack.com/tools/pytest-deprecation-lint</p>
<p>The paid tier is a different job rather than a bigger one - sweeping every test file, conftest.py and config file in the workspace in one pass and exporting the dated findings as Markdown or JSON you keep for the CI job or the migration ticket. A contract Python engineer doing that reading by hand bills roughly $80 to $120 an hour in the US market.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku72262_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku72262_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Pytest Deprecation Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a test file, conftest.py, pytest.ini, pyproject.toml, setup.cfg or tox.ini and flags each line that current pytest handles differently than it reads: APIs removed in pytest 4.0 through 8.0, and config tables pytest ignores. Every finding names the removal version and prints the replacement line.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Python developers and QA engineers holding a pytest suite that predates the pytest 8 bump, and anyone reviewing test code an assistant generated. Assistants were trained on years of pytest 6 and 7 examples, so yield_fixture, pytest.warns(None) and message= keep reappearing in new files.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a free linter not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Ruff, flake8 and pylint check Python, not pytest&#x27;s own removal history or its config tables. None of them tells you that a [tool.pytest] table in pyproject.toml is being ignored, that a mark above a fixture does nothing, or that parametrize on a TestCase subclass never injects its arguments.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what costs money?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Linting the file you have open is free and complete: all 19 rules, every finding, every replacement line, no key and no account. The paid tier adds a different job, not a bigger one: sweeping the whole workspace in one pass and exporting dated findings as Markdown or JSON you keep.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost done by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Reading a mid-sized suite line by line against the pytest changelog is an afternoon for someone who knows pytest, and a contract Python engineer in the US market bills roughly $80 to $120 an hour for it. The extension is $29 once,.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Pytest Lint - pytest 8 deprecations and removals checks the file you open against 19 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Pytest Deprecation Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a test file, conftest.py, pytest.ini, pyproject.toml, setup.cfg or tox.ini and flags each line that current pytest handles differently than it reads: APIs removed in pytest 4.0 through 8.0, and config tables pytest ignores. Every finding names the removal version and prints the replacement line."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Python developers and QA engineers holding a pytest suite that predates the pytest 8 bump, and anyone reviewing test code an assistant generated. Assistants were trained on years of pytest 6 and 7 examples, so yield_fixture, pytest.warns(None) and message= keep reappearing in new files."}}, {"@type": "Question", "name": "Why is a free linter not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Ruff, flake8 and pylint check Python, not pytest's own removal history or its config tables. None of them tells you that a [tool.pytest] table in pyproject.toml is being ignored, that a mark above a fixture does nothing, or that parametrize on a TestCase subclass never injects its arguments."}}, {"@type": "Question", "name": "What is free and what costs money?", "acceptedAnswer": {"@type": "Answer", "text": "Linting the file you have open is free and complete: all 19 rules, every finding, every replacement line, no key and no account. The paid tier adds a different job, not a bigger one: sweeping the whole workspace in one pass and exporting dated findings as Markdown or JSON you keep."}}, {"@type": "Question", "name": "What would this cost done by hand?", "acceptedAnswer": {"@type": "Answer", "text": "Reading a mid-sized suite line by line against the pytest changelog is an afternoon for someone who knows pytest, and a contract Python engineer in the US market bills roughly $80 to $120 an hour for it. The extension is $29 once,."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Pytest Lint - pytest 8 deprecations and removals checks the file you open against 19 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:72262,slug:"pytest-deprecation-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/config-dsl-lint-snippet-pack"><span class="yj-rel-t">Config &amp; DSL Lint + Snippets — 9 Sets</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/devicetree-config-language-pack"><span class="yj-rel-t">DeviceTree Config Audit: 23 dtc-Style Rules, 50 Snippets</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/en18031-psti-lint"><span class="yj-rel-t">EN 18031 / PSTI Device Config Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/gnu-global-cpp-vscode-config-pack"><span class="yj-rel-t">GNU Global C/C++ .vscode Config Pack</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/actions-deprecation-lint-2026"><span class="yj-rel-t">GitHub Actions Deprecation Lint: the 2026 runner and action EOL dates</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mcp-config-guard"><span class="yj-rel-t">MCP Config Guard - agent config lint</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>