Download index.html from getreadystack/pyside6-migration-lint: direct link, hf CLI and curl.
- Browser
- Download file 38.1 kB
-
https://huggingface.co/spaces/getreadystack/pyside6-migration-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/pyside6-migration-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/pyside6-migration-lint/resolve/main/index.html
38.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>PySide6 PyQt Migration — check your code for free</title> | |
| <meta name="description" content="This free tool flags PyQt imports and PyQt-only APIs that need changes for LGPL PySide6, with a fix for each line."> | |
| <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/pyside6-migration-lint"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static;justify-content:flex-end;margin:0 0 10px}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>PySide6 PyQt Migration — check your code for free</h1> | |
| <p class="lede">Flags PyQt5/PyQt6 imports (GPL v3 or a paid Riverbank licence) and the PyQt-only APIs that break under LGPL PySide6: pyqtSignal, pyqtSlot, uic.loadUi, QRegExp, exec_, sip. One fix per line. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false">"""Invoice viewer - desktop build shipped to customers as a closed-source installer.""" | |
| import sys | |
| from PyQt5 import QtCore, QtWidgets, uic | |
| class InvoiceWindow(QtWidgets.QMainWindow): | |
| saved = QtCore.pyqtSignal(str) | |
| def __init__(self): | |
| super().__init__() | |
| uic.loadUi("invoice.ui", self) | |
| self.number_rule = QtCore.QRegExp(r"INV-\d{6}") | |
| @QtCore.pyqtSlot() | |
| def on_save(self): | |
| self.saved.emit("INV-000142") | |
| if __name__ == "__main__": | |
| app = QtWidgets.QApplication(sys.argv) | |
| window = InvoiceWindow() | |
| window.show() | |
| sys.exit(app.exec_())</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> Every PyQt5/PyQt6 import and PyQt-only API in one .py file, each with its PySide6 replacement line. <strong>In the editor:</strong> The whole workspace in one pass plus an exportable migration report per file for release or licence sign-off — 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 PYS6_RULES = [ | |
| {"id":"pyqt5-import","sev":"error","re":"^\\s*(from|import)\\s+PyQt5\\b","msg":"PyQt5 import: PyQt5 is GPL v3 unless you hold a Riverbank commercial licence (670 USD per developer), and it runs on Qt 5.15, whose standard support ended 2025-05-26.","fix":"from PySide6 import ... (LGPL v3, Qt 6)","date":true}, | |
| {"id":"pyqt6-import","sev":"error","re":"^\\s*(from|import)\\s+PyQt6\\b","msg":"PyQt6 import: PyQt6 is GPL v3 unless you hold a Riverbank commercial licence (670 USD per developer). A closed-source app that ships it under GPL must publish its source.","fix":"from PySide6 import ... (LGPL v3)"}, | |
| {"id":"pyqt-signal","sev":"error","re":"\\bpyqtSignal\\b","msg":"pyqtSignal does not exist in PySide6.","fix":"Signal (from PySide6.QtCore import Signal)"}, | |
| {"id":"pyqt-slot","sev":"error","re":"\\bpyqtSlot\\b","msg":"pyqtSlot does not exist in PySide6.","fix":"Slot (from PySide6.QtCore import Slot)"}, | |
| {"id":"pyqt-property","sev":"error","re":"\\bpyqtProperty\\b","msg":"pyqtProperty does not exist in PySide6.","fix":"Property (from PySide6.QtCore import Property)"}, | |
| {"id":"uic-loadui","sev":"error","re":"\\buic\\.loadUi(Type)?\\b","msg":"uic.loadUi / uic.loadUiType is PyQt-only; PySide6 has no uic module.","fix":"QUiLoader().load(path) from PySide6.QtUiTools, or compile with pyside6-uic"}, | |
| {"id":"sip-module","sev":"error","re":"^\\s*import\\s+sip\\b|\\bsip\\.(isdeleted|delete|wrapinstance|unwrapinstance|cast|setapi)\\b","msg":"sip is PyQt's binding layer; PySide6 uses shiboken6.","fix":"import shiboken6; shiboken6.isValid(obj) / shiboken6.wrapInstance(ptr, cls)"}, | |
| {"id":"pyqt-version-const","sev":"warn","re":"\\bPYQT_VERSION(_STR)?\\b","msg":"PYQT_VERSION / PYQT_VERSION_STR are PyQt-only constants.","fix":"PySide6.__version__ (binding) and qVersion() (Qt)"}, | |
| {"id":"qvariant","sev":"error","re":"\\bQVariant\\b","msg":"QVariant is not exposed in PySide6; values are plain Python objects.","fix":"pass the Python value directly (None, int, str, dict)"}, | |
| {"id":"qregexp","sev":"error","re":"\\bQRegExp\\b","msg":"QRegExp was removed in Qt 6, so PySide6 has no QRegExp.","fix":"QRegularExpression (from PySide6.QtCore import QRegularExpression)"}, | |
| {"id":"qdesktopwidget","sev":"error","re":"\\bQDesktopWidget\\b|\\.desktop\\(\\)","msg":"QDesktopWidget and QApplication.desktop() were removed in Qt 6.","fix":"QGuiApplication.primaryScreen().availableGeometry()"}, | |
| {"id":"fontmetrics-width","sev":"error","re":"fontMetrics\\(\\)\\.width\\(|QFontMetricsF?\\(.*\\)\\.width\\(","msg":"QFontMetrics.width(text) was removed in Qt 6.","fix":"horizontalAdvance(text)"}, | |
| {"id":"qaction-qtwidgets","sev":"error","re":"QtWidgets\\s+import\\s+[^#]*\\bQAction\\b|QtWidgets\\.QAction\\b","msg":"QAction moved from QtWidgets to QtGui in Qt 6.","fix":"from PySide6.QtGui import QAction"}, | |
| {"id":"exec-underscore","sev":"warn","re":"\\.exec_\\(","msg":"exec_() is the Python 2 era alias; PySide6 deprecates it.","fix":".exec()"} | |
| ] | |
| ;</script> | |
| <script>/* PySide6 Migration Lint engine: PyQt5/PyQt6 licence gate + PyQt-only API breaks. Same file runs in node and the browser. */ | |
| (function () { | |
| var DOC = (typeof module !== 'undefined' && typeof require !== 'undefined') ? require('./rules.json') : window.PYS6_RULES; | |
| var RULES = Array.isArray(DOC) ? DOC : (DOC && DOC.rules) || []; | |
| var QT515_EOS = Date.UTC(2025, 4, 26); | |
| function dateTail(today) { | |
| var m = String(today || '').match(/(\d{4})-(\d{2})-(\d{2})/); | |
| if (!m) return ''; | |
| var t = Date.UTC(+m[1], +m[2] - 1, +m[3]); | |
| if (isNaN(t)) return ''; | |
| var d = Math.round((t - QT515_EOS) / 86400000); | |
| if (d > 0) return ' On ' + m[0] + ' that is ' + d + ' days without Qt 5.15 standard support.'; | |
| if (d < 0) return ' On ' + m[0] + ' Qt 5.15 standard support ends in ' + (-d) + ' days.'; | |
| return ' Qt 5.15 standard support ends today.'; | |
| } | |
| // blank out '#' comments that are not inside a string literal | |
| function stripComment(line) { | |
| var q = null; | |
| for (var i = 0; i < line.length; i++) { | |
| var c = line[i]; | |
| if (q) { if (c === '\\') i++; else if (c === q) q = null; } | |
| else if (c === '"' || c === "'") q = c; | |
| else if (c === '#') return line.slice(0, i); | |
| } | |
| return line; | |
| } | |
| var COMPILED = RULES.map(function (r) { return { r: r, re: new RegExp(r.re) }; }); | |
| function check(text, opts) { | |
| opts = opts || {}; | |
| var tail = dateTail(opts.today); | |
| var lines = String(text || '').split(/\r?\n/); | |
| var findings = []; | |
| for (var i = 0; i < lines.length; i++) { | |
| var code = stripComment(lines[i]); | |
| if (!code.trim()) continue; | |
| for (var k = 0; k < COMPILED.length; k++) { | |
| var r = COMPILED[k].r; | |
| if (!COMPILED[k].re.test(code)) continue; | |
| findings.push({ check: r.id, sev: r.sev, line: i + 1, | |
| msg: r.msg + (r.date ? tail : '') + ' Fix: ' + r.fix, src: lines[i].trim() }); | |
| } | |
| } | |
| return { findings: findings, rule_count: RULES.length }; | |
| } | |
| var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = API; | |
| if (typeof window !== 'undefined') window.PYS6ENGINE = API; | |
| })(); | |
| </script> | |
| <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다. | |
| ; | |
| function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } | |
| 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 PYS6REPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = PYS6REPORT; | |
| else window.PYS6REPORT = PYS6REPORT; | |
| </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.PYS6ENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.PYS6REPORT.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://open-vsx.org/extension/readystack/pyside6-migration-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pyside6-migration-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><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" 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 one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind PySide6 Migration Lint: PyQt5/PyQt6 Licence Gate every day. When a rule changes, you get a single email with what changed and the updated check. 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">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</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:"pyside6-migration-lint",lang:"en",source:"rule_alert"+((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);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},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 full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">The whole workspace in one pass plus an exportable migration report per file for release or licence sign-off</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Commercial PyQt licence from Riverbank Computing: 670 USD per developer (price page read 2026-09-29)</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_HtFHnfIyo6aTQYqQSXIrnEmAog30G1oPrJHpq13w45c" 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> | |
| <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><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 {"&":"&","<":"<",">":">",'"':"""}[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><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-29">Updated 2026-09-29</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-29","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/sku388654_result_card.jpg" alt="Migrate from PyQt5 to PySide6: 6 findings in one invoice window" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>6 findings in one 24-line PyQt5 invoice window: that is what a Python developer shipping a closed-source desktop app gets before moving from PyQt5 to PySide6. The import line, pyqtSignal, uic.loadUi, QRegExp, pyqtSlot and exec_() all have to change.</p> | |
| <h3>What changed, and when</h3> | |
| <p>PyQt5 and PyQt6 are dual-licensed by Riverbank Computing: GPL v3, or a commercial licence. Riverbank's price page (read 2026-09-29) lists the commercial licence at 670 USD per developer. If your app is closed source and you ship PyQt under the GPL, the GPL asks you to publish your source. PySide6, the Qt for Python binding from The Qt Company, is LGPL v3, which lets a closed-source app link to it dynamically.</p> | |
| <p>The second clock is Qt itself. PyQt5 runs on Qt 5.15, and The Qt Company ended Qt 5.15 standard support on 2025-05-26; 5.15.19 was the last Qt 5.15 release. On 2026-09-29 that is 491 days without standard support.</p> | |
| <h3>Why find-and-replace fails</h3> | |
| <p>The obvious port is a search for "PyQt5" and a replace with "PySide6". It fixes the imports and nothing else. PySide6 has no pyqtSignal, pyqtSlot or pyqtProperty (it has Signal, Slot and Property). It has no uic module, so uic.loadUi needs QUiLoader or pyside6-uic. It has no QVariant. Qt 6 removed QRegExp, QDesktopWidget and QFontMetrics.width, and moved QAction from QtWidgets to QtGui. The file may import cleanly and then fail on the first signal connection, often on a path the tests never run.</p> | |
| <h3>What the lint does</h3> | |
| <p>PySide6 Migration Lint reads 2 inputs, the .py source text and today's date, and runs 14 rules. Each finding gives the line number and the PySide6 replacement. Comments are ignored, so a note like "# was: from PyQt5 import ..." stays quiet.</p> | |
| <p>On the sample file, dirty.py, it reports:</p> | |
| <ul><li>line 4: from PyQt5 import QtCore, QtWidgets, uic becomes from PySide6 import QtCore, QtWidgets</li><li>line 8: QtCore.pyqtSignal(str) becomes QtCore.Signal(str)</li><li>line 12: uic.loadUi("invoice.ui", self) becomes QUiLoader().load("invoice.ui", self)</li><li>line 13: QtCore.QRegExp(...) becomes QtCore.QRegularExpression(...)</li><li>line 15: @QtCore.pyqtSlot() becomes @QtCore.Slot()</li><li>line 24: app.exec_() becomes app.exec()</li></ul> | |
| <p>The ported clean.py returns 0 findings. The PyQt5 import finding also carries the Qt 5.15 day count for the date you enter, so a file checked on 2025-03-01 says standard support ends in 86 days.</p> | |
| <h3>The 14 rules</h3> | |
| <p>pyqt5-import, pyqt6-import, pyqt-signal, pyqt-slot, pyqt-property, uic-loadui, sip-module, pyqt-version-const, qvariant, qregexp, qdesktopwidget, fontmetrics-width, qaction-qtwidgets and exec-underscore.</p> | |
| <h3>Free and full version</h3> | |
| <p>The free version checks one .py file at a time with all 14 rules, in VS Code or on the web page, with no key. The full version scans the whole workspace in one pass and exports a migration report per file for release or licence sign-off.</p> | |
| <p>It does not rewrite code and it is not legal advice: it shows which lines still tie you to PyQt, so you can port them or keep paying for a commercial PyQt licence.</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/sku388654_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku388654_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 PySide6 Migration Lint do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads 2 inputs, the .py source text and today's date, and runs 14 rules that flag every PyQt5 or PyQt6 import and every PyQt-only API such as pyqtSignal, pyqtSlot, uic.loadUi, QRegExp, sip and exec_(). Each finding names the line and the PySide6 replacement. A sample PyQt5 invoice window returns 6 findings.</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 PySide6 Migration Lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Python developers and small software companies who ship a closed-source desktop application built on PyQt5 or PyQt6, and who want to move to LGPL PySide6 instead of paying for a commercial PyQt licence per developer. It also suits teams still on Qt 5.15, whose standard support ended on 2025-05-26.</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 find-and-replace PyQt5 with PySide6?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A text replace fixes the import lines only. Names like pyqtSignal, pyqtSlot, pyqtProperty, uic.loadUi, QVariant and QRegExp do not exist in PySide6, and QAction moved to QtGui in Qt 6. The file then fails at import time or on the first signal, often on a path your tests never run.</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 needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key: one .py file at a time, with both inputs (source text and date), all 14 rules, and a fix line for every finding, in VS Code or the free web version. The licence key adds the whole workspace in one pass and an exportable migration report per file for release or licence sign-off.</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 does staying on PyQt cost instead?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Riverbank Computing lists its commercial PyQt licence at 670 USD per developer (price page read 2026-09-29), and a closed-source app that ships PyQt under the GPL v3 must publish its source. PySide6 is LGPL v3 from The Qt Company. This lint shows which lines still tie you to PyQt before you choose.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does PySide6 Migration Lint do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads 2 inputs, the .py source text and today's date, and runs 14 rules that flag every PyQt5 or PyQt6 import and every PyQt-only API such as pyqtSignal, pyqtSlot, uic.loadUi, QRegExp, sip and exec_(). Each finding names the line and the PySide6 replacement. A sample PyQt5 invoice window returns 6 findings."}}, {"@type": "Question", "name": "Who is PySide6 Migration Lint for?", "acceptedAnswer": {"@type": "Answer", "text": "Python developers and small software companies who ship a closed-source desktop application built on PyQt5 or PyQt6, and who want to move to LGPL PySide6 instead of paying for a commercial PyQt licence per developer. It also suits teams still on Qt 5.15, whose standard support ended on 2025-05-26."}}, {"@type": "Question", "name": "Why not just find-and-replace PyQt5 with PySide6?", "acceptedAnswer": {"@type": "Answer", "text": "A text replace fixes the import lines only. Names like pyqtSignal, pyqtSlot, pyqtProperty, uic.loadUi, QVariant and QRegExp do not exist in PySide6, and QAction moved to QtGui in Qt 6. The file then fails at import time or on the first signal, often on a path your tests never run."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key: one .py file at a time, with both inputs (source text and date), all 14 rules, and a fix line for every finding, in VS Code or the free web version. The licence key adds the whole workspace in one pass and an exportable migration report per file for release or licence sign-off."}}, {"@type": "Question", "name": "What does staying on PyQt cost instead?", "acceptedAnswer": {"@type": "Answer", "text": "Riverbank Computing lists its commercial PyQt licence at 670 USD per developer (price page read 2026-09-29), and a closed-source app that ships PyQt under the GPL v3 must publish its source. PySide6 is LGPL v3 from The Qt Company. This lint shows which lines still tie you to PyQt before you choose."}}]}</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:388654,slug:"pyside6-migration-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/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pekko-migration-gate"><span class="yj-rel-t">Pekko Migration Gate — Akka BSL licence check</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/vscodium-extension-licence-lint"><span class="yj-rel-t">VSCodium Migration Lint: Microsoft-only Extensions</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/crashlytics-migration-lint"><span class="yj-rel-t">Crashlytics Migration Lint for App Center</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/js-licence-key-gate"><span class="yj-rel-t">JS Licence Key Gate: GPL & Eval Keys</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/maui-ios-submission-gate"><span class="yj-rel-t">MAUI iOS Submission Gate</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> | |