usawa

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | Submodules | LICENSE

commit 84805b603dac3c924427681d08522c5fbf764491
parent 7a8ee65dc5c2fddfa6a6ca7a0d4a919a9a9f61b2
Author: lash <dev@holbrook.no>
Date:   Fri, 13 Feb 2026 10:27:12 +0000

Add asset file parser

Diffstat:
Adummy/doc/server.texi | 10++++++++++
Mdummy/setup.py | 1+
Adummy/tests/asset.py | 22++++++++++++++++++++++
Adummy/usawa/asset.py | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdummy/usawa/runnable/add.py | 2++
5 files changed, 122 insertions(+), 0 deletions(-)

diff --git a/dummy/doc/server.texi b/dummy/doc/server.texi @@ -0,0 +1,10 @@ +@anchor{service} +@chapter Service + +usawa provides a socket daemon service to allow multiple clients operate on the same entry store concurrently. + +It is currently implemented as a @emph{Unix socket} and a @emph{TCP socket}. + +The daemon will be run with its own private key, and will sign all responses. + +The daemon will also provide an authentication mechanism, checking access to the store with an ACL and a signature for each request. diff --git a/dummy/setup.py b/dummy/setup.py @@ -9,6 +9,7 @@ varints_dir = os.path.join(aux_dir, 'varints', 'varints') setup( install_requires=[ + "filemagic~=1.6", "wheepy[valkey]~=0.0.3", "confini~=0.6.5", "lxml~=6.0.2", diff --git a/dummy/tests/asset.py b/dummy/tests/asset.py @@ -0,0 +1,22 @@ +import logging +import os +import unittest + +from usawa.asset import Asset + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +testdir = os.path.realpath(os.path.dirname(__file__)) + + +class TestAsset(unittest.TestCase): + + def test_asset_file(self): + fp = os.path.join(testdir, 'test.xml') + asset = Asset.from_file(fp) + logg.debug('asset {}'.format(asset)) + + +if __name__ == '__main__': + unittest.main() diff --git a/dummy/usawa/asset.py b/dummy/usawa/asset.py @@ -0,0 +1,87 @@ +import hashlib +import mimetypes +import logging +import os + +import magic + +logg = logging.getLogger('asset') + +BLOCKSIZE = 512 + +magic = magic.Magic(flags=magic.MAGIC_MIME_TYPE) + +def parse_path(path): + s = os.path.basename(path) + v = s.rsplit('.', maxsplit=1) + slug = v[0] + ext = None + if len(v) == 2: + ext = v[1] + return (slug, ext,) + +class Asset: + + def __init__(self): + self.digest = None + self.mime = None + self.enc = None + self.slug = None + self.ext = None + + + + + def get_filename(self): + s = self.slug + if self.ext != None: + s += '.' + self.ext + return s + + + def get_mimestring(self): + s = self.mime + if self.enc != None: + s += '; encoding=' + self.enc + return s + + + + @staticmethod + def from_file(filepath, description=None, slug=None, mimetype=None): + o = Asset() + h = hashlib.sha256() + f = open(filepath, 'rb') + b = f.read(BLOCKSIZE) + if mimetype == None: + v = mimetypes.guess_file_type(filepath, strict=True) + if v != None: + mimetype = v[0] + o.enc = v[1] + if mimetype == None: + mimetype = magic.id_buffer(b) + o.mime = mimetype + h.update(b) + c = BLOCKSIZE + while True: + b = f.read(BLOCKSIZE) + if len(b) == 0: + break + h.update(b) + c += len(b) + f.close() + 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(filepath) + + logg.debug('asset read {} bytes from path {} mime {}'.format(c, filepath, o.mime)) + + return o + + + def __str__(self): + return 'file ̈́' + self.get_filename() + ' mime ' + self.get_mimestring() + ' digest ' + self.digest.hex() diff --git a/dummy/usawa/runnable/add.py b/dummy/usawa/runnable/add.py @@ -28,6 +28,7 @@ class Context: self.part = [] self.output = None self.f = None + self.attachments = [] def close(self): @@ -114,6 +115,7 @@ argp.add_argument('-r', type=str, help='external reference') argp.add_argument('-s', type=str, dest='src_account', default='general', help='source account') argp.add_argument('-t', type=str, dest='dst_account', default='general', help='destination account') argp.add_argument('-a', type=str, dest='amount', help='source and destination amount') +argp.add_argument('-x', type=str, dest='attachment', action='append', help='add file attachment') argp.add_argument('-o', type=str, dest='output', help='output file for updated XML document') argp.add_argument('--src-type', dest='src_type', type=str, choices=CATEGORIES, default='expense', help='source type') argp.add_argument('--dst-type', dest='dst_type', type=str, choices=CATEGORIES, default='asset', help='dest type')