commit 501a53c1cff48d60d024d28c1a2afea02d9563d9
parent d11b6d22e2d986e54c8d0809b88e3f101588a6c1
Author: lash <dev@holbrook.no>
Date: Fri, 13 Feb 2026 12:07:59 +0000
Add inline docs for asset
Diffstat:
4 files changed, 110 insertions(+), 18 deletions(-)
diff --git a/dummy/tests/entry.py b/dummy/tests/entry.py
@@ -108,5 +108,6 @@ class TestEntry(unittest.TestCase):
tree = lxml.etree.fromstring(s)
tree = Entry.from_tree(tree, self.uidx)
+
if __name__ == '__main__':
unittest.main()
diff --git a/dummy/usawa/asset.py b/dummy/usawa/asset.py
@@ -7,6 +7,7 @@ import uuid
import lxml.etree
import magic
+from .constant import NSPREFIX
from .xml import nsmap
logg = logging.getLogger('usawa.asset')
@@ -15,7 +16,21 @@ BLOCKSIZE = 512
magic = magic.Magic(flags=magic.MAGIC_MIME_TYPE)
+"""Return a stem name and extension as a tuple from an absolute or relative path.
+
+Stem name will always be set. Extension will be returned as None if none could be found.
+
+Does not check whether or not the file exists.
+
+:param path: File path.
+:type path: str
+:raises ValueError: Empty path value.
+:return: Stem name and extension.
+:rtype: Tuple
+"""
def parse_path(path):
+ if len(path) == 0 or path == None:
+ raise ValueError('empty path')
s = os.path.basename(path)
v = s.rsplit('.', maxsplit=1)
slug = v[0]
@@ -24,8 +39,16 @@ def parse_path(path):
ext = v[1]
return (slug, ext,)
+
class Asset:
+ """Represents a file asset, used as attachment in usawa.Entry.
+ Object is not intended to be instantiated directly. Instead one of the following static methods should be used:
+
+ Asset.from_file() - Read from local file.
+ Asset.from_io() - Read from a io.BufferedIOBase input stream.
+ Asset.from_tree() - Recreate from an XML tree.
+ """
def __init__(self):
self.digest = None
self.mime = None
@@ -38,6 +61,13 @@ class Asset:
self.description = None
+ """Return the preferred filename with extension for the asset.
+
+ Must always return a value. The filename may or may not have an extension.
+
+ :return: Filename
+ :rtype: str
+ """
def get_filename(self):
s = self.slug
if self.ext != None:
@@ -45,29 +75,75 @@ class Asset:
return s
+ """Return the mime type.
+
+ The encoding specifier will be added if it exists.
+
+ Returns None if mime type has not been set.
+
+ :return: Mime string
+ :rtype: str
+ """
def get_mimestring(self):
s = self.mime
- if self.enc != None:
- s += '; encoding=' + self.enc
+ if s != None:
+ if self.enc != None:
+ s += ';charset=' + self.enc
return s
- @staticmethod
- def from_file(filepath, description=None, slug=None, mimetype=None, localref=None, extref=None):
- f = open(filepath, 'rb')
- return Asset.from_io(f, filepath, f.close, description=description, slug=slug, mimetype=mimetype, localref=localref, extref=extref)
+ """Instantiate an asset object from a local file.
+
+ File is opened and the file object is passed on to the from_io() method.
+ The filepath argument will be passed as the "src" argument to from_io().
+ See from_io() for documentation on the named arguments.
+
+ :param filepath: File path to read from.
+ :type filepath: str
+
+ :raises FileNotFoundError: File does not exist.
+ :raises IsADirectoryError: Path is a directory.
+ :raises PermissionError: File cannot be read.
"""
- :todo: make sure stream close on exception
+ @staticmethod
+ def from_file(filepath, description=None, slug=None, mimetype=None, localref=None, extref=None):
+ f = open(filepath, 'rb')
+ return Asset.from_io(f, filepath, closer=f.close, description=description, slug=slug, mimetype=mimetype, localref=localref, extref=extref)
+
+
+ """Instantiate an asset object from an input stream.
+
+ Unless explicitly set, an attempt to automatically guess the mime type of the stream. If the mime type cannot be guessed by the file extension, a filemagic buffer scan is attempted (first usawa.asset.BLOCKSIZE bytes).
+
+ :param io: Buffered input to read from.
+ :type io: io.BufferedIOBase
+ :param src: Source location.
+ :type src: Source location
+ :param closer: Function that will be called after completed read to close the io stream.
+ :type closer: io.IOBase.close
+ :param description: A description of the file contents.
+ :type description: to this value.
+ :param slug: Override filename with this as stem name.
+ :type slug: str
+ :param mimetype: Explicitly set mime type to this value.
+ :type mimetype: str
+ :param localref: A local reference (e.g. invoice number).
+ :type localref: str
+ :param extref: An external reference (e.g. invoice number).
+ :type extref: str
+ :todo: make sure stream close on exception.
+ :todo: make path uri/remote url friendly when implementing remote stream.
+ :todo: document possible exceptions.
"""
@staticmethod
- def from_io(io, path, closer, description=None, slug=None, mimetype=None, localref=None, extref=None):
+ def from_io(io, src, closer=None, description=None, slug=None, mimetype=None, localref=None, extref=None):
o = Asset()
h = hashlib.sha256()
b = io.read(BLOCKSIZE)
if mimetype == None:
- v = mimetypes.guess_file_type(path, strict=True)
+ v = mimetypes.guess_file_type(src, strict=True)
if v != None:
mimetype = v[0]
o.enc = v[1]
@@ -82,18 +158,19 @@ class Asset:
break
h.update(b)
c += len(b)
- closer()
+ if closer != None:
+ closer()
o.digest = h.digest()
s = mimetypes.guess_extension(o.mime, strict=True)
if s != None:
o.ext = s[1:]
- (o.slug, o.ext) = parse_path(path)
+ (o.slug, o.ext) = parse_path(src)
if slug != None:
logg.info('overriding file base name {} -> {}'.format(o.slug, slug))
o.slug = slug
- logg.debug('asset read {} bytes from path {} mime {}'.format(c, path, o.mime))
+ logg.debug('asset read {} bytes from path {} mime {}'.format(c, src, o.mime))
o.uuid = str(uuid.uuid4())
if localref == None:
@@ -105,8 +182,14 @@ class Asset:
return o
+ """Generate and return an XML representation of the asset.
+
+ :returns: XML tree representing the asset.
+ :rtype: lxml.etree.Element
+ :todo: implement sigs
+ """
def to_tree(self):
- tree = lxml.etree.Element('attachment', nsmap=nsmap())
+ tree = lxml.etree.Element(NSPREFIX + 'attachment', nsmap=nsmap())
if self.mime != None:
tree.set('mime', self.get_mimestring())
if self.uuid != None:
@@ -142,8 +225,16 @@ class Asset:
return tree
- """
+ """Create object from an asset part defined as an XML tree.
+
+ The XML expected is an ledger/entry[]/attachment[] element (in schema defined as the Attachment complexType)
+
+ :param tree: The asset as XML tree.
+ :type tree: lxml.etree.ElementTree
+ :return: Asset part
+ :rtype: lxml.etree.Element
:todo: add to docs cannot directly import from tree generated from to_tree, must go way by string export
+ :todo: implement sigs
"""
@staticmethod
def from_tree(tree):
diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd
@@ -135,7 +135,7 @@
<xs:complexType name="EntryData">
<xs:sequence>
<xs:element name="parent" type="xs:string" />
- <xs:element name="ext" type="xs:string" />
+ <xs:element name="ref" type="xs:string" />
<xs:element name="serial" type="xs:positiveInteger" />
<xs:element name="date" type="xs:date" />
<xs:element name="dateTimeRegistered" type="xs:dateTime" />
diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py
@@ -53,7 +53,7 @@ class EntryPart:
The XML expected is the ledger/entry/data/debit or ledger/entry/data/credit (in schema, defined as the EntryPart complexType).
:param tree: The entry as XML tree.
- :type tree: lxml.etree.ElementTree
+ :type tree: lxml.etree.Element
:param debit: True if the transaction part is a debit.
:type debit: boolean
:return: Entry part
@@ -214,7 +214,7 @@ class Entry:
"""Create an entry object from an XML representation.
:param tree: A parsed XML tree containing the entry.
- :type tree: lxml.etree.ElementTree
+ :type tree: lxml.etree.Element
:param unitindex: A unitindex containing the necessary definitions for the unit symbol used in the entry.
:type unitindex: usawa.UnitIndex
:param min: Minimal valid serial entry.
@@ -438,7 +438,7 @@ class Entry:
:todo: Make sure that sigs publickey lookup key is bytes type
:returns: XML tree representing the entry.
- :rtype: lxml.etree.ElementTree
+ :rtype: lxml.etree.Element
"""
def to_tree(self):
#tree = etree.Element('entry', type=self.typ)