Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
[Unreleased]¶
Added¶
- AI agent template (#111) —
templates/ai-agent/with 5 examples: agent state management (sessions, messages, tool calls with JSON columns), MCP tool chain (programmatic server invocation), RAG metadata hybrid (document store with SET tagging + SEQUENCE chunk tracking), agent loop (query → think → act → observe), and AI chatbot backend (SQLAlchemy ORM with JSON LLM I/O). Requirescubrid-mcp-server>=0.3. - One-command dashboard demo —
templates/dashboard/now ships its owndocker-compose.yml(CUBRID 11.4 + Streamlit at http://localhost:8501), and the five recipes readDATABASE_URLfrom the environment (default unchanged:cubrid+pycubrid://dba@localhost:33000/testdb) so the compose service can point them at the co-located container.
Docs¶
- AI agent demo GIF embedded in README — agent state management showing sessions, JSON columns, SET tags.
- 한국어 getting-started 사이트 페이지 (#109) — GETTING_STARTED의 3-경로 사이트 번역판을
docs/ko/getting-started.md로 추가하고 Project → Translations에 노출. - 한국어 README 추가 (
docs/README.ko.md) — README 전문 번역으로 4개 Python 리포 모두 한국어 지원(접근성). 사이트 Project → Translations → 한국어로 노출. 번역 동기화 관리 장치 도입: 모든 번역 파일에 동기화 마커 + docs-sync에translation-sync잡 추가 —README.md가 바뀌는데 번역이 같은 PR에서 안 바뀌면 실패 (translations-deferred라벨로 의도적 보류). - Docs site information architecture unified across the ecosystem — nav aligned to the shared six-tab skeleton (Home / Getting Started / Usage / Reference / Operations / Project): catalog/topic pages and the six templates under Usage, Support Matrix under Reference, Known Issues under Operations, Changelog under Project.
- Added
SUPPORT.md(help channels, templates, security pointer) — root-file parity with the sibling repos instead of relying on the org fallback. - Documentation site launched with full content indexing — mkdocs-material site at https://cubrid-lab.github.io/cubrid-cookbook-python/.
scripts/stage_docs.shstages the repo's real documentation (root README catalog, GETTING_STARTED, SUPPORT_MATRIX, KNOWN_ISSUES, CHANGELOG, fundamentals/performance/pitfalls topic READMEs, all six template READMEs — 19 pages) into the site on every build (CIdocs.ymlandmake docs), so site search indexes the actual docs and the repo remains the single source of truth. README's private org-board link removed (404 for signed-out readers). -
GETTING_STARTED.mdrestructured as the judge/demo entry point — three-path flow (pycubrid first query → ORM/templates →uvx cubrid-mcp-serverwith the four natural-language steps incl. the read-only refusal), expected-output pointers to the CI-checked goldens, and fixed stalefundamentals/connect/-style links that no longer matched the directory layout. -
CUBRID server license line added; copyright notice unified (#99) —
THIRD_PARTY_LICENSES.mdstates the verified upstream licensing (server engine Apache-2.0, APIs/connectors BSD per CUBRID'sCOPYING; GPL v2+ is outdated) and that the examples are independent clients withcubrid/cubridimages used for CI/demo only. LICENSE/NOTICE copyright unified toYeongseon Choe, Gyeongjun Paik(2025-2026), replacing theCUBRID Labsline.
Docs¶
-
Added
THIRD_PARTY_LICENSES.mdandNOTICE— pip-licenses-generated inventory of the union of template/example requirements (all permissive; no GPL), and a NOTICE declaring the examples original with no embedded third-party source. Documentation only. -
Corrected the async recipe's
sqlalchemy-cubridfloor from>=1.0to>=1.4.2(#94) —fundamentals/async/02_async_sqlalchemy.pybuilds anAsyncEnginewith thecubrid+aiopycubrid://URL, but that async dialect (cubrid.aiopycubridentry point) only became installable from PyPI insqlalchemy-cubrid1.2.3 — the entry points were missing from the 1.2.0–1.2.1 releases and 1.2.2 was yanked; 1.2.1 only shipped theget_pool_class()/create_async_engine()fix (#116). So the recipe'ssqlalchemy-cubrid>=1.0pin (in itsrequirements.txt, its printed "Requirements:" line, and the matching golden) advertised a version that cannot run it. Bumped all three to>=1.4.2, matching the floor already used by the other advanced SQLAlchemy recipes (pandas, ORM, Django, dashboard) rather than the bare 1.2.3 minimum.SUPPORT_MATRIX.mdnow carries an[^async]footnote documenting that the sync dialect works from 1.0 while the async recipe needs>=1.4.2. The global README badge stays at>=1.0: the synccubrid+pycubrid://recipes (including all FastAPI templates, which usecreate_engine, notcreate_async_engine) genuinely run on 1.0. - SUPPORT_MATRIX bug fixes (#57) — fixed the "Run all tests" commands, which chained two relative
cds so the second (templates/api-service-fastapi/recipes) resolved againsttemplates/flask/and failed; each is now wrapped in a subshell. Added the previously-omittedasync-worker(Celery) andbatch-etl(Pandas) templates to the recipe-coverage table as manual-run entries and updated the total. (The version-badge/matrix and Python-table inconsistencies from the same issue were already resolved in #80 and #81.) - Reconciled the minimum
sqlalchemy-cubridversion to>=1.0(#80) — the README badge advertised≥1.6.0whileSUPPORT_MATRIX.mdlisted≥1.0, and scatteredrequirements.txtfiles pinned stale/invalid versions (>=2.0.0, which does not exist;>=0.4.1;>=0.3.0). Aligned the badge and the invalid/pre-1.0 stragglers to>=1.0(the documented source of truth); feature-specific>=1.4.2pins were left unchanged. - README fundamentals link renamed to "Parameterized queries" (#82) — the link targeting
fundamentals/parameterized-queries/was labelled "Prepared statements", implying server-side prepare that pycubrid does not do; relabelled to "Parameterized queries" with a client-side note to match the recipe's own README warning. - SUPPORT_MATRIX Python table fixed (#81) — added a 3.14 row and re-sorted the Python versions table into consistent descending order (3.14 → 3.9); previously 3.14 was missing and 3.13 was listed after 3.10.
- SUPPORT_MATRIX corrected to match CI (#72) — the matrix claimed CUBRID 11.4 was "fully supported" and "all 62 recipes pass", but CI only runs
make verify(46 stdout goldens) on CUBRID 11.2 / Python 3.12; the Flask/FastAPI/Streamlit/Django pytest suites and CUBRID 11.4 are never exercised in CI. Reworded the server/Python/recipe tables to state exactly what CI enforces vs. what is run manually or merely expected to work, removing the unenforced green checkmarks.
Changed¶
- FastAPI recipe tests can run against live CUBRID (#122): every
templates/api-service-fastapi/recipes/*/tests/directory now has aconftest.pythat provides the databaseenginefixture. It connects toCUBRID_TEST_URLwhen set and falls back to in-memory SQLite otherwise, points the recipe's ownDATABASE_URLat the same test database, drops and recreates the recipe's tables around each test (so an interrupted run cannot break the next one), and reports which database is in use in the pytest header. The 12test_main.pyfiles lose their copy-pasted SQLite engine setup, and recipes 11 and 12 no longer forceDATABASE_URLto SQLite. All 82 tests pass on CUBRID 11.2, CUBRID 11.4, and the SQLite fallback. The recipe READMEs, the template README, andSUPPORT_MATRIX.mddocumentCUBRID_TEST_URL. - Templates now install
pycubridandsqlalchemy-cubridfrom PyPI instead ofgit+…@main— theasync-worker,batch-etl, andflasktemplates'requirements.txtpinned both drivers to themainbranch heads, so a template install could break whenevermainmoved and required a git toolchain at deploy time. They now pinpycubrid>=1.7,<2andsqlalchemy-cubrid>=1.7,<2(the current PyPI releases). Theapi-service-fastapitemplate'spycubrid>=0.0.4floor — a pre-release number predating the first public PyPI release — is raised to the same>=1.7,<2pin. - Flask recipe tests can run against live CUBRID (groundwork for #131): each of the 11
templates/flask/*/tests/directories now has aconftest.pythat provides adatabase_configfixture forcreate_app(). It usesCUBRID_TEST_URLwhen set and falls back to a temporary SQLite file per test otherwise, drops the recipe's tables before and after each test (so an interrupted run cannot break the next one), disables connection pooling so the per-test apps do not leave idle connections on the CUBRID broker, puts the recipe directory onsys.path, and reports which database is in use in the pytest header. The test files pass the fixture tocreate_app()instead of building SQLite URLs. All 89 tests pass on CUBRID 11.2, CUBRID 11.4, and the SQLite fallback. Every recipe README now has test instructions (previously only 01 and 09 did), andSUPPORT_MATRIX.mddocumentsCUBRID_TEST_URL.
CI¶
- Flask and FastAPI pytest suites run in smoke-test CI (#131):
smoke-test.ymlnow installs the recipe test dependencies and runs everytemplates/flask/*/testsandtemplates/api-service-fastapi/recipes/*/testssuite (23 suites, 171 tests) against the job's live CUBRID container on both 11.2 and 11.4, using theCUBRID_TEST_URLsupport from #132 and #134. The suites run on pushes tomain, the nightly schedule, upstream-release dispatches and manual runs; pull requests skip them so the PR gate stays fast. Each suite runs in its own pytest process; all suites run even after a failure and the step then fails, a suite that collects no tests also fails it, results are written to the job summary, and the step has a 15-minute timeout. The dependency list is explicit rather than each recipe'srequirements.txt, whose pins conflict, and it leaves the releasedpycubridandsqlalchemy-cubridunder test untouched.SUPPORT_MATRIX.mdand the docs home page no longer describe these suites as manual-only. - cubrid-mcp-server now installs from PyPI in smoke tests (was git fallback). Falls back to the v0.4.0 git tag only if PyPI is unreachable.
- Pinned
ruffto0.16.4in CI (#78) — the lint job installedruffunpinned, so formatter/linter rule changes in new ruff releases could break CI unpredictably; pinned to0.16.4to match the version used bypycubridandsqlalchemy-cubrid. - Golden-coverage guard closes the silent-skip gap (#89) — added
scripts/check_expected_coverage.py, wired intomake verify(via a newcheck-coveragetarget) and thesmoke-test.ymlworkflow.make verifyis golden-driven, so any*.pyinside a directory that owns anexpected/folder but lacks a matchingexpected/<name>.expectedgolden was silently never executed in CI. The guard now fails loudly on any such uncovered script (exceptions go inscripts/verify_exclusions.txtwith a reason). - Smoke tests now run a CUBRID 11.2 + 11.4 job matrix —
smoke-test.ymlpreviously started a singlecubrid/cubrid:11.2container; it now runsmake verifygoldens against both 11.2 and 11.4, giving the "expected to work" claim for 11.4 inSUPPORT_MATRIX.mddirect CI evidence.SUPPORT_MATRIX.mdupdated accordingly.
Fixed¶
- The FastAPI template and nine of its recipes could not create their tables on CUBRID (#122):
templates/api-service-fastapi/app/models.pyand recipes 01-08 declared 20 primary key columns withindex=True, and recipe 11 indexedclient_rate_windows.client_idon top of itsUniqueConstraint. CUBRID rejects a second index over already-indexed columns (Index "pk_cookbook_tasks_id" already defined), soBase.metadata.create_all()failed at app startup on both 11.2 and 11.4. SQLite accepts the redundant index, which is why the SQLite-only test suites never caught it. Removed the redundant indexes and documented the behavior asKNOWN_ISSUES.md#5 and in theSUPPORT_MATRIX.mdlimitations table. - FastAPI recipes 02, 04, and 05 were missing
email-validator(#122): their schemas use pydantic'sEmailStr, which requiresemail-validator, but it was not in theirrequirements.txt, so the apps and their tests failed on import after a clean install. Added it asemail-validator>=2. - The documented FastAPI test command could not import recipes 01-08 (#122): the
SUPPORT_MATRIX.mdloop (for d in */tests; do python3 -m pytest "$d" -q; done) failed withModuleNotFoundError: No module named 'database'for recipes 01-08 because nothing put the recipe directory onsys.path. The newconftest.pydoes, so the loop runs all 12 suites. The 01-08 README test instructions now also installpytest pytest-asyncio httpx, which theirrequirements.txtfiles do not include. - Regenerated the two LOB goldens that recorded pycubrid's pre-1.7.0 escaping bug —
fundamentals/pycubrid/expected/06_lob.expectedandfundamentals/lob-handling/expected/06_lob.expectedwere captured while pycubrid silently negotiated the wrongno_backslash_escapesmode (the bug fixed loud in pycubrid 1.7.0, pycubrid#263/#293), so every newline in the round-tripped CLOB text carried a stray backslash (64 chars→66 chars). pycubrid 1.7.0 fixed the corruption, which made the nightly smoke red against these stale goldens since Sep 4. Goldens regenerated from the now-correct output against CUBRID 11.2;make verifypasses 23/23. - Flask recipes 06, 08, and 11 crashed comparing naive and aware datetimes: the
datetime.utcnow()replacement for #30 (#34) switched these apps todatetime.now(timezone.utc), but theirDateTimecolumns store naive UTC values, so comparing a value read back from the database with the new aware "now" raisedTypeError: can't compare offset-naive and offset-aware datetimes. Claiming an already-claimed case (06), leasing outbox messages (08), and confirming or sweeping reservations (11) returned HTTP 500, and 11 of their tests failed; it went unnoticed because the Flask suites do not run in CI. The apps now use anaive_utc_now()helper for these comparisons. Writes of aware UTC values elsewhere (recipes 04, 05, 07) were checked on CUBRID 11.2 and 11.4 and store correctly, so they are unchanged. - Flask recipe 09 could not start from its own
requirements.txt: it did not listpycubridandsqlalchemy-cubrid, socreate_app()failed withCan't load plugin: sqlalchemy.dialects:cubrid.pycubrid. Added both with the template's>=1.7,<2pins. - The documented Flask test command could not import recipe 11: its test module imported
appwithout putting the recipe directory onsys.path, so theSUPPORT_MATRIX.mdloop failed withModuleNotFoundError: No module named 'app'. The newconftest.pyadds it. - De-duplicated the parameterized-queries recipe golden (#79) —
fundamentals/parameterized-queries/04_parameterized.pywas a byte-for-byte copy of the canonicalfundamentals/pycubrid/04_prepared.py, and both shippedexpected/goldens, somake verifycounted the same recipe twice. The topic entry is now a thin redirect to the canonical recipe (itsexpected/golden removed and the folder allowlisted inscripts/docs-sync-allowlist.txt), dropping the CI golden count from 46 to 45 while keeping topic-based discovery intact. - Fixed 3 broken examples the missing-golden gap was hiding (#89) — backfilled
expected/goldens for 11 previously-unverified scripts and, in doing so, uncovered and fixed three broken examples:fundamentals/error-handling/03_query_timeout.py(a server-sidelock_timeoutsystem parameter did not bound the blocked client on CUBRID 11.2, so the demo hung forever — rewritten to use a client-sidepycubrid.connect(..., read_timeout=…));fundamentals/pycubrid/12_pool_retry_worker.py(hung at cleanup because a pooled connection still held a lock whenDROP TABLEran — thefinallynow closes the pool before dropping); andfundamentals/pycubrid/16_batch_error_handling.py(crashed on a duplicate-key retry that assumed post-failure statements were skipped — rewritten to teach the real CUBRID all-or-nothingrollback()semantics, and a module-levelprintthat scrambled output order was moved intomain()). - Added datetime-microsecond and bulk-insert timing rules to
scripts/normalize_output.sh—{{DATE}} HH:MM:SS.ffffffnow normalizes to{{DATETIME}}and theexecute(insert, rows):/add_all:perf-summary lines normalize their seconds to{{TIME}}s, so the merge/serial/bulk-insert examples produce reproducible goldens. Covered by newscripts/test_normalize_output.shcases.
Added¶
- CUBRID-distinctive SQL feature recipes (6 new pycubrid scripts):
fundamentals/pycubrid/17_window_functions.py— ROW_NUMBER/RANK/DENSE_RANK, LAG, running SUM over partitions (with deterministic tie-breakers)fundamentals/pycubrid/18_recursive_cte.py—WITH RECURSIVEnumber series and hierarchy path building (contrast with 08 CONNECT BY)fundamentals/pycubrid/19_pagination.py—LIMIT/OFFSETvs CUBRID-idiomaticFOR ORDERBY_NUM() BETWEEN, plus theROWNUMcaveatfundamentals/pycubrid/20_timezone_datetime.py—DATETIMETZ/DATETIMELTZnative reads andSET TIME ZONE;TIMESTAMPTZrendered via server-sideTO_CHARto sidestep pycubrid#289fundamentals/pycubrid/21_enum_type.py— ENUM declaration-order sorting,col + 0ordinal, out-of-set rejectionfundamentals/pycubrid/22_date_formatting.py—TO_CHAR/TO_DATEdate and number formatting with visible fixed-width padding- v1.6.x feature recipes (8 new scripts):
fundamentals/async/— pycubrid.aio + SQLAlchemy async enginefundamentals/alembic/— programmatic Alembic migration with CubridImplfundamentals/json/— native JSON columns, JSON_EXTRACT/UNQUOTE patternsfundamentals/isolation-levels/— 6 CUBRID levels + dirty-read demofundamentals/sqlalchemy/07_collection_types.py— SET/MULTISET/SEQUENCE ORMfundamentals/pycubrid/15_cursor_memory_bound.py— fetch_size + tracemallocfundamentals/pycubrid/16_batch_error_handling.py— executemany_batch error paths
Previous Releases¶
- Python examples: FastAPI, Django, Flask, SQLAlchemy, pycubrid, Pandas, Celery, Streamlit
- llms.txt for AI agent discoverability
- PRD with Example-first Design Philosophy
Changed¶
- Refactored to Python-only repository (removed planned Go and Node.js examples)
Fixed¶
- Python lint errors and code formatting across all examples
- All examples verified against live CUBRID instance
fundamentals/sqlalchemy/07_collection_types.py— SET/MULTISET/SEQUENCE collection columns now render correct single-quoted SQL literals (with quote escaping) instead of malformed inline SQL, and the example is verified against a goldenexpected/07_collection_types.expectedoutput (Closes #56)