Repository navigation
This release modernizes Cement against the current Python ecosystem while
holding the 3.0.x public API stable. Python 3.8 and 3.9 are dropped (EOL) and
the supported matrix is now Python 3.10–3.14. Every deprecation in this release
is warn-only, with removals signposted for 3.2.0 so downstream apps have a clear
upgrade window. A new automated GitHub Actions release workflow replaces the
manual release checklist, and the cement generate project and todo-tutorial
templates are now fully typed, PEP 517-buildable, and green under make comply
and make test out of the box.
Bugs:
[core]Add an explicit__all__tocement/__init__.pyso
from cement import *exports only the intended public surface (App,
TestApp, Interface, Handler, Controller, the framework exceptions, and
the documented helpers) instead of leaking every transitively imported
name — #756[core.foundation]HonorMeta.config_section(notMeta.label) when
applying theextensionsconfig override, mirroring thetemplate_dirs
fix — resolves #777[ext.smtp]Fixtimeoutbeing passed aslocal_hostnamein the SMTP
constructors[ext.smtp]Fix a stale variable reference in_get_paramsfor
per-message headers[ext.smtp]Fix an SMTP connection leak when a send fails with an
exception[ext.smtp]Only log on send errors (was logging unconditionally on
every send)[ext.smtp]Fix header encoding being incorrectly affected by the
body_encodingsetting[cli]Generatedcement generate projectoutput now builds under pip's
default PEP 517 isolation on Python 3.10+ (the legacysetup.py
self-imported the package being built, which fails in isolated build
envs)[core.handler]Resolve a mypy union-attr false-positive in handler
resolution[ext.redis]Resolve mypy union-attr/arg-type/misc errors surfaced by
redis 7 typing changes[ext.watchdog]Drop now-unused# type: ignorecomments on the
Observer calls — watchdog 6 ships precise type stubs[utils.fs]Restoreos.pathsemantics inabspath()— preserve
symlink paths and fall through on unknown~userprefixes (regression
from the Phase 03 pathlib migration; restores the 3.0.x BC contract)[dev]Use explicitencoding='utf-8'inscripts/audit-public-api.py
so themake audit-public-apigate is portable across non-UTF-8 locales[dev]Fixscripts/cli-smoke-test.shcapturing a CRLF-tainted temp
path fromdocker exec -tthat broke pdm/venv on the first Python
version (drop-ton that one capture)[dev]Fixscripts/cli-smoke-test.shpdm installfailing to resolve
the unreleased dev cement pin by injecting a local find_links source
(/src/dist); harness-only, the project gates are unaffected[core.interface]Widen theInterfaceManager.getfallbackparameter
back toAnyto match the documented contract (it was mechanically
narrowed by the Phase 03 UP045 sweep; the runtime always accepted
arbitrary fallback values)[ext.smtp]Type the message body and**paramscorrectly across
send/_make_messageand the related private helpers (new private
_BodyTypealias reflects the dict body shape); drop nine now-unused
# type: ignoresuppressions. Public API byte-identical[utils.shell]Correct thecmd/exec_cmdreturn annotations from
strtobytes—subprocess.Popenreturns bytes by default and the
existing tests assert byte literals; docstrings now point at
text=True/encoding=forstroutput (runtime unchanged)[utils.shell]Correct thePrompt.Meta.optionsannotation from
dict | Nonetolist[str] | Noneto match actual runtime usage[ext.yaml]Use explicittemplate: str | None = None(PEP 484 /
RUF013) inrender()and drop the signature-line# type: ignore[ext.mustache]Use explicittemplate: str | None = None(PEP 484 /
RUF013) inrender(); scope the remaining# type: ignoreto the load
callsite[utils.misc]MakeMinimalLogger.__init__idempotent — guard the
console-handler add onnot self.backend.handlersso repeated
minimal_logger(ns)calls no longer stack duplicate handlers (which
caused log output to repeat N times)[ext.daemon]Remove a duplicate module-scope
LOG = minimal_logger(__name__)rebind[core.template]Replace the fragile bareassertin
TemplateHandler.copy()with an explicitNotADirectoryError(the
assert was stripped underpython -Oand passed for regular files).
Compatibility note: the raised exception type changes from
AssertionErrortoNotADirectoryError[core.interface]String-quote alist[str]return annotation for
autodoc compatibility[core.deprecations]Drop a trailing period from the3.0.10-1
deprecation message (it rendered as..oncedeprecate()appended
its suffix)[dev]make docszero-warnings gate now uses&&(was;) so it
fails on Sphinx warnings[ext.generate]Fix thevariablesdefault from{}to[]— the
generate flow iteratesvariablesas a list of dicts[ext.generate]Narrow the dynamic-template-moduleexceptto
(AttributeError, ModuleNotFoundError)with a name guard so transitive
import errors in a user's template module propagate normally[ext.generate]type: booleanvariables now emit a real Python bool
at the top level (data[name]) so{% if feature_x %}works; boolean
prompts use a vars-style[(Y)es/(N)o]format in a single
declaration-order pass — resolves #782 (thefeatures:namespace is
removed)[cli]Generatedcement generate todo-tutorialoutput now builds under
PEP 517 isolation on Python 3.10+ (mirrors thegenerate projectfix) —
#735[cli]Fix aNameErrorin generatedtodo/main.py(now
except TodoError as e:) — #735[cli]Fix a false-green assertion in the generated project test
template — a membership check replacesstr.find(), which returned a
truthy-1and never failed — #735[cli]Generated todo-tutorial imports are isort-clean and its
[tool.ruff]is scoped totodo/, so the shippedmake complyis green
out of the box — #735[cli]Constrain the cement dependency in generatedprojectand
todo-tutorialtemplates to a compatible 3.0.x range (~=3.0.0/
>=3.0,<3.1) so freshly generated projects install even when generated
by an unreleased dev cement — #735
Features:
[core.foundation]Support config override ofApp.Meta.template_dirs
via the[<app>]section — accepts a list (native-list config handlers)
or a comma-separated string (the INI handler), parallel to the existing
extensionshandling.[ext.generate]Add optional features support to generate templates —
conditional variables, exclude/ignore patterns, and order-independent
requiresdependency resolution (with transitive cascade).[ext.generate]Addprompt_mode: selectfor multi-valued feature
prompts — a numbered picker dispatching the chosen value into one of N
optionsbranches, each with its ownignore/exclude/variables;
defaults toboolean(byte-identical when absent).[ext.generate]Addtype: choicetemplate variables — a numbered
picker that emits the chosen option string at the top level;options:
accepts scalars or{value, prompt}objects with per-option effects in
extend:rules; misconfig is fail-fastValueError.[ext.generate]type: booleanprompt:is now polymorphic — an object
form{text, accept, reject}lets the template author own the prompt
text and supply the token lists that map input to a real bool; unmatched
input aborts withInvalid Response.[ext.generate]extend.whennow composes scalar-equality, in-list
membership, and string-regex match forms (all matching rules fire) with
nested depth-firstextend.variables; a new top-levelrequires:key
gates variables using the same vocabulary, AND-ed and resolved
order-independently, defaulting gated-out variables so templates never
KeyError. Also addresses the PR #780 review feedback (features prompt
after vars, custom prompt text, vars-style input).[ext.argparse]Add a read-only_command_metaproperty on
ArgparseControllerso an exposed command can read its ownCommandMeta
from inside its body; returnsNoneoutside a dispatched command and
never raises. Additive — thefunc()dispatch signature is unchanged.[ext.argparse]Add a companion read-only_default_command_meta
property that resolves the controller's default sub-command meta (via
Meta.default_func); returnsNonewhen there is no exposed default and
never raises.[utils.misc]Add an optionalCEMENT_FRAMEWORK_LOG_FILEenv var — when
framework logging is enabled, debug output is also written to the given
file. Purely additive: no duplicate handlers on repeat calls, and an
invalid path is ignored rather than raising.
Refactoring:
[ext.generate]Remove the unreleasedfeatures:schema wholesale —
everything it expressed is now atype: boolean/type: choicevariable
carryingextend:/requires:; the legacy compatibility bridge is
deleted (#782)[dev]Migrate thedemo/generate-features/webapp template to the
unifiedtype:/extend:/requires:schema and demonstrate the #782 fix
(top-level{% if docker %}/{% if web_framework == ... %})[ext.smtp]PEP 8 naming, idiomatic string methods, and cleaner type
validation[ext.smtp]Refactor_make_messageinto focused private methods[ext.smtp]Simplify X-header normalization and preserve original casing[dev]Python 3.14 default development target[dev]Remove support for Python 3.8 (EOL)[dev]Remove support for Python 3.9 (EOL)[cli]Migrate thecement generate projecttemplate from
setuptools+setup.pyto pdm-backend with full PEP 621 metadata and PEP
735 dev deps; generatedversion.pyno longer imports cement at build
time[cli]Generated projectREADME.md/Makefilenow document Python
3.10+ and PDM, usepip install .for end users, and rename the
virtualenvtarget tosetup(pdm install)[core]Modernize type annotations to PEP 585 builtin generics (UP006:
List→list,Dict→dict,Tuple→tuple,Type→type); prune
orphanedtypingre-exports[core]Modernize union types to PEP 604 syntax (UP007:Union[X, Y]→
X | Y); prune orphanedtyping.Unionimports[core]Modernize Optional types to PEP 604 syntax (UP045:
Optional[X]→X | None); prune orphanedtyping.Optionalimports[core]MoveCallable/Generatorimports fromtypingto
collections.abc(UP035)[core]Convert printf-style format strings to modern format (UP031);
protected.format(**template_dict)template callsites preserved[core]Drop redundant(object)base classes (UP004)[core]Simplifysuper()calls to the zero-arg form (UP008)[core]Drop the redundant'r'mode argument fromopen()calls
(UP015)[core]Replace theIOErroralias withOSError(UP024)[dev]Drop the legacyu"..."unicode literal prefix in test code
(UP025)[dev]Replace the deprecatedmockimport withunittest.mock(UP026)[core]Replacefor x in iterable: yield xwithyield from(UP028)[core]Convert.format()calls to f-strings (UP032); protected
template callsites preserved[core]TightenAnytypes incement/core/where narrower types are
provably correct; survivingAnycarries inline justification[dev]Refresh CONVENTIONS.md type-annotation guidance to PEP 585 / PEP
604 syntax[core]Wrap long log/error messages (E501) and reorder imports (I001)
surfaced by the UP sweeps[core]Dropfrom __future__ import annotationsfrom all 29cement/
files (native on 3.10+); convert affected forward references to PEP 484
string annotations[utils.fs]Migratecement/utils/fs.pyinternals to pathlib while
preservingstrreturn boundaries (public surface unchanged)[core.config]Migrate theconfig.pyparse_fileos.path callsite to
pathlib; retainimport osas a no-op to keep the public surface intact[core.foundation]Migrate thefoundation.py_find_config_files
os.path callsite to pathlib; retain the publicjoin = os.path.join
alias and leave protected template callsites untouched[core.template]Migratetemplate.pyos.path internals to pathlib;
retain theos.walk(src)callsite (no direct pathlib equivalent for the
triple-tuple loop)[dev]Auditpragma: nocoversites incement/core/with
locked-vocabulary category labels[dev]Auditpragma: nocoversites incement/ext/(first half) with
locked-vocabulary category labels[dev]Auditpragma: nocoversites incement/ext/(second half) with
locked-vocabulary category labels[dev]Auditpragma: nocoversites incement/cli/and
cement/utils/with locked-vocabulary category labels[core.deprecations]Pin the3.0.10-1and3.0.16-1deprecation
removal versions to v3.2.0[ext.logging]Tighten the FATAL deprecation removal version in
docstrings[ext.smtp]Document thesend()bool-return removal in v3.2.0[cli]Migrate thecement generate todo-tutorialtemplate to
pdm-backend with full PEP 621 metadata and a PEP 735 dev group (mirrors
generate project; #735)[cli]Ship[tool.ruff]/[tool.mypy]/[tool.pytest]gate config in
the generated project and todo-tutorial somake comply/make testare
green out of the box (#735)[cli]Type-annotate all generated templates
(project/script/extension/plugin/todo) and modernize idioms to f-strings
(#735)
Misc:
[ci]Add GitHub Actions PR CI (build_and_test.yml) running the test
suite on pull requests with minimal permissions — #757[ci]Add an automated release workflow (release.yml) — tag-triggered:
preflight guard → gate suite → isolated build → TestPyPI publish +
5-Python install smoke → environment-gated OIDC PyPI publish →
post-approval fan-out (Docker Hub multi-arch,stable/3.0.xsync, RTD
re-point, GitHub Release, dev-bump PR, checklist issue);
workflow_dispatchruns it as a dry run[ci]Refactor the PR gate chain into a reusablegates.yml
(workflow_call) shared by PR CI and release; wire the matrix Python
versions; add (disabled) Windows core-test and macOS/Windows native smoke
gates[dev]Add release dev-tooling scripts:testpypi-smoke.py,
cli-smoke-native.py,bump_dev_version.py[dev]Add devbox/direnv development-environment configuration for
reproducible local setup[ext.smtp]Isolate test defaults to prevent cross-test state pollution[dev]Bump ruff to 0.15.x; codify rule sets explicitly and resolve all
surfacing lint findings[dev]Bump mypy to ~=1.20.2 and codify the type-check surface[dev]Bump pytest 9.0.3, pytest-cov 7.1.0, coverage 7.13.5[dev]Add amake cli-smoke-testtarget — generated-project install
smoke across Python 3.10–3.14 in Docker[dev]Bump the dev/extras lockfile to current non-breaking versions
(redis 7.4, watchdog 6.0, tabulate 0.10, sphinx 8.1, requests 2.33,
others)[dev]Wire the 100% coverage gate via[tool.coverage.report]
fail_under+--cov-fail-under[ci]Pin GitHub Actions to exact tags[ci]Add PyPy 3.11 to the CI test matrix (alongside PyPy 3.10)[ci]Enable Dependabot for the github-actions ecosystem (weekly)[ci]Add aworkflow_dispatchtrigger topdm.yml[dev]Add amake audit-public-apitarget + AST-walk public-surface
enumerator + baseline snapshot[dev]Enable ruffUP(pyupgrade) andFA(flake8-future-annotations)
families[dev]Capture the Phase 03 Any-in-core / pragma / pathlib baselines in
03-VERIFICATION.md[dev]Finalize Phase 03 verification (all D-24 conjuncts green;
REFACTOR-01..04 + COV-01..03 satisfied)[dev]Complete Phase 03 (Internal Refactor & Coverage Hardening);
ROADMAP updated[docs]Drop the unsupportedlogotheme option from the Sphinx config[docs]Remove the orphandocs/source/api/index.rst[docs]Renamedisplay_versiontoversion_selector(sphinx_rtd_theme
3.x)[docs]Fix inline-literal RST in theshell.cmd()docstring[docs]Add a top-level DEPRECATIONS.md mirroring the GitBook narrative[dev]Wire-Wintomake docs(zero-warnings gate)[docs]Drop the Travis CI link/badge (CI moved to GitHub Actions)[docs]Align CONTRIBUTING with Conventional Commits + atomic-per-concern[docs]Exposecement.core.deprecationsin the Sphinx API reference
(was missing)[docs]Fix astderror→stderrtypo in thecement.utils.shell
cmd()/exec_cmd()Returns docstrings[docs]Remove an orphaned[Commit Guidelines]reference-link
definition from.github/CONTRIBUTING.md[dev]Extend the CIcli-smoke-testto gate the generated project's own
make comply/make testand to build/install the generated todo-tutorial
(#735)[dev]Add a generated-todo ruff-clean regression guard
(test_generate_todo_ruff_clean) (#735)
Deprecations:
[ext.smtp]SMTPMailHandler.send()returningboolis deprecated
(warn-only); it will return asenderrsdict, with removal targeted for
v3.2.0