commit c5336f91fee5ea735ab3ec87b99bf0bfee942a4a
parent 53a3e887a2a29f7c49c9baf3baf335c42f2952c6
Author: lash <dev@holbrook.no>
Date: Sat, 7 Mar 2026 21:35:15 -0600
Add accounts validator
Diffstat:
3 files changed, 117 insertions(+), 0 deletions(-)
diff --git a/dummy/tests/account.py b/dummy/tests/account.py
@@ -0,0 +1,44 @@
+import logging
+import datetime
+import unittest
+import os
+
+from usawa import UnitIndex
+from usawa.account import AccountIndex
+from usawa.error import AccountError
+
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+testdir = os.path.realpath(os.path.dirname(__file__))
+
+class TestAccount(unittest.TestCase):
+
+ def setUp(self):
+ self.uidx = UnitIndex('FOO')
+ self.uidx.add('BAR')
+
+ def test_account_lock(self):
+ idx = AccountIndex(self.uidx)
+ idx.add('FOO', 'bar.baz')
+ idx.add('FOO', 'bar.baz')
+ with self.assertRaises(AccountError):
+ idx.add('FOO', 'bar.baz-')
+ with self.assertRaises(AccountError):
+ idx.add('BAZ', 'foo.bar')
+ self.assertFalse(idx.check('BAR', 'foo.baz'))
+ self.assertTrue(idx.check('FOO', 'bar.baz'))
+
+
+ def test_account_list(self):
+ idx = AccountIndex(self.uidx)
+ idx.add('FOO', 'bar.bar')
+ idx.add('FOO', 'bar.baz')
+ idx.add('BAR', 'foo.baz')
+ v = list(idx)
+ logg.debug('results {}'.format(v))
+ self.assertEqual(len(v), 3)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/dummy/usawa/account.py b/dummy/usawa/account.py
@@ -0,0 +1,69 @@
+import logging
+
+from .error import AccountError
+
+logg = logging.getLogger('account')
+
+
+def default_check(path):
+ parts = path.split('.')
+ for v in parts:
+ if not v.isalnum():
+ raise AccountError('invalid part: ' + v)
+ return True
+
+
+class AccountIndex:
+
+ def __init__(self, unitindex, pathvalidator=default_check):
+ self.uidx = unitindex
+ self.accounts = {}
+ self.locked = False
+ self.validate = pathvalidator
+ self.iterval = None
+
+
+ def add(self, sym, path):
+ try:
+ sym = self.uidx.sym(sym)
+ except KeyError:
+ raise AccountError('unknown unit ' + sym)
+ if self.locked:
+ raise AccountError('account index locked')
+ self.validate(path)
+ if self.accounts.get(sym) == None:
+ self.accounts[sym] = []
+ elif path in self.accounts[sym]:
+ logg.debug('Ignoring duplicate account: {}:{}'.format(sym, path))
+ self.accounts[sym].append(path)
+
+
+ def lock(self):
+ self.locked = True
+
+
+ def check(self, sym, path):
+ try:
+ return path in self.accounts[sym]
+ except KeyError:
+ return False
+
+
+ def __iter__(self):
+ keys = list(self.uidx.syms())
+ keys.sort()
+ for k in keys:
+ for v in self.accounts[k]:
+ if self.iterval == None:
+ self.iterval = []
+ self.iterval.append(k + '/' + v)
+ return self
+
+
+ def __next__(self):
+ if len(self.iterval) == 0:
+ self.iverval = None
+ raise StopIteration()
+ v = self.iterval.pop(0)
+ return v
+
diff --git a/dummy/usawa/error.py b/dummy/usawa/error.py
@@ -12,3 +12,7 @@ class ValidateError(Exception):
class SocketError(Exception):
pass
+
+
+class AccountError(Exception):
+ pass