Changelog¶
This page records user-visible release changes, including unpublished attempts.
Unreleased¶
0.1.0b4 — 2026-10-20¶
Changed¶
- Reduce response-render time with the next three changes: compiled templates,
a faster validation registry and one extra-schema check per response. An initial
combined measurement of those changes, before the flatter
r4schema described below, took 23 to 52 percent less time than 0.1.0b3 for a fullHyperviewTemplateResponserender of a HyperTodo-shaped workload with one extra schema. It used 8 interleaved runs and reports the median of the medians of the runs. These are pre-r4results, not a final beta4-versus-beta3 benchmark; the laterr4measurements below are incremental and their percentages must not be added to these. On Python 3.12 a navigation fragment (1 KB rendered) took 1.79 ms before those three changes and 1.11 after them, a list of 40 rows (73 KB rendered) 32.5 ms and 24.9, a settings screen (20 KB rendered) 10.3 ms and 4.95, and a dashboard (33 KB rendered) 19.2 ms and 10.1, which is 24 to 52 percent less. On Python 3.14 the same screens took 1.99, 38.2, 11.2 and 21.4 ms and take 1.28, 29.3, 5.79 and 12.2, which is 23 to 48 percent less. Another workload can gain more or less. - Compile each template once while it is unchanged. The template loader now keeps
the Django templates it compiled and reuses one when the resolver returns the same
template again, so a repeated render no longer validates the source and runs
Django's compiler for every template it touches. Measured on Python 3.12 with a
HyperTodo-shaped workload, a full response render takes 8 to 30 percent less: 1.58
to 1.27 ms for a navigation fragment (1 KB rendered), 7.4 to 5.2 ms for a settings
screen (20 KB), 12.8 to 10.7 ms for a dashboard (33 KB) and 27.6 to 25.3 ms for a
list of 40 rows (73 KB), where rendering dominates. The resolver still runs on
every render, so invalidation, cache generations and the per-render revision
snapshot behave as before. A stored template is reused only when the new
resolution matches it in every field, including the source text, so a custom
source whose
revisiondoes not follow its content never serves stale code, and the template keeps the origin it would have been compiled with. The source validation policy is part of the key, andHYPERVIEW,TEMPLATESandDEBUGchanges rebuild the default engine, which starts with an empty cache. Validation and compile errors are not stored. The memory is bounded for each engine at 256 templates and 524,288 characters of source, with the least recently used template leaving first, and a template that is published again replaces its previous version instead of adding to it; the configuration guide estimates the memory a compiled template takes. A subclass ofResolverLoaderthat overridesget_contents()orget_template_sources()still compiles on every render, and the newResolverLoader.reset()empties the cache, as Django's loader contract expects. Compiled templates are now shared between threads, as in Django's cached loader, so a custom template tag must keep no per-render state on its nodes; the tags and filters of the package keep none. A deep copy of aResolverLoader, of aHyperviewEngineor of a compiled template starts with an empty cache of its own and compiles its templates again. - Validate rendered documents faster. The compiled registry that validates every
rendered document and fragment is now built with a small
xmlschema.XMLSchema11subclass that answers three questions once instead of for every element: which attributes of a group are required or carry a value constraint, which type a registeredSCHEMA_EXTENSIONS["BEHAVIORS"]action selects, and whether an attribute value was already accepted. An element looks its type up by the value ofactionin a dictionary only when every alternative that has a test has the form the package generates,@action = '<name>', and no attribute inherited from an ancestor applies; any other test goes through XPath, as before. The memory of accepted values keeps at most 128 values of at most 64 characters per attribute declaration, about 7 MiB per registry in the worst case, and only for attribute types whose result depends on nothing but the value. A type with an XSD 1.1 assertion facet is never remembered, because an XPath assertion can read more than the value, ascurrent-dateTime()does: a type that declares an assertion, inherits one from a base type, or is a list whose item type, or a union with a member type, has one is checked in full every time. None of the simple types of the bundled Hyperview schemas has an assertion facet, so this rule does not reduce the speedup below for them. Attributes of typexs:ID,xs:IDREF,xs:IDREFS,xs:ENTITY,xs:ENTITIES,xs:QNameorxs:NOTATION, or of a type derived from one of them, attributes with afixedordefaultvalue, and plainxs:string,xs:tokenandxs:normalizedStringare left alone, and a value that fails is never remembered. Hyperview's ownhv:IDis a restriction ofxs:token, so its values are remembered. The accept or reject outcome, the validation code, the message and the line are meant to stay those of a plainXMLSchema11registry. A differential test compares the two over a seeded corpus of valid and mutated documents, for the Hyperview schema, for registered behaviors and for an extra schema that includes a type with an assertion facet, and further tests move a controlled clock past the cutoff of a time-dependent assertion. Those tests and the reasoning above show the equivalence for the cases they cover; they do not prove it for every schema thatEXTRA_SCHEMAScan load. In a benchmark that renders four HyperTodo-shaped screens throughHyperviewTemplateResponseon Python 3.12 with xmlschema 4.3.2, a full response took 12 to 38 percent less time than with a plain registry: 12 percent for a navigation fragment (1 KB rendered), 16 percent for a list of 40 rows (73 KB), and 36 and 38 percent for a settings screen (20 KB) and a dashboard (33 KB). Another document can gain more or less. The subclass relies on xmlschema internals that are not documented API. It is enabled only for the exact verified version string4.3.2; even a later 4.3 patch, pre-release or modified version string gets plainxmlschema.XMLSchema11, which still performs full validation at the previous speed. Another release must pass explicit validation before the allowlist can expand. Extra schemas compiled only to check for conflicts keep the plain class. - Check the extra schemas once for each response instead of twice. A response
guards every
EXTRA_SCHEMASfile when it validates, which parses each file and its includes again, and it guarded them a second time when it committed the validated result. A response now commits the result its own validation just produced after comparing only the settings snapshot, the limits and the root mode that result was validated under, so the guard runs once. A result that has waited is still checked completely: the body thatGZipMiddlewarecompresses after the response was rendered meets the current registry and limits, and so does a result whose settings changed before it was committed, and either one is validated again when the contract differs. Validation itself is unchanged and still guards the extra schemas every time: a schema file that was rewritten so that it is unsafe, with a DOCTYPE, anxs:override, a reference outside its directory or malformed XML, is rejected by the next validation. A rewrite that leaves the file safe and keeps its size and modification time is not compiled again, because the compiled registry is selected by path, size and modification time, as in 0.1.0b3. On Python 3.12 with one extra schema file of 5 KB, a full response took 0.1 to 0.3 ms less: 1.30 to 1.14 ms for a navigation fragment (1 KB rendered), 5.4 to 5.2 ms for a settings screen (20 KB) and 10.8 to 10.5 ms for a dashboard (33 KB), with no measurable change for a list of 40 rows (73 KB). The saving grows with the number and size of the extra schemas, and there is none withoutEXTRA_SCHEMAS. - Validate rendered documents faster with a flatter schema. Upstream defines the
children of
body,header,view,form,select-singleandselect-multiple(viewChildren) and ofitemandsection-title(nonListViewChildren) as a choice inside a group inside a sequence inside another choice, and xmlschema walks every level and tries the alternatives of a choice in order. The bundled revisionr4replaces both groups with one flat choice of the same elements and the same##otherwildcard, with the elements that rendered documents contain most often first:view,text,behavior,image,text-field,formandspinner. The documents that are accepted do not change: the children, the wildcard and the repetition are the same, and the codes, messages and lines of the errors, the editor catalog and the Admin static catalog are those ofr3, whosecatalog.jsonr4keeps byte for byte. A test compares the compiled flat groups with the upstream groups for each of the eight containers (child names, minimum and maximum occurrences, the namespace constraint andprocessContentsof the wildcard, the kind of model and its depth), and a differential run over 112,249 documents (the test suites of this package and of HyperTodo, rendered templates, generated content-model cases and seeded mutants) on the default registry and on one with an extra schema found no difference in verdict, code, message or line. They show the equivalence for the cases they cover; they do not prove it for every schema thatEXTRA_SCHEMAScan load. A project schema that useshv:viewChildrenorhv:nonListViewChildrenin its own types gets the flat group. Measured asHyperviewTemplateResponse.render()of a HyperTodo-shaped workload with warm caches and no database, not as the latency of a full HTTP request, a render took about 14 to 25 percent less than before this change on Python 3.12 and 15 to 30 percent less on Python 3.14 (medians of the paired ratios of interleaved runs, over two independent measurements). In one of those runs, on Python 3.12, a navigation fragment (1 KB rendered) took 1.13 ms and takes 0.87, a list of 40 rows (73 KB) 25.3 ms and 21.2, a settings screen (20 KB) 5.11 ms and 3.95, and a dashboard (33 KB) 10.3 ms and 7.83. Between a third and a half of the gain comes from the flat shape and the rest from the order, so a document with another mix of elements can gain more or less. Building a registry takes up to about 7 ms longer, once for each registry in a process, because the schema document is 49 lines longer and is checked when it is compiled, and a compiled registry keeps about 26 KiB more. - The bundled corrected schema revision is now
r4, and ther3files are no longer shipped, as those ofr2were not before. A project that kept a copy of ther3files, for example as a vendored reference or in a check that pins their hash, should drop it or replace it with ther4files. The package never loads an external copy of ther3files by itself. It reads one only if a file listed inEXTRA_SCHEMASis that copy or imports or includes it, and then rejects it withforbidden_schema_reference(dj_hyperview.E012), because those files containxs:override, as it did before. Such a project should drop that entry or import.HYPERVIEW_SCHEMA_VERSIONandHYPERVIEW_VALIDATION_CONTRACTdo not change. - Check an Admin draft in 39 to 51 percent less time, with the same diagnostics. The
static check behind Format and Validate and the save preflight (
ADMIN.EDITOR) copied the 82 KB schema catalog each time it ran, which is once for the draft, once for each include it expands and once for the composed result, and it asked the attribute type whether each value was valid by building a validation context for every attribute. It now reads the shared catalog, which is made of read-only mappings and tuples so no check can change what the next one reads, and it asks a type about a value once for each check, because the answer depends on the type and the value alone; nothing is remembered between checks, and a value that carries a template token is still not asked. The codes, messages, lines and order of the diagnostics do not change, and a differential test pins them for drafts that repeat invalid values on several elements. On Python 3.12 with xmlschema 4.3.2,validate_draft_source()took 2.4 ms instead of 4.9 ms for a navigation fragment (2 KB of source), 23.9 instead of 42.2 for a list of 40 rows (9 KB), 10.0 instead of 16.3 for a settings screen (12 KB) and 33.2 instead of 55.3 for a dashboard (25 KB). - Keep the compiled schema when a test changes the
HYPERVIEWsetting. A change to the setting used to empty the compiled registry and both catalogs. A test that enteredoverride_settings(HYPERVIEW=...)therefore compiled the schema again on its first validation, about 110 ms with an extra schema, and once more when it left the override, however small the change. The caches now keep their entries: the same value reuses the compiled registry, a differentEXTRA_SCHEMASorSCHEMA_EXTENSIONSvalue selects its own, an extra schema file that changed on disk is still guarded on every call and compiled again when its size or modification time differs, and a limit underVALIDATIONis read from the current settings as before. Entering and leaving an override with the same value and validating once on each side took 216 ms and takes 15 ms, which is the rebuild of the settings snapshot and its system check. The base suite of this package ran in 57 s and runs in 43 s, and its Admin profile in 22 s and 6 s. Production does not change: nothing there setsHYPERVIEWat run time. Each cache keeps at most 64 entries and a compiled registry takes about 1.3 MB, so a suite that validates against many different extra schemas holds up to 64 of them, which raised the peak memory of this package's own suite from 176 MB to 301 MB. A test that counts compilations must start from an empty cache, which the package's own tests do with a fixture.
0.1.0b3 — 2026-10-14¶
Security¶
- Reject an element with more than 128 attributes, more than 8 in-scope namespaces,
or more than 32 namespace declarations on the path from the root to it, before XSD
validation, using the new stable validation code
max_attributes. Validation time grew faster than linearly with these counts, so a few kilobytes of untrusted markup could occupy a worker for seconds. The schema validator rebuilds an element's namespace map from all of its ancestors several times, and once more for each in-scope prefix when the element declares a namespace, so the cost grows with the declarations on a path times the prefixes in scope. The path cap counts every declaration, including a prefix rebound at each level. Looser caps would not do: at 64 prefixes and 256 declarations, 20,000 elements that each rebind a prefix below a path at the cap take about 28 s at the default limits, against about 1.1 s with these caps and about 0.6 s for 20,000 plain elements. The widest element the bundled schema declares has 126 attributes. The HyperTodo templates, with their includes expanded, use at most 13 attributes on an element, 5 namespace declarations on a path and 2 prefixes in scope, and sibling elements do not add up. Valid Hyperview markup has no reason to exceed a cap, and the caps are fixed, so a document that does must be restructured. Checking them takes time proportional to the number of nodes and declarations. They do not bound the whole document:VALIDATION.MAX_BYTES,MAX_NODESandMAX_DEPTHremain the global limits, a large valid document still costs one to two seconds of CPU on a fast development machine, and untrusted input should lower them. - Schema validation now stops at the first violation. xmlschema's default (lax)
mode builds an error for every violation in the whole tree before it reports the
first, and an undeclared attribute costs time proportional to the number of
attributes on its element. Rejecting 300 elements with 256 undeclared attributes
each therefore took about 7.7 s, where a valid document of the same size takes
under a second. The reported violation is the first one xmlschema finds, with the
same message and line, which is not always the first in document order: it reports
a parent's content-model error, such as a missing required child, after the errors
inside its children. One classification changed. A document that has a violation
and, after it, an
xsi:typenaming no declared type used to be rejected asschema_invalid, because the lax mode failed on the type before it returned anything; it is now rejected with the earlierschemaviolation. A document whose only problem is such anxsi:typeis stillschema_invalid. Validating a valid document costs the same as before. - Stop echoing rejected template names from an ordered selection. When every
candidate misses,
TemplateNotFoundnow lists only valid names that were not found, so control characters and escape sequences in hostile names cannot reach logs. - The Admin Format and Validate check, which the Admin form repeats before every
save when
ADMIN.EDITORis on, now takes time proportional to the size of the draft. It used to lex the whole draft again for every Django token, and to rescan the rest of the input for every{{,{%,<?xmlor<!--that had no closer. An authorized user could therefore occupy a worker, and hold the row lock of a save, for tens of seconds: 4,000 tokens took 25 s,"{{\n" * 40000(120 KB) 30 s,"<?xml " * 40000(240 KB) 32 s, and"{{ a }}" + "<!--" * 40000(160 KB) 49 s. They now take well under a second. The check also compiles the draft with a lexer that finds the same tokens as Django's in linear time: Django's lazy pattern reads on to the end of the line for every{{,{%or{#without a closer, so 40,000 of them on one line (120 KB) took 10 s in its lexer alone. Literal{% include %}targets are now read from their source and expanded with that lexer. Before, each target was also compiled by Django'sTemplate, so a draft with one include of 40,000{{took 10 s and four such includes took 42 s; they now take 0.3 s. The composed source is bounded as well: expansion stops, with the informational noteschema_static_incomplete, once the draft and its includes together exceedVALIDATION.MAX_BYTES, counted in UTF-8 bytes as the limit is everywhere else. A draft with 64 includes of a 990 KB template used 3.3 GB and now uses 79 MB. - Bound the memory of the Admin Format and Validate check. The check replaces
every Django tag and variable with a marker, and it lengthened the marker with
underscores until the draft could not contain it, so a draft chose the length:
DJHVSTATICDYNAMIC, a long run of underscores, then{{a}}repeated made a 16 MB masked copy from 18 KB (47 MiB at peak), about 1 GiB from 144 KB, and would have needed about 47 GiB from 1 MB. An authorized user could therefore exhaust the memory of a worker. That is a user who may use the editor, a superuser unlessADMIN.PERMISSIONallows more. The marker is now numbered, such asDJHVSTATICDYNAMIC0, one above the highest number that follows its name in the draft, so it stays under 30 characters whatever the draft holds and still never occurs in it. The number is also chosen after the numeric character references of the draft are resolved, as the parser resolves them:DJHVSTATICDYNAMIC0is the marker text once parsed, and a draft that spelled its value that way used to pass the check although serving it was rejected. A draft that already holds the number 99999999 after a marker name, in either form, which would leave no eight-digit number above it, is skipped with the noteschema_static_incomplete. The same 1 MB draft now uses 53 MiB at peak. The copy is also bounded in itself: it is the draft plus one marker for each tag or variable that renders output, not for text, comments,{% load %}or{% verbatim %}. When it would exceed four timesVALIDATION.MAX_BYTES, counted in UTF-8 bytes like the limit itself, which only a draft made almost entirely of tags shorter than a marker reaches, the check is skipped for that draft with the informational noteschema_static_incomplete. The 18 KB draft above was also reported as invalid XML, because the parser refused a 16 MB text node, and is now checked. - Stop the Admin Format and Validate check from showing its internal marker. A
Django tag or variable inside an element or attribute name, such as
<a{{ x }} />, was reported asElement "aDJHVSTATICSTRUCTURE0" is not declared...; the message now readsElement "a{...}" is not declared.... - Limit the work that unclosed template delimiters cost when a stored template is
compiled, using the new stable validation code
max_unclosed. Django's lexer reads on to the end of the line for every{{,{%or{#that closes no tag, so a stored template of"<view>" + "{{ " * 40000(120 KB, far belowVALIDATION.MAX_BYTES) passed validation and then occupied a request worker for 10.5 s every time Django compiled it. Publication, model validation, the Admin check (including the targets of literal includes) and the template loader now count those characters in linear time, as the sum over every opener that closes no tag of the characters up to the end of its line, and reject a source above 10 million. The count tracks what Django's lexer reads, so the worst accepted source costs it about a tenth more than the same source without the stray openers, measured on 1,000,000 bytes of ordinary tags. The limit is fixed and does not grow withMAX_BYTES. The loader applies it before it hands any source to Django, so a stored row that predates the limit, or that was written withbulk_create()orsave(), fails when it is served instead of occupying a worker. Filesystem templates are developer-owned and no user can write them; they pass through the same loader check, which only a deliberately pathological file reaches. See Security. sse_responsenow sendsCache-Control: no-store, no-transforminstead ofno-cache, no-transform. The stream is per authenticated user, andno-cachestill lets a shared cache store the response and revalidate it later.
Breaking changes¶
Each entry changes behavior that existing code, configuration or templates may rely on, and says how to migrate.
- Custom template sources are no longer cached unless they declare
cacheable = True. Before, the resolver cached every source that lacked a private opt-out marker, keyed only by the source class and its constructor options. A source whose content depends on ambient state, such as aContextVarthat holds the current tenant, therefore served one caller's content to another onceHYPERVIEW["CACHE"]was enabled.FileSystemSourceandDatabaseSourcewith an explicitusingalias keep their caching, but only whileresolve()is their own: a subclass that overridesresolve(), for example to select a tenant's directory, is no longer cached until it setscacheable = True. To keep caching a custom source, setcacheable = Trueon it, but only when its output depends solely on the template name and the constructor options; see Cache consistency. The private_dj_hyperview_cacheablemarker is removed and no longer has any effect. publish_template()no longer re-activates an inactive template. Itsactiveargument now defaults toNone, which keeps an existing template's value and publishes a new template as active; before, the defaultTrueoverwrote the stored value. A caller that relied on republishing to activate a template must now passactive=True.- The resolver now checks what a source returns, with or
without a cache. A
ResolvedTemplatemust be named exactly as requested and all of its fields must bestr; otherwise the resolver raisesSourceUnavailable(reasoninvalid resolved template) in both failure modes. Before, such a result was written to the cache and then rejected on every read:raisemode failed on the second lookup withinvalid payload,bypassmode never cached it, and a field that is not JSON serializable raisedTypeErroreven inbypassmode. The built-in sources already comply. A custom source that answered to another name, or left one of the five fields asNone, bytes or an integer, must be fixed. A subclass ofResolvedTemplatemay still add fields of its own: they are not checked, an uncached lookup returns the subclass instance, and the cache stores, and later returns, only the five contract fields as a plainResolvedTemplate. - The system check now compiles the merged
EXTRA_SCHEMASregistry. It compiled each extra schema alone, so two schemas that declare the same element differently, or an extra schema that declares a Hyperview or alert name differently from the bundled schemas, passed it. The conflict then surfaced asduplicate_schema_declarationfrom every validation, which recompiled the registry each time (70 to 90 ms, never cached). After every file passes on its own, the check compiles the registry the first request would use, once, and reports a failure asdj_hyperview.E012against the whole setting, withreason: duplicate_schema_declaration, orreason: schema_invalidfor any other failure of the compiler, including one it does not translate. A registry that compiles cleanly is cached, so the first request does not compile it again, and a project withoutEXTRA_SCHEMASpays nothing. The merge is skipped whileSCHEMA_EXTENSIONShas errors (dj_hyperview.E021). A project whose extra schemas conflict now fails system checks, and every call that reads the settings raisesHyperviewConfigurationError, a subclass ofHyperviewErrorandImproperlyConfiguredthat is neither aTemplateValidationErrornor aValueError. These returned normally before:get_settings(),validate_template_source,HyperviewEngine(),resolve_template,TemplateResolver.from_settings(),TemplateCache.from_settings(),FileSystemSource(), andinvalidate_templates()whenCACHEis set. These raisedTemplateValidationErrorbefore:validate_hxml,validate_fragment_hxml,validate_hyperview_schema, the response classes,HyperviewTemplateView,render_templateonce it validates a rendered document, andget_hyperview_catalog. Code that handlesTemplateValidationErroraround them must handleHyperviewConfigurationErroras well. A schema file that starts to conflict after the settings were first read still raisesTemplateValidationErrorfrom the call that notices it. - Unknown
HYPERVIEWkeys are now configuration errors (dj_hyperview.E023) instead of being ignored. The rule covers the top level,SOURCESentries,CACHE,VALIDATIONandADMIN, so a typo such asMAX_BYTE,FAILURE_MODESorPERMISIONcan no longer silently fall back to a default. A project with a stray key now fails system checks andget_settings(); correct or remove the key. The removedSCHEMA_PROFILE,VALIDATION.MODEandVALIDATION.SCHEMAkeep their existing errors and are not reported twice. - Reject format characters, and line and paragraph separators, in template names.
The rule is a denylist of Unicode categories: Cf (format characters, such as
bidirectional overrides and isolates, zero-width characters, the soft hyphen, the
byte order mark, and tag characters), Zl and Zp (the line and paragraph
separators U+2028 and U+2029) now raise
InvalidTemplateName, like Cc (control characters) and Cs (surrogates) already did. It is not a general test for invisible or confusable names: space separators (Zs, such as U+00A0 or U+3000) and letters or symbols that merely look blank, such as U+3164 HANGUL FILLER or U+2800 BRAILLE PATTERN BLANK, are still accepted. The rejected characters could make two names look identical, or hide a file extension, in logs and error pages. The rule is shared by the resolver, the engine, the cache and signal helpers, and the database source, model validator and Admin editor. A template whose name uses one of these characters, for example a zero-width joiner in an emoji sequence, must be renamed.check_hyperview_templatesreports a stored row with such a name asinvalid_name, and the row is no longer resolved. - Reject any
..segment, any non-printable character, and the characters?,#and:in a schema reference of anEXTRA_SCHEMASfile with the stableforbidden_schema_referencecode (dj_hyperview.E012during configuration checks), and read the references of a symlinked schema file from the symlink's own directory. The dependency guard resolved symlinks physically while the XSD compiler folded..lexically, so a reference such aslink/../../outside.xsdthrough a symlinked directory passed the guard and made the compiler load a file outside the configured root. The compiler's URL parser also drops tabs and carriage returns, so.<TAB>.passed as an ordinary directory name while the compiler read... It drops an empty?or#and reads a first segment such as1:.as a drive, sox.xsd?named a benign decoy filex.xsd?for the guard and the filex.xsd, a symlink to a schema outside the root, for the compiler. And the guard joined the references of a schema file that is a symlink onto the directory of its target, where the compiler joins them onto the directory of the link, soout/evil.xsdcould name a decoy for the guard and a file outside the root for the compiler. AschemaLocationthat starts with<, which the XSD compiler reads as inline XML rather than as a path, is rejected by the:rule, because any inline schema declares a namespace; it was reported asschema_invalid. A schema that relied on..inside its own root, for examplecomponents/../shared.xsd, must now use the plain pathshared.xsd. A schema file whose name contains?,#or:, such asa#b.xsd, must be renamed. A schema file that is a symlink now finds its dependencies beside the link, as the compiler does, not beside its target. - The Admin Format and Validate check now applies the limits that rendering
applies before any schema work. It rejects a template whose source has more than
VALIDATION.MAX_NODESnodes orMAX_DEPTHlevels, an element with more than 128 attributes or 8 in-scope namespaces, or a path with more than 32 namespace declarations, using the stable codesmax_nodes,max_depthandmax_attributes, and it returns at most 50 diagnostics, errors first, followed by an informationaldiagnostics_truncatedentry when more were found. Before, 1,008 namespace declarations on one path took 10 s, and 80,000 undeclared elements produced 80,000 diagnostics and a 16 MB response. The source of a template counts every branch of a conditional, so a template whose branches together exceed a limit now fails the check although each rendering would pass. Raise the limit if you need such a template. select_template()called with a bare string now raisesTypeError, as Django does, instead of iterating its characters as template names. Pass a list or a tuple of names, or useget_template()for one name.- The Admin schema catalog endpoint (
hxml-catalog/) now answers 404 unlessHYPERVIEW["ADMIN"]["EDITOR"]is enabled, as the validation endpoint already did. Before, it served the catalog, including the names from the project'sEXTRA_SCHEMASandSCHEMA_EXTENSIONS, to every staff user with view permission while the editor was off. An integration that fetched the catalog with the editor off must enable the editor. X-Hyperview-Versionis treated as a claimed value. A request still counts as Hyperview whenever the header is present, butHyperviewRequestDetails.versionis nowNoneunless the value matches[0-9A-Za-z][0-9A-Za-z.+_-]{0,63}. It used to return any string verbatim, including empty or oversized values. Code that readsversionmust acceptNone.- Reaching the limit of 64 expanded literal includes is now the informational note
schema_static_incompleteand no longer the errordjango_include_limit, which blocked the save of a template that was only too large to check completely. Expansion stops at the limit, and the codedjango_include_limitis no longer produced. Tooling that matcheddjango_include_limitmust expectschema_static_incomplete, with severityinfo. - The
charsetargument of a response, and thecharsetparameter of itsContent-Type, now follow the rule that decides the encoding name of an XML declaration (see Fixed): the name is read from the alias table of Python'sencodingspackage and never looked up incodecs. A spelling that only the lookup's own normalization accepted, such asutf 8or a trailing space, is now rejected with the usualValueError("Hyperview responses require UTF-8"). So is a name that contains a NUL or a lone surrogate, which used to let the lookup's ownValueErrorescape with another message. Pass a standard spelling such asutf-8. - Template source is now rejected with the stable code
max_unclosedwhen the unclosed{{,{%and{#delimiters on its lines make Django's lexer read more than 10 million characters (see Security). Such a source was accepted before, and every compile of it took seconds. Only a very long line of stray openers reaches the limit: about 2,600{{in a row (8 KB on one line) do, and a template with line breaks, or one whose delimiters close, never does. A stored or filesystem template that reaches it must close the delimiters, spell them with{% templatetag openvariable %}and its siblings, or break the line; a stored row that reaches it fails when it is served. - The database template admin no longer strips
content: leading and trailing whitespace, including a final newline, is now stored exactly as submitted. Django's form field stripped it, so every admin save silently rewrote a template that ended in a newline. With the editor off, content that is only whitespace is therefore saved; the empty string is still rejected as required, and with the editor on the source check still rejects whitespace-only content. An integration that relied on the form trimmingcontent, or on it rejecting whitespace-only content with the editor off, must do that itself. - A Hyperview
Content-Typeparameter whose value needs quoting, such asprofile="mobile app", is now written back quoted and escaped. Before, the rebuilt header lost the quotes (profile=mobile app), which is not a valid parameter. A parameter name that is not an RFC 9110 token or ends in*(an RFC 8187 extended name such asa*=x), or a value with a control character, a character above U+00FF, or a trailing backslash, now raisesValueErrorinstead of producing a malformed header. Django andemailread the quote after a trailing backslash as escaped, so such a value would come back changed and swallow the next parameter, and Django drops the star of an extended name and decodes its value, soa*=xwould come back asa=x. The check splits the header the way Django does, so a stray quote cannot hide a star from it. A header that Django cannot parse, such as an extended value with an unknown encoding (which Django 5.2 reports asLookupError), raises the sameValueErroron both supported Django versions, and so does aContent-Typelonger than 10,000 characters, which Django 6.1 already refused and Django 5.2 read without a cap (1 MB of semicolons took about 15 s). A caller that passes such a parameter must drop it or give it a value that fits: printable Latin-1 characters, and no backslash at the end. - Source that nests blocks more than 100 levels deep, such as
{% if %}, no longer fails the Admin validation request and the Admin save with HTTP 500, and can no longer be saved only to fail withRecursionErrorwhen it is served. Django's parser compiles about 490 nested{% if %}blocks, but rendering recurses three frames for each and fails at about 330 in an empty stack and at about 230 below 300 frames of caller, where a view in a plain Django request was measured 8 to 24 frames deep, so the earlier check accepted source that could not render. The check, the Admin form,publish_templateand model validation report it astoo_deep, "Template is nested too deeply." The levels are counted while the source is parsed, so an empty innermost block counts, and a filter in an opening tag, such as{% if a|upper %}, no longer turns the failure at about 490 levels into aTypeErrorand HTTP 500. For a literal include target the Admin reportstoo_deepfor the target. The limit applies to each template on its own, so chained includes can still nest more deeply than can be rendered. A stored template that nests blocks more than 100 levels deep is now rejected when it is saved; flatten it. - The source check for forbidden declarations now agrees with Django about three
constructs. A
{% comment %}inside a{% verbatim %}body hides nothing, so a declaration there is rejected when the template is saved, as it was already when it was served. A comment ends at the first tag named exactlyendcomment, so a declaration after{% endcomment note %}is hidden and the template is accepted, as it renders. A tag whose delimiters are split over a line break, such as{%, a newline andcomment %}, is plain text in Django and now hides nothing either. A template that hid a declaration in a{% comment %}inside a{% verbatim %}body, or after a comment tag split over a line break, is now rejected when it is saved; remove the declaration.
Added¶
- A public, optional
cacheableattribute on template sources. It defaults toFalse;FileSystemSource.cacheableisTrueandDatabaseSource.cacheableisTrueonly with an explicitusingalias, each only while the instance uses the built-inresolve(). Both can be assigned, for exampleself.cacheable = Falsein a subclass constructor, or overridden by a class attribute. On an instance they behave like a class attribute: deleting an assignment, or patching withunittest.mock.patch.object, restores the default. TheTemplateSourceprotocol still requires onlyresolve(). dj_hyperview.validators.validate_xml_text, a Django validator (error codexml_characters) that rejects characters XML 1.0 cannot represent.hv_textandhv_attrtemplate filters that remove those characters and escape element text or attribute values.hv_attralso encodes line feeds, carriage returns and tabs so they survive XML attribute-value normalization.CACHE.FAILURE_MODE = "bypass"is no longer silent. Each cache failure it swallows logs aWARNINGon the newdj_hyperview.cachelogger, such astemplate_cache_bypassed operation=read reason=invalid payload, with fixed codes only: never a template name, cache key, source identity or backend message. A source that declarescacheable = Truebut whose constructor options the cache cannot identify is logged once atINFOastemplate_source_uncacheable. A working cache logs nothing. Before, a backend that failed at runtime, or a payload that could not be read back, left no trace; a wrong alias or an unsupported backend was already a system-check error (dj_hyperview.E004,E018).
Changed¶
ADMIN.PERMISSIONis called at most once per request. Every add, change and delete check of one request now shares the first result, which is also kept when the callback raises, so the failure is logged once. A page used to call the callback eight times on the changelist. The next request calls it again. Only aboolcounts as the shared result, so a request object that invents attributes, such as a mock, still runs the callback.- The database admin changelist no longer loads the template bodies; the change and delete pages still do.
- The after-commit realtime publisher no longer builds a Redis client for every
hint. The worker keeps one connection per configuration, replaces it after a
failure, after 30 seconds without use, or after five minutes of age under steady
traffic so that the host name is resolved again, and still sends the channels of
a hint in one pipeline round trip. It checks the connection that a hint uses
against those limits, and sweeps every connection at most every 30 seconds,
whether hints arrive or not, so a configuration that is never used again does not
keep its connection until the process ends. The sweep also forgets a
configuration, with the failures counted for it, once nothing was attempted on it
for 30 seconds and its breaker is not open, so one that only ever failed does not
stay in memory, and a failure that old stops counting toward the breaker. A short
circuit breaker also stops each commit from waiting for the connection timeout
while Redis is down: after three consecutive failures of a configuration, hints
for it are dropped immediately for five seconds, and the next hint probes the
server. A client that cannot be built counts as a failure. The breaker logs the
fixed code
realtime_publish_circuit_openonce when it opens, not once per dropped hint. Publication stays best-effort and at-most-once, and a failure still never fails the committing request. The log carries no payload, topic or URL. If the client raises aBaseException, such as a geventTimeout, which ends the worker thread, the next commit starts a new worker instead of waiting for its full timeout, and the ended worker releases the commits queued behind it, which would otherwise wait out their own timeouts, before it closes its clients, so a client that fails to close cannot hold them, and a client that raises aBaseExceptionon close does not leave the others open. - A failed realtime publication now logs the exception type name as well:
realtime_publish_failed error_type=<TypeName>. The exception message is still never logged because it can contain the Redis URL. - The new system check
dj_hyperview.W007warns whenHYPERVIEW["REALTIME"]is configured and redis-py is not installed, with the hintpip install "dj-hyperview[realtime]". Before, a missing extra passed the checks and only showed up at runtime as a fixedrealtime_publish_failedlog line. The check looks the package up without importing it. SOURCES[].OPTIONSis bound to the backend constructor during system checks. Options the constructor does not accept, or required arguments it does not receive, producedj_hyperview.E024. Before, such a project passed the checks and raisedTypeErroron every request. On Python 3.14, where annotations are evaluated lazily, the check reads the constructor signature without resolving annotations; a name imported only underTYPE_CHECKINGwould otherwise raiseNameErrorand silently disable the check for that backend.requires-pythonis now>=3.12with no upper bound. The previous<3.15cap stopped installers on Python 3.15 from installing the package at all. Python 3.12, 3.13 and 3.14 remain the tested set, declared by the classifiers and the CI matrix; newer versions are not blocked by the package metadata but are not tested yet.uv.lockwas regenerated and now also records Python 3.15 wheels.dj_hyperview.E012now ends with the cause, such as(reason: schema_invalid), anddj_hyperview.E003anddj_hyperview.E017add(import raised ModuleNotFoundError)when the dotted path could not be imported. Before, a missing file, an invalid schema, a forbidden reference and a DTD all produced the same E012 message. The cause is a stable code or an exception class name; file paths and exception messages are never included.- A rendered document that contains a character XML 1.0 cannot represent now fails
with
malformed_xmland the message "document contains characters not allowed in XML 1.0" instead of the generic "invalid XML". Validation still fails closed and the message still does not echo document content. This includes a lone surrogate passed toHyperviewResponse,HyperviewFragmentResponseorwrite(). A document that also exceedsMAX_BYTES, declares a DTD or entity, or declares a non-UTF-8 encoding keeps reportingmax_bytes,forbidden_declarationorinvalid_encoding, with one exception: a lone surrogate cannot be encoded, so the validators reportmalformed_xmlfor it even when the document also exceedsMAX_BYTESor declares a DTD or entity. A non-UTF-8 declaration is still reported first. - Render validation now removes only XML whitespace (space, tab, carriage return
and line feed) before the document, after an optional BOM. Before, it also
dropped other whitespace and control characters silently, such as U+00A0,
U+0085 or U+001C. A document that starts with one of them now fails with
malformed_xml.
Fixed¶
- The editor's validation and completion URLs are now resolved on the
AdminSitethat serves the form. Before, they were always looked up in the defaultadminnamespace, so a model registered on a custom site silently lost its validation and completion. content_type=NoneonHyperviewResponse,HyperviewTemplateResponseand the fragment classes now yields the Hyperview media type of the class, as the default argument does, whenheadershas noContent-Type. Before, it fell through to Django's defaulttext/html. Whenheaderscarries aContent-Type(in any letter case),Nonekeeps that header, as it does in Django. Omitting the argument with such a header still raises Django'sValueErrorabout giving the type twice, because the default is a content type too.- A
Content-Encodingother thangziporidentitynow fails at iteration and serialization with the message "only gzip Content-Encoding is supported". Before, it reported only that the header does not match the transport, which hid that the encoding itself is unsupported. The error codehttp_encodingis unchanged. dj_hyperview.loaders.ResolverLoadercan be named inTEMPLATESOPTIONS["loaders"]. Itsresolverandvalidationarguments now default to the resolver and validation limits built fromHYPERVIEW; before, Django's construction of the loader from its dotted path raisedTypeError.- The engine behind
render_templateand the template responses is rebuilt whenDEBUGchanges, as Django rebuilds its own engines, instead of keeping the debug option it first saw. - A
TEMPLATESentry without aBACKEND, or withBACKENDset toNone, no longer makesHyperviewEnginefail withAttributeError. The engine skips it, like an entry whose backend cannot be imported, and reads its options from the first Django backend. - Admin Format and Validate now treats a Django delimiter the way Django's lexer
does. A
{% comment %}block is ignored whatever follows its name, as in{% comment "temporarily disabled" %}: the check looked for{% endcomment "temporarily disabled" %}, which Django never needs, so the commented-out markup was validated as live and could block a save of a template that renders. A{# ... #},{{ ... }}or{% ... %}that spans a line break is literal text, as it is when the template is served, so the check now reports what that text does to the document. A{# note #}comment with a line break inside it between two elements used to pass the check, and the rendered document then failed schema validation. The tokens are the ones Django's lexer produces, and the check classifies all of them in a single pass instead of lexing the draft again for each one. - A variable that applies the
safefilter in element text, such as{{ fragment|safe }}in an element that allows only elements, no longer fails the Admin check. It may render markup, so its position is now reported with the informational note, like other runtime-dependent structure. Variables withoutsafestill count as text, as before. - The Admin check no longer counts the namespace declaration and prefix of its own synthetic root toward the namespace limits. A document with exactly 32 declarations on a path or 8 in-scope namespaces passed at render time and failed in the Admin.
- A
{% verbatim %}body that supplies markup, such as the required child of an element or half of a tag, now gets the informational note in the Admin check. It used to be reported as the blockingschema_structureorxml_syntaxerror, although the template renders. - Publication and model validation compile stored source with the same linear lexer
as the Admin check, so a long line of unclosed
{{is checked in linear time instead of the 10 s that Django's lexer takes for 40,000 of them (120 KB). How much such a line may cost when the template is served has its own limit; see Security. - With unusable
HYPERVIEWsettings, including conflicting extra schemas, the Admin no longer fails with a server error on the change page. The change page of an existing template renders read-only with a plain text area instead of the editor; adding, changing and deleting are denied (HTTP 403), as for any invalid setting; and the HXML completion endpoint answers with the sameconfiguration_errorJSON diagnostic as the validation endpoint, instead of an unhandled exception. A page logs oneWARNINGwithout a traceback that points topython manage.py check; only a permission callback that raises is logged as anERRORthat namesADMIN.PERMISSION. - A
SystemExit,KeyboardInterruptor other non-Exceptionraised whileRedisBroker.subscribeopens a subscription is no longer converted intoRealtimeUnavailable. The owned clients are still closed first, then the original exception propagates, as cancellation already did. - Saving or deleting a template through a proxy model of
HyperviewTemplatenow schedules cache invalidation. Django sends the model signals of a proxy instance with the proxy class as sender, so the handlers connected forHyperviewTemplatealone never ran, and a cached template stayed stale after such a write. The handlers are now connected to the model and to each of its subclasses, including a proxy defined after startup, and to no other model, so Django keeps its fast bulk delete for the rest of the project. A rename or delete also reads the stored name through the base manager, so a proxy whose default manager hides the row still invalidates the old name. - Admin Format and Validate now formats a template that contains
{% load %}. The tag renders nothing, but a top-level one made the formatter keep the whole source and report "HXML was already formatted." The package's own{% hv_csrf_token %}tag is also accepted; before, a template using it could not be formatted at all. - The Admin deletion page shows the right objects when the list is truncated.
truncated_unordered_listnow decides whether an item is visible before it counts the item's children, as Django's own filter does. Before, a visible parent disappeared when its children crossed the limit and the "…and N more objects" count was wrong. It also no longer renders an empty<ul>when all the children are hidden. - Schemas now load from a directory whose name contains percent escapes, such as
pct%20dirora%41b. The XSD compiler percent-decoded plain path sources, so a project, or an installation of the package, under such a directory failed withdj_hyperview.E012orschema_invalideven with default settings. The package now hands the compiler absolute file URIs. - A malformed
schemaLocationin anEXTRA_SCHEMASfile, such as//[::1/x.xsd, is now reported asforbidden_schema_reference(dj_hyperview.E012withreason: forbidden_schema_referenceduring configuration checks). Before,urlparseraised a bareValueErrorthat crashedmanage.py check. - The dependency system checks (
dj_hyperview.E013anddj_hyperview.E015) no longer crash withValueErrorwhen the module they look up is a stand-in insys.moduleswithout a spec, such as aModuleType,MagicMockorSimpleNamespacethat a test installs.importlibraises for such an entry, but it still imports, so the check now counts it as installed. The newdj_hyperview.W007follows the same rule. - A
VALIDATIONlimit that is anintsubclass other thanboolnow fails the system check withdj_hyperview.E009, the ruleValidationSettingsalready enforced. Before, the check accepted it and the first read of the settings raised a bareValueError. - An XML declaration whose encoding name is not an XML 1.0
EncName(a letter followed by letters, digits,.,_or-), is longer than 64 characters, or names no codec, such as a name containing a NUL or a lone surrogate, now fails withinvalid_encoding, like any other encoding that is not UTF-8. The codec lookup raisesValueErrorfor such a name, notLookupError, sovalidate_hxml,validate_fragment_hxml,validate_rendered_hxml,validate_template_source,HyperviewResponseand the template loader leaked a bareValueErrororUnicodeEncodeErrorthat noTemplateValidationErrorhandler caught, andpublish_template, model validation and the Admin form raised it instead ofValidationError. The name is now decided from the alias table of Python'sencodingspackage, which is how the registry resolves it, and never passed tocodecs.lookup. Python keeps every name it looks up, found or not, for the life of the process, so each distinct name in a declaration used to add an entry to a cache that is never emptied: 5,000 distinct names added 5,000 entries, 20 names of 200 KB each kept 4.2 MB, and valid-looking names such aszz0tozz4999still added 5,000 after a grammar check alone. Every spelling of UTF-8 that the registry accepts and the grammar allows is still accepted, for exampleutf-8,UTF8,u8,utfandcp65001; a spelling that only the lookup's own normalization accepted, such asutf 8or a trailing space, is rejected. - A document is reported as
max_depthonly when libxml2 stops with a resource-limit error that mentions depth, or with its own "Excessive depth in document" message, which libxml2 before 2.13 reported as an internal error. Before, any syntax error whose message contained "depth" was reported asmax_depth, so a mismatched end tag in an element nameddepth, or a duplicated attribute nameddepth, hid the realmalformed_xmldiagnosis. - Checking a document for DTD and entity declarations is much faster after a declaration literal appears anywhere in it, even inside a comment or CDATA section. The scan used to test every character: about 1 MB took 215 ms rendered and 373 ms as template source, and now takes a few milliseconds. The result is unchanged.
- The
ADMIN.PERMISSIONsystem check no longer crashes withNameErroron Python 3.14 when the permission callable annotates a parameter with a type that is imported only underTYPE_CHECKING. - Importing the package no longer loads Django's test framework into production
processes:
setting_changednow comes fromdjango.core.signals. - A UTF-8 BOM followed by template whitespace before the XML declaration, such as
a file that starts with a BOM and
{% load dj_hyperview %}, now renders. The BOM is preserved and the whitespace after it is removed, as documented. HyperviewEngine.get_templateraisesInvalidTemplateNamefor an empty,Noneor non-string name on Django 5.2 and 6.1 alike. Django 6.1 rejects these before any loader runs, which surfaced asTemplateNotFoundand an HTTP 500 instead of the 400 that unsafe names produce.- On Django 6.1, an
Acceptheader with a media range longer than Django's 10,000 character limit no longer makesHyperviewMiddlewarefail the request. It is treated as not accepting Hyperview. Django 5.2 has no such limit and parses the header as before.
Documentation¶
- The configuration guide no longer says that project schemas can import the
Hyperview namespace. Only
xs:anywith that namespace lets a custom element hold Hyperview elements: anxs:importof it cannot supply Hyperview components, because every extra schema is also compiled on its own, without the Hyperview schema, and arefortypethat names one fails withdj_hyperview.E012(reason: schema_invalid). VALIDATION.MAX_NODESandMAX_DEPTHare documented as bounds on nodes, not only elements. Comments and processing instructions inside the root element have always counted toward both, which is the safer bound. Those outside the root element are not walked, so onlyMAX_BYTESbounds them.- A system check reference in the configuration guide lists every
dj_hyperview.EandWID with its meaning and fix, and explains thatDummyCacheraises onlyW004yet makes every lookup fail underFAILURE_MODE="raise". - The HTTP guide documents
HyperviewMiddlewareandrequest.hyperview, and states that the middleware adds noVaryheader: a view that returns HXML to Hyperview clients and something else to other callers on one URL must addVary: Accept, X-Hyperview-Versionitself. The README no longer calls request detection "content negotiation". - The configuration guide documents the Redis 7 ACL rule that realtime needs
(
resetchannelsplus the namespace's channels) and the limits of therediss://andunix://options, and showspublish_after_commitwith its real signature:usingis keyword-only and required. - Hyperview compatibility is a page of its own again,
hyperview-compatibility.md, that describes the current 0.111.0 contract and the 0.110.0 history. It replaces thehyperview-0.110.0stub, whose URL no longer exists. - The documentation no longer says that redis-py 7.4.1 is exercised against a live
Redis: CI runs 8.1.0, and 7.4.1 is only the declared lower bound of the
[realtime]extra. It also drops the removedVALIDATION.MODEfrom the database guide, the "confirm publication" hedging, the unsupported claim about Hyperview 0.112.0, and HyperTodo instructions that no longer match that application.
Compatibility¶
- Hyperview 0.112.0 removes
createTestPropsand changes native test-ID behavior in the client. The server contract is unchanged: 0.1.0b3 continues to validate against the corrected 0.111.0 schema introduced in 0.1.0b2. This repository records upstream provenance for 0.110.0 and 0.111.0 only.
Maintenance¶
- Refresh compatible documentation/development dependencies, the pinned CI uv patch, and verified setup-node/setup-uv action releases without changing the runtime dependency bounds.
- Pin the CI uv patch to 0.12.22, the release that regenerated
uv.lock(lock format revision 5), souv sync --lockedin CI uses the uv that wrote the lock. - Give every workflow a
concurrencygroup, in which only a pull request cancels its earlier run, and make every checkout drop its token from the git configuration (persist-credentials: false). The CI group has its own prefix, because a workflow that the release calls sees the release's name ingithub.workflow, and the same group in both deadlocks the run. The default token was already read-only, with write access granted per job. - Declare PyYAML, which the tests import, in the
devdependency group, make the release tag tool read thepyproject.tomlbeside it instead of the current directory's, and ignore thesite/documentation build. - Stop shipping package data that nothing read at runtime: the unreferenced
schemas/0.111.0/catalog.json, and the package-ownedr2corrections, margin adaptation and committed catalog of the archived 0.110.0 schemas. The upstream 0.110.0 XSDs stay, soget_hyperview_schema_path()andbuild_hyperview_catalog()are unchanged, and the package carries about 410 KB less data. The tests that only compared those files with each other are removed with them. - Remove test and tooling code that pinned nothing: the
--show-matrixoption andmatrix_commands()oftools.test_matrix, which CI and the documentation never used; a duplicate test of the coverage runner; seven of eight editor theme tests, which asserted onesetThemecall after the theme listeners they exercised were removed; an unused XML fixture; and the assertions that the old package namesdjango_hvanddjango-hyperviewdo not appear. - Replace the Codecov receipt and seal machinery with Codecov's own checks. It was
about 2,600 lines of tools, tests and workflow steps, and its pull-request path
had never run. CI now uploads the default and Redis reports with the pinned
Codecov action and
fail_ci_if_error, andcodecov/projectandcodecov/patchbecome required checks through a ruleset onmain, which the maintainer configures (see Release and rollback).codecov.ymlis removed, so Codecov uses its defaults, and the local ≥95% thresholds oftools.test_matrixare unchanged. The release no longer reuses a coverage receipt: it requires the tagged commit to be an ancestor ofmain, runs CI without a coverage upload and no longer receives the Codecov token.
0.1.0b2 — 2026-09-24¶
Added¶
- Validate against Hyperview 0.111.0, including optional
boolean
content-insetson scroll-capable elements. The 0.110.0 upstream resources and inspection helper remain available unchanged; the new corrected registry and its editor catalog share one 0.111.0 contract.
Fixed¶
- The optional Admin HXML editor stays dark from initialization, regardless of the Admin theme, theme toggles, or system color preference.
Changed¶
- Admin Format and Validate reports expected dynamic-template limits as a
short, neutral informational note (
schema_static_incomplete, severityinfo) rather than a warning. Source navigation, blocking errors, save checks, and mandatory validation of rendered HXML are unchanged.
Documentation¶
- Explain the Django-to-Hyperview request lifecycle in the Quick Start, with an executable full-screen and tap-to-fragment example, server/client responsibilities and direct links from the landing, mobile and HTTP response guides.
Compatibility¶
- The HyperTodo test application's mobile client now pins upstream Hyperview 0.111.0, whose #1348 fix distinguishes failed fragment fetches from intentional sync drops. Mounted client tests in that application cover HTTP 500, network rejection, indicator and pull-refresh cleanup, successful retry, and a dropped request. This is not a package change: no server SSE protocol change or React Navigation migration is required.
0.1.0b1 — 2026-09-10¶
Changed¶
- Start the 0.1 beta line with
Development Status :: 4 - Betametadata and an explicit within-line public-API compatibility commitment. Incompatible changes require migration guidance, changelog disclosure and regression tests; security fixes may require an accelerated, documented exception. - Formalize the tested contract already shipped in a22. This release introduces no new package SSE runtime behavior; HyperTodo mobile fixes and its platform acceptance belong to that application's own release and tests.
Documentation¶
- Consolidate superseded local plans into one current roadmap, preserve pending upstream and capability work, and keep the old Hyperview compatibility URL as a link to the canonical testing section.
- State the exact Python/Django, Admin, SQLite, Redis and HXML test scope and deployment limits. Beta does not imply PostgreSQL/MySQL, load/soak or universal native-platform proof; SSE remains best-effort with no durable replay guarantee.
0.1.0a22 — 2026-09-10¶
Added¶
- Realtime invalidate v2 adds bounded opaque mutation/entity metadata
and the public
INVALIDATION_VERSIONScapability while preserving exact v1 envelopes and after-commit capture. Applications must negotiate/project for old clients and deploy backend workers together. Updated guidance separates package primitives, coordinated application behavior and exact v1 fallback; package tests alone are not native acceptance.
Fixed¶
- Normal CI now includes the live Redis profile while preserving all six no-Redis compatibility cells and explicit base-only callers. The coverage collector verifies current-run artifact identities, exact counters and report digests before uploading both actual XML reports together; previously the executed Redis coverage was absent from the upload. External Codecov acceptance remains distinct from successful upload.
- Release now requires an exact-SHA successful main CI acceptance receipt plus current successful Codecov project/patch statuses before staging. Ordinary CI explicitly assesses merged reports; identical evidence can reuse or promote an immutable assessment without duplicate uploads. Bounded waits occur outside the SHA lock for producers, and missing, expired or changed proof fails closed. Coverage thresholds and Codecov automatic targets are unchanged.
0.1.0a21 — 2026-09-09¶
Added¶
- Centralized optional
HYPERVIEW.REALTIMEconfiguration with exactREDIS_URLandNAMESPACE, frozenget_settings().realtime, sanitizedE022checks and no Redis import/network during settings access. Omitted/None disables it; cache-alias adaptation is not implemented. The configuration guide now opens with a complete executable example, then explains each section in plain English. - Step-by-step SSE guide with verified ASGI/publication snippets, typed list/form examples and an explicit package-versus-application integration boundary.
- Optional
[realtime]Redis broker with immutable after-commit capture, actual subscription ACK admission, bounded queues and conservative resync hints. -
Public
realtime_asgirequest ownership andsse_responseclosed framing; cleanup survives sync middleware adaptation and pre-frame disconnects. These are transport primitives, not application authorization or replay. -
Public frozen
TemplateInvalidationevents and robusttemplate_invalidatedsignal for supported database template mutations after commit. Notifications share the cache callback, including disabled/failing cache paths, without suppressing existing cache exceptions or requiring Redis/network transport.
Fixed¶
- Fresh-process realtime tests now explicitly locate the repository source,
independent of an inherited
PYTHONPATH. Redis-import and network prohibitions remain enforced; no runtime transport behavior changed.
0.1.0a20 — 2026-09-09 (not published)¶
- Release checks failed before artifact staging and publication because three fresh-process tests could not import the source package in CI. The tag is retained unchanged; the corrected SSE release is a21 above.
0.1.0a19 — 2026-09-09¶
Fixed¶
- Context-free Admin validation now ignores XML comments, CDATA sections, and
processing instructions while locating Django tokens. Literal
{% include %}tags that follow documentation containing quotes are expanded correctly instead of producing false text-content schema errors.
0.1.0a18 — 2026-09-09¶
Added¶
- Context-free Admin validation uses Django's parser for actionable syntax
diagnostics and recursively resolves every literal
{% include %}through the configured Hyperview source precedence. Included source is checked both independently and in its parent position; missing, unsafe, unavailable, cyclic, and excessive expansion is rejected without exposing private source details. - Statically visible HXML child placement and order are validated against the same corrected schema used for rendered responses. Django expressions in text or quoted attributes are treated as an unknown scalar, preserving structural validation without inventing runtime values.
Changed¶
- The Admin HXML editor uses a 15-pixel font and two-space soft tabs.
- Runtime schema failures now explain unexpected children, incomplete content, forbidden or missing attributes, and invalid typed values without including rendered data or internal paths.
Fixed¶
- Mutually exclusive Django branches no longer create false child-order errors. Conditional structure, inheritance, and variable-selected includes instead produce an explicit incomplete-analysis warning and remain subject to final rendered-response validation.
0.1.0a17 — 2026-09-08¶
Fixed¶
- Context-free Admin validation no longer treats non-rendering
{% load %}tags or inline Django comments as incomplete dynamic HXML. When later runtime values remain, the warning points to the first value that actually requires rendering instead of blaming the template preamble.
0.1.0a16 — 2026-09-08¶
Added¶
- One automatically enforced Hyperview 0.110.0 schema includes narrowly verified
text, image, accessibility, date-field, and percentage-margin corrections.
Only
style@widthis constrained to its supported numeric forms. Schema revision tracking is internal; upstream resources remain unchanged. - Typed
SCHEMA_EXTENSIONSdeclarations describe application-owned behavior attributes and extra attributes on specific built-in elements without opening standard actions or allowing arbitrary attributes. Rendered validation, context-free Admin checks, and editor completion share this single contract. See custom schema declarations. HYPERVIEW_VALIDATION_CONTRACT="automatic-xsd-v1"identifies automatic validation support for consumers that must reject an incompatible package. This exported capability is not a setting or a package release version.
Breaking changes¶
- Remove
SCHEMA_PROFILE,VALIDATION.MODE, andVALIDATION.SCHEMAfrom settings. Their presence now raises a configuration error with migration guidance.ValidationSettingsaccepts safety limits only; validation cannot be disabled or replaced by a callback. xmlschemais a normal dependency. The old[schema]installation extra is retained as an empty compatibility alias;[editor]remains optional.- Hyperview responses require complete, valid HXML. Body assignments,
write(), andwritelines()validate atomically; failed updates preserve the prior body. Empty document bodies are allowed only for 204, 205, and 304, which reject nonempty bodies. HEAD validates the document before suppressing its transport body; passrequest=requestto eager responses for this request context. Assemble incremental XML before assigning it. - Django gzip transport is accepted only when it encodes the already-validated
document exactly. Compressed input cannot introduce a new document or bypass
validation. Remove
Content-Encodingbefore replacing the document afterward. - Adopt package and application configuration together after reviewing existing database overrides. Previously unchecked invalid output now fails validation; do not use seed commands, automatic repairs, or shared-cache flushes to migrate.
Fixed¶
- The active completion catalog preserves attribute namespaces and offers custom behavior attributes only for the selected literal action. Namespaced standard attributes no longer collide with unqualified application attributes in that catalog; upstream inspection helpers retain their original output.
- Incompatible duplicate declarations are rejected consistently, including differences inherited from schema defaults and namespace bindings, rather than letting the order of external schemas change validation results.
- External elements with simple XSD types no longer crash completion or Admin validation catalogs. They expose no custom attributes; validation stays strict.
Changed¶
- The optional Admin editor now runs Format and Validate before Save, Save and add another, and Save and continue editing. Errors block the write with inline diagnostics, warning-only results continue the selected action, and the server form repeats validation when JavaScript is bypassed.
0.1.0a15 — 2026-09-08¶
Changed¶
- The optional Admin editor now exposes one Format and Validate action. It applies conservative formatting first and then validates the resulting Ace buffer. When formatting is unsafe, validation still runs against the unchanged source and reports both outcomes independently.
0.1.0a14 — 2026-09-08¶
Added¶
- Validate source now checks statically visible HXML elements, attributes,
required attributes, and literal enumerations against the selected XSD catalog,
including project
EXTRA_SCHEMAS. Dynamic values remain deferred to full rendered-response validation, and incomplete static analysis is reported as a warning rather than a false success.
0.1.0a13 — 2026-09-08¶
Added¶
- Validate source in the optional Admin editor checks an unsaved template name and source without context, rendering, persistence, or revision changes. It reports safe source diagnostics and Django syntax line numbers when available.
Removed¶
- The experimental static Admin template preview and its server-configured scenarios. Template authoring keeps the Ace editor, formatter, schema-backed completion, and publication validation.
0.1.0a12 — 2026-09-08¶
Added¶
- Opt-in Admin template preview with configured example-data scenarios, unsaved draft rendering, structured diagnostics, and an isolated static web view. Preview is disabled by default, keeps the editor buffer unchanged, and never publishes a template.
Fixed¶
- Static preview now applies styles declared at document level and refreshes visual warning state when selecting another screen.
0.1.0a11 — 2026-09-08¶
Upgrade notes¶
- Django
FileBasedCacheand its subclasses are rejected because their non-atomicadd()cannot protect invalidation barriers. Configure a compatible backend or disable Hyperview caching;bypassdoes not suppress this error. See cache migration guidance. bulk_create(update_conflicts=True)now raisesNotSupportedErrorbefore consuming input or writing. Normal inserts andignore_conflictsremain supported; use publication services for updates.- Run the read-only
check_hyperview_templates --database ALIAScommand only on authorized databases before deployment. Invalid names, incorrect identities, or duplicates require a backed-up, human-reviewed repair, not an automatic migration. See historical integrity checks.
Added¶
- Opt-in
SCHEMA_PROFILE="compatible-0.110.0"permits percentages in the nine style margin attributes without broadening unrelated types. The defaultupstream-0.110.0and standalone upstream helpers retain their existing meaning. Validation and autocomplete use the selected profile together. See custom schemas. - The package README and documentation landing page now link to HyperTodo, the maintained Django and Expo test application.
Fixed¶
- Declaration scanning is linear, including unclosed Django delimiters; only raw template sources interpret Django comments. Rendered XML retains its independent safety checks and escaping. See XML security.
- An initial BOM no longer conceals incompatible XML encoding declarations; valid UTF-8 BOM content remains unchanged.
- Filesystem permission and I/O failures remain
SourceUnavailableon Python 3.14 instead of selecting a lower-priority template. See filesystem sources. - Read-only Admin users can view templates while the Ace editor is enabled.
- HXML formatting preserves meaningful whitespace, mixed content, CDATA,
preformatted text and Django syntax, including
blocktranslateandblocktransbodies; ambiguous input remains unchanged. See Admin authoring. - Nested deletions use both database alias and primary key when tracking invalidations; commit and rollback keep their existing guarantees.
- Generator-based
update_fieldsis consumed once without dropping fields. - Extra XSDs reject
xs:override; only the fixed bundled compatibility adaptation uses it. Validation and completion both inspect transitive local references before reusing dependency-aware caches. See schema safety. - The compatibility runner enforces lines ≥95% and branches ≥95% separately after combining base and Admin coverage, while retaining the combined gate. Missing, malformed and stale reports cannot pass. See testing.
0.1.0a10 — 2026-09-07¶
Added¶
ADMIN.PERMISSIONas an authoritative callable or dotted-path policy for stored-template creation, changes, and deletion, with a superuser-only default.- Detailed custom-schema guidance for project-owned namespaced HXML elements, validation, and editor autocomplete.
Changed¶
- The HXML Ace editor now fills the available Django 5.2 and Django 6.1 admin form width instead of collapsing to its gutter inside the admin flex layout.
Security¶
- Template mutation callbacks grant access only when they return the literal
boolean
True; import problems fail system checks and runtime failures deny access.
0.1.0a9 — 2026-09-07¶
Added¶
- A versioned Hyperview 0.110.0 XSD registry, provenance, checksums, and a deterministic completion catalog.
- Optional
[schema]support for XSD 1.1 validation of rendered documents and fragments, including project-owned custom schemas. - An optional
[editor]profile with a CSP-safe HXML Ace editor, contextual completion, Django-template highlighting, safe formatting, and theme-aware presentation. - Safe line and column coordinates on
TemplateValidationErrorwhen available.
Changed¶
- Database publication now compiles Django template syntax before any write or revision increment, without rendering context-dependent output.
- Schema and catalog caches now follow settings and local-file fingerprints.
- CI now tests editor JavaScript on Node 24 and smoke-tests base, schema, and editor wheel profiles.
Security¶
- Custom schemas reject remote references, local-root escapes, and incompatible duplicate declarations.
- The admin catalog endpoint requires authentication and model view permission; editor assets are local and avoid inline scripts.
0.1.0a1 to 0.1.0a7 — 2026-09-04 to 2026-09-05¶
These early alphas predate this changelog, so no per-release notes exist. The Git
tags v0.1.0a1 to v0.1.0a7 and the history behind them are the record.
0.1.0a8 — 2026-09-06¶
Added¶
- First-class eager and lazy fragment responses with the
application/vnd.hyperview_fragment+xmlmedia type. - A PEP 561
py.typedmarker and the publicdj_hyperview.__version__value. - A standalone Expo and Hyperview mobile Getting Started guide.
Changed¶
- Hardened database publication, byte-exact identity handling, legacy-row recovery, mutation routing, and transaction-aware invalidation.
- Isolated the package template engine from unsafe consumer engine options.
- Preserved cache-safe source results across mixed source chains.
- Reported absent source configuration and missing filesystem roots as actionable Django system-check warnings.
- Reused validated settings and the default rendering engine until a dependent Django setting changes.
- Rejected unsafe
bulk_create()names and invalidated cache state after bulk writes, including conflict updates.
Compatibility¶
- Tested against Django 5.2 and Django 6.1 on supported Python versions.
- Fragment update endpoints must return a bare client-safe element. Full HXML
documents continue to use
application/vnd.hyperview+xml.