ChangeLog
3.0.16 - July 13, 2026
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__.pysofrom 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_dirsfix — 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 thebody_encodingsetting[cli]Generatedcement generate projectoutput now builds under pip's default PEP 517 isolation on Python 3.10+ (the legacysetup.pyself-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.pyso 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 acrosssend/_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 fromstrtobytes—subprocess.Popenreturns bytes by default and the existing tests assert byte literals; docstrings now point attext=True/encoding=forstroutput (runtime unchanged)[utils.shell]Correct thePrompt.Meta.optionsannotation fromdict | 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 repeatedminimal_logger(ns)calls no longer stack duplicate handlers (which caused log output to repeat N times)[ext.daemon]Remove a duplicate module-scopeLOG = minimal_logger(__name__)rebind[core.template]Replace the fragile bareassertinTemplateHandler.copy()with an explicitNotADirectoryError(the assert was stripped underpython -Oand passed for regular files). Compatibility note: the raised exception type changes fromAssertionErrortoNotADirectoryError[core.interface]String-quote alist[str]return annotation for autodoc compatibility[core.deprecations]Drop a trailing period from the3.0.10-1deprecation 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(nowexcept 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 generatedprojectandtodo-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_dirsvia the[<app>]section — accepts a list (native-list config handlers) or a comma-separated string (the INI handler), parallel to the existingextensionshandling.[ext.generate]Add optional features support to generate templates — conditional variables, exclude/ignore patterns, and order-independentrequiresdependency resolution (with transitive cascade).[ext.generate]Addprompt_mode: selectfor multi-valued feature prompts — a numbered picker dispatching the chosen value into one of Noptionsbranches, 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 inextend: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 neverKeyError. 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 onArgparseControllerso an exposed command can read its ownCommandMetafrom 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_metaproperty that resolves the controller's default sub-command meta (viaMeta.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 thevirtualenvtarget 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 fromtypingtocollections.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_filesos.path callsite to pathlib; retain the publicjoin = os.path.joinalias 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/andcement/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 (mirrorsgenerate 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 in03-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.shellcmd()/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 ownmake 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
# 3.0.14 - May 5, 2025
Bugs:
[ext_jinja2]Refactor hard-coded reference tojinja2template handler.[ext_smtp]Misc fixes and updates to better support content types.
Features:
None
Refactoring:
None
Misc:
None
Deprecations:
None
3.0.12 - Nov 10, 2024
Bugs:
None
Features:
None
Refactoring:
[dev]Refactor String Substitutions (%s) with F-Strings[dev]Allow line lengths up to 100 characters (previously 78)[dev]Modernize Packaging (pyproject.toml, PDM)[dev]Implement Ruff for Code Compliance (replaces Flake8)[dev]Remove Python 3.5, 3.6, 3.7 Docker Dev Targets[dev]Added Python 3.13 Dev Target[dev]Testing now requires typing compliance (make test->make comply-mypy)[dev]Type Annotations (related: PR #628)[core.arg]Issue #692[core.cache]Issue #693[core.config]Issue #694[core.controller]Issue #695[core.deprecations]Issue #696[core.exc]Issue #697[core.extension]Issue #698[core.foundation]Issue #699[core.handler]Issue #700[core.hook]Issue #700[core.interface]Issue #702[core.log]Issue #703[core.mail]Issue #704[core.meta]Issue #705[core.output]Issue #706[core.plugin]Issue #707[core.template]Issue #708[ext.alarm]Issue #709[ext.argparse]Issue #710[ext.colorlog]Issue #711[ext.configparser]Issue #712[ext.daemon]Issue #713[ext.dummy]Issue #714[ext.generate]Issue #715[ext.jinja2]Issue #716[ext.json]Issue #717[ext.logging]Issue #718[ext.memcached]Issue #719[ext.mustache]Issue #720[ext.plugin]Issue #721[ext.print]Issue #722[ext.redis]Issue #723[ext.scrub]Issue #724[ext.smtp]Issue #725[ext.tabulate]Issue #726[ext.watchdog]Issue #727[ext.yaml]Issue #728[utils.fs]Issue #688[utils.misc]Issue #689[utils.shell]Issue #690[utils.version]Issue #691
Misc:
[cli] Move CLI dependencies to
cement[cli]extras package, and remove included/nextedcontribsources. See note on 'Potential Upgrade Incompatibility'
Deprecations:
None
Special Recognitions:
Many thanks to @sigma67 for their contributions in modernizing the packaging system. Cement was started in 2009, and has some lingering technical debt that is now being addressed. Their contribution was a major help in moving off of setuptools and on to PDM and pyproject.toml, along with initial implementations of Ruff for a new generation of code compliance. I sincerely appreciate your help!
Many thanks to @rednar for their contributions toward adding type annotations in PR #628. This PR was too large to merge directly, but it is serving as a guide to finally begin work toward adding type annotations to Cement. This was a massive effort, and is very helpful to have this work available to guide the effort even if it will not be merged directly.
Potential Upgrade Incompatibility:
This update removes included contrib libraries that are dependencies for the cement command line tool to function (PyYAML, and Jinja2). The dependencies are now included via the cement[cli] extras package.
This is not an upgrade incompatibility in the core Cement code, and it would not affect any applications that are built on Cement. That said, it does have the potential to break any automation or other uses of the cement command line tool.
Resolution:
3.0.10 - Feb 28, 2024
Bugs:
[ext.logging]Supportlogging.propagateto avoid duplicate log entries[core.foundation]Quiet mode file is never closed[ext.smtp]Ability to Enable TLS without SSL[ext.smtp]Empty (wrong) addresses sent when CC/BCC isNone
Features:
[utils.fs]Add Timestamp Support to fs.backup[ext.smtp]Support for sending file attachements.[ext.smtp]Support for sending both Plain Text and HTML
Refactoring:
[core.plugin]Deprecate the use ofimpin favor ofimportlib[ext.smtp]Actually test SMTP against a real server (replace mocks)
Misc:
[dev]Add Smoke tests for Python 3.11, 3.12[dev]Make Python 3.12 the default development target[dev]Drop support for Python 3.7[docker]Base official Docker image on Python 3.12[utils.version]Resolve deprecateddatetime.utcfromtimestamp()[dev]Addcomply-typingto make helpers, start working toward typing.[dev]Addmailpitservice to docker-compose development config.
Deprecations:
[ext.logging]Deprecate FATAL facility in favor of CRITICAL.
3.0.8 - Aug 18, 2022
Bugs:
[cli]Cement CLI broken on Python 3.10[cli]Generated script returns version of Cement[cli]Generated script should allow dash/underscore[core.foundation]App.render() not suppressed in quiet mode[core.foundation]Console log not suppressed by output handler override (JSON/YAML)
Features:
[utils.shell]Supportsuppressmeta option onPromptto suppress user input.[ext]Useextras_requirefor optional extensions
Refactoring:
[utils.misc]Use SHA256 instead of MD5 inrando()to support Redhap/FIPS compliance[core.foundation]Make quiet/debug options configurable
Misc:
[dev]Cement CLI smoke tests[dev]Add Python 3.10 to Travis CI tests[core.deprecations]Implement Cement deprecation warnings
Deprecations:
[core.foundation]Deprecate CEMENT_FRAMEWORK_LOGGING in favor of CEMENT_LOG.
3.0.6 - Dec 18, 2021
Bugs:
[ext.argparse]Parser (self._parser) not accessible inside_pre_argument_parsingwhenstacked_type = 'embedded'[ext.configparser]Overriding config options with environment variables doesn't work correctly with surrounding underscore characters[utils.fs]Fix bug where trailing slash was not removed infs.backup()of a directory.[cement.cli]Generated README contains incorrect installation instructions.
Features:
None
Refactoring:
[ext.colorlog]Support subclassing of ext_colorlog.
Misc:
[dev]Update to Python 3.10 for default development / Docker version.[dev]Remove Python 3.5/3.6 from Travis CI tests.
3.0.4 - May 17, 2019
Bugs:
[ext.yaml]YamlConfigHandler uses unsafe load method[ext.configparser]Configparser 'getboolean' exception
Features:
[utils.misc]Supportyas a truth boolean inutils.misc.is_true
3.0.2 - November 6, 2018
Bugs:
[cli]Generate Variable Mishap in Project Template[ext.generator]Error class is malformed[core.template]MemoryError during 'cement generate project'[core.foundation]Contents of plugin_dirs is printed to console
Features:
[ext.argparse]Command name override
3.0.0 - Aug 21, 2018
Bugs:
[ext.redis]Unable To Set Redis Host[ext.argparse]Empty Sub-Commands List[core.foundation]Handler Override Options Do Not Honor Meta Defaults
Features:
[core]Add Docker / Docker Compose Support[core]Add ability to override the output handler used whenapp.render()is called.[ext.print]Add the Print Extension to be used as a drop in replacement for the standardprint(), but allowing the developer to honor framework features likepre_renderandpost_renderhooks.[ext.scrub]Add Scrub Extension to easily obfuscate sensitive data from rendered output.[core]Add ability to override config settings via environment variables.[ext.argparse]Add ability to get list of exposed commands[core]Add Template Interface[ext.mustache]Add MustacheTemplateHandler[ext.handlebars]Add HandlebarsTemplateHandler[ext.jinja2]Add Jinja2TemplateHandler[ext.generate]Add Generate Extension[ext.logging]Add-l LEVELcommand line option to override log level[cli]Add Cement CLI (includes ability to generate apps, plugins,extensions, and scripts using the Generate Extension)
[core]Added clear separation between Interfaces and Handlers[utils.fs]- Added several helpers includefs.Tmpfor creation and cleanup of temporary directory and file.
Refactoring:
Too many to reference
Incompatible:
[core]Replace Interfaces with ABC Base Classes[core.foundation]RenameCementApptoApp.[core.foundation]Drop deprecatedApp.Meta.override_arguments[core.foundation]RemoveApp.Meta.plugin_config_dirandApp.Meta.plugin_config_dirsin favor ofApp.Meta.config_dirs[core.founcation]RenameApp.Meta.plugin_bootstrapasApp.Meta.plugin_module[core.handler]RenameCementBaseHandlertoHandler[core.handler]Drop deprecated backend globals[core.hook]Drop deprecated backend globals[core.controller]DropCementBaseController[ext.logging]Drop deprecatedwarnfacility (usewarning)[ext.argcomplete]Drop ArgComplete Extension[ext.reload_config]Drop Reload Config Extension[ext.configobj]Drop ConfigObj Extension[ext.json]Disableoverridableoption by default[ext.yaml]Disableoverridableoption by default[ext.json_configobj]Drop JSON ConfigObj Extension[ext.yaml_configobj]Drop YAML ConfigObj Extension[ext.handlebars]Drop Handlebars Extension[ext.genshi]Drop Genshi Extension[ext.argparse]ArgparseController.Meta.default_funcis now_default, and will print help info and exit. Can now set this toNoneas well to pass/exit.[ext.plugin]All plugin configuration sections must start withplugin..For example,
[plugin.myplugin].[core.foundation]RenamedApp.Meta.config_extensiontoApp.Meta.config_file_suffix[core.foundation]DropApp.Meta.arguments_override_config
Deprecation:
Everything with deprecation notices in Cement < 3
Last updated