Content and attachments¶
Inline content is stored in the XML document. Attachments are external files
or directories referenced by src. Bundles with attachments can be ZIP
archives or directories containing XML and files.
The <repository> and <file> elements can reference attachments. Their
src paths are relative to the bundle’s XML file, including any containing
basesrc prefix. A file without inline content defaults to src=name when
src is omitted. Directory attachments include their nested files and
directories. ZIP attachments are read directly from the archive.
The Repository and
File classes expose attachment access.
open() returns a binary stream that the caller closes. read() returns
all file bytes. copy_to() copies content to a directory and refuses to
overwrite an existing destination. A directory attachment requires a relative
member path when opening or reading one of its files.
A file’s value property accesses inline Python values. It raises
AttachmentContent for an external
attachment. open() and read() work for both attachments and inline text
or bytes. Inline strings are encoded as UTF-8 by these methods.
The content parameter of create()
and recreate() accepts bytes
for binary inline content. Export encodes these bytes in the XML document, and
loading the bundle restores the original bytes.
New attachment paths must exist when the element is created. Source files must remain available until export. For directory export, source paths must not overlap the destination directory.
Recreating ordinary attachment elements keeps references to the original source without reading or copying its contents. The source files or ZIP bundle must remain available until the target bundle is exported. Self-test fixtures and report records have separate ownership rules, described in Self-test declarations and reports.
ZIP export copies compressed attachment bytes directly when the source and target
use the same compression method, even when compresslevel is set. Copied
attachments retain their original compression level. Set
always_recompress=True to decompress and recompress ZIP attachments using the
requested compression settings. Its default is False. A different compression
method also requires recompression. Newly compressed files and the bundle XML use
the requested compresslevel.
Added in version 9.0.0: Copying compressed ZIP attachments, and the always_recompress option
that turns that copying off.
Directory export copies attachments out of their source storage. With sign_files=True, export
also reads file contents to calculate and verify hashes.
Executable directory members¶
Added in version 9.0.0.
New filesystem attachments strip executable bits by default. To retain executable
files inside a directory attachment, pass preserve_executable=True when
creating the file. This example requires an existing dist-newstyle directory:
from pathlib import Path
from momotor.bundles import ProductBundle
from momotor.bundles.elements.files import File
bundle = ProductBundle()
build_directory = Path("dist-newstyle").resolve()
artifacts = File(bundle).create(
name='dist-newstyle', src=build_directory, preserve_executable=True,
)
bundle.create(files=[artifacts])
bundle.to_file("artifacts.zip")
The option retains each file’s existing executable bits. It does not make other files executable or copy ownership, setuid, or setgid bits. Directory permissions use the normal filesystem defaults.
ZIP export stores executable bits on each member. Import, copying, recreation, and subsequent exports honor those bits without another opt-in. Directory exports also retain the bits, so importing an exported directory has the same behavior. Bundles that already lost the bits cannot recover them.
API¶
- class momotor.bundles.elements.content.ContentAttachmentElement(bundle)¶
A full content element supporting attachments.
Contents can be string, bytes, boolean, integer, float or
decimal.Decimalvalues, or an attachment referenced bysrc.- file_size(path=None)¶
Return the content length.
Inline strings report their character count. Inline bytes and external files report their byte count.
- Parameters:
path (
str|PurePath|None) – Optional relative path inside a directory attachment.- Return type:
- Returns:
Content length.
- Raises:
FileNotFoundError – If the attachment path is missing.
IsADirectoryError – If the attachment path identifies a directory.
- has_writable_content()¶
Return whether the element has an attachment or inline text or bytes.
- is_dir(path=None)¶
Return whether the attachment or a member is a directory.
- open(path=None)¶
Open inline text, inline bytes, or an attachment as a binary stream.
Inline strings are encoded as UTF-8. The caller closes the returned stream.
- read(path=None)¶
Read inline text, inline bytes, or an attachment as bytes.
- Parameters:
path (
str|PurePath|None) – Relative member path for a directory attachment. Omit for inline content or a single file.- Return type:
- Returns:
Complete file content. Inline strings are encoded as UTF-8.
- Raises:
FileNotFoundError – If a member path is supplied for inline content, or an attachment is missing.
- class momotor.bundles.elements.content.ContentBasicElement(bundle)¶
A basic content element. Contents can be string or boolean values.
- class momotor.bundles.elements.content.ContentFullElement(bundle)¶
A full content element.
Contents can be string, bytes, boolean, integer, float or
decimal.Decimalvalues.
- momotor.bundles.elements.content.NO_CONTENT = <object object>¶
A sentinel value indicating there is no content.
- class momotor.bundles.elements.content.ContentElementBase(bundle)¶
Base class for elements with content, either as a value attribute, child nodes or as an attachment. Handles name, type, value and encoding attributes and child node content. This unifies the handling of <option>, <property> and <file> nodes
The subclasses
ContentBasicElement,ContentFullElementandContentAttachmentElementexist mainly to create the proper documentation of the properties and methods.value attribute, attachments and child nodes with content are mutually exclusive. type has a double role: For nodes with a ‘value’ attribute it indicates the type (string, integer or float), for nodes with child content or attachment, it is the content mime type. encoding is only used with child content, supported encodings are
base64andquopriSee Content and attachments for details on content handling and attachments.
- property value: PT¶
value attribute: The content.
Setting value to None will generate a node with a <none/> tag. Set value to
NO_CONTENTto create an empty content node.When accessing, raises
NoContentif no content was set instead of returningNO_CONTENT, raisesAttachmentContentif the node refers to an attachment usingsrc.
- class momotor.bundles.mixins.attachments.SrcAttachmentMixin(*args, **kwargs)¶
Mixin providing external file attachment functionality.
Adds support for referencing external files via the
srcattribute. Handles both filesystem and ZIP bundle-based attachments with validation, copying, and export capabilities.See Content and attachments for details on how attachments are handled differently for new vs. existing bundles.
- copy_to(destination, *, name=None)¶
Copy an attachment this element refers to, to given destination directory.
If the attachment is a file, creates a new file in the given directory. If name is provided this will be the name of the new file, otherwise the name of the source file is used.
If the attachment is a directory, copies the contents of the source directory to the destination directory. If name is provided it is created as a new directory inside the destination directory.
Will not overwrite an existing file or directory.
- Parameters:
- Raises:
ValueError – when the attachment is not writeable as a file
FileExistsError – when a file already exists
- Return type:
- directory_hashes(hash_names)¶
Calculate versioned digests of every regular descendant and relative name.
- file_ctime(path=None)¶
Get file creation time for the attachment.
- Return type:
- file_hashes(hash_names)¶
Calculate the hashes of the file.
Only for file attachments. Will return an empty dictionary if the file does not exist or is a directory.
- has_attachment_content()¶
Check if the element has attachment content.
- Return type:
- Returns:
True if the element has an attachment with content
- has_bundle()¶
Check if src references a file associated with a bundle.
- Return type:
- Returns:
True if src references a file associated with a bundle
- has_writable_content()¶
Check if the attachment content can be written.
- Return type:
- Returns:
True if the element has attachment content that can be written
- has_zip_bundle()¶
Check if src references a file in a zipped bundle.
- Return type:
- Returns:
True if src references a file associated with a zipped bundle
- is_dir(path=None)¶
Check if the attachment is a directory.
- Parameters:
path (
str|PurePath|None) – Optional subpath within the attachment- Return type:
- Returns:
True if the attachment (or subpath) refers to a directory
- Raises:
FileNotFoundError – If the attachment or path doesn’t exist
- iterdir(*, include_empty_root=False)¶
Recursively iterate the contents of a directory attachment. The returned paths are relative to self.absolute_path
If the attachment is an empty directory and include_empty_root is True, a single None value is yielded
- open(path=None)¶
Open the attachment file for reading. Handles opening files directly from filesystem and from zipped bundles
- Parameters:
path (
str|PurePath|None) – for directory attachments, path selects a file in that directory- Return type:
- Returns:
the opened file
- Raises:
FileNotFoundError – if the element has no attachment (when
srcis None), or when it’s a directory and path does not exist in that directoryIsADirectoryError – when it’s a directory
- read(path=None)¶
Read the contents of the attachment
- validate_hashes(expected_hashes, *, kind=None)¶
Validate file hashes or an explicitly versioned directory digest.
- property absolute_path: Path¶
Get the absolute path of the attachment referenced by
src- Raises:
ZippedAttachment – when the attachment is in a zipped bundle and therefor has no filesystem path
- bundle¶
- class momotor.bundles.mixins.attachments.AttachmentSrc(path=None, bundle=None, *, validate=True, preserve_executable=False)¶
Represents a source reference for bundle attachments.
Stores an absolute filesystem path or a path relative to a bundle. A missing path raises
FileNotFoundErrorby default. Withvalidate=False, a missing path emits a warning and becomesNone.- Parameters:
path (
PurePath|None) – Path to the attachment (absolute or relative to bundle)bundle (
Bundle|None) – Bundle containing the attachment (for relative paths)validate – Whether to validate path existence during initialization
preserve_executable (
bool) – Whether to retain source executable bits. Imported attachments retain them automatically.
- Raises:
FileNotFoundError – If validate=True and the path doesn’t exist
Content type parameter¶
PT denotes the decoded Python value type in inherited content properties.
- momotor.bundles.elements.content.PT = ~PT¶
Type variable.
Usage:
T = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # Must be str or bytes
Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions. See class Generic for more information on generic types. Generic functions work as follows:
- def repeat(x: T, n: int) -> List[T]:
‘’’Return a list containing n references to x.’’’ return [x]*n
- def longest(x: A, y: A) -> A:
‘’’Return the longest of two strings.’’’ return x if len(x) >= len(y) else y
The latter example’s signature is essentially the overloading of (str, str) -> str and (bytes, bytes) -> bytes. Also note that if the arguments are instances of some subclass of str, the return type is still plain str.
At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError.
Type variables defined with covariant=True or contravariant=True can be used to declare covariant or contravariant generic types. See PEP 484 for more details. By default generic types are invariant in all type variables.
Type variables can be introspected. e.g.:
T.__name__ == ‘T’ T.__constraints__ == () T.__covariant__ == False T.__contravariant__ = False A.__constraints__ == (str, bytes)
Note that only type variables defined in global scope can be pickled.
NoContentType is the type of the NO_CONTENT sentinel. It distinguishes
an omitted value from an explicit null value, represented by None.
- momotor.bundles.elements.content.NoContentType = momotor.bundles.elements.content.NoContentType¶
NewType creates simple unique types with almost zero runtime overhead.
NewType(name, tp) is considered a subtype of tp by static type checkers. At runtime, NewType(name, tp) returns a dummy callable that simply returns its argument.
Usage:
UserId = NewType('UserId', int) def name_by_id(user_id: UserId) -> str: ... UserId('user') # Fails type check name_by_id(42) # Fails type check name_by_id(UserId(42)) # OK num = UserId(5) + 1 # type: int