Mechanics#

Note

Prepare playground

pip install ipython izulu

ipython -i -c 'from izulu.root import *; from typing import *; from datetime import *'
  • inheritance from izulu.root.Error is required

class AmountError(Error):
    pass
  • optionally behaviour can be adjusted with __toggles__ (not recommended)

class AmountError(Error):
    __toggles__ = Toggles.DEFAULT ^ Toggles.FORBID_UNDECLARED_FIELDS
  • you should provide a template for the target error message with __template__

    class AmountError(Error):
        __template__ = "Data is invalid: {reason} (amount={amount})"
    
    print(AmountError(reason="negative amount", amount=-10.52))
    # [2024-01-23 19:16] Data is invalid: negative amount (amount=-10.52)
    
  • error instantiation requires data to format __template__

    • all data for __template__ fields must be provided

      class AmountError(Error):
          __template__ = "Data is invalid: {reason} (amount={amount})"
      
      print(AmountError(reason="amount can't be negative", amount=-10))
      # Data is invalid: amount can't be negative (amount=-10)
      
      AmountError()
      # TypeError: Missing arguments: 'reason', 'amount'
      AmountError(amount=-10)
      # TypeError: Missing arguments: 'reason'
      
    • only named arguments allowed: __init__() accepts only kwargs

      class AmountError(Error):
          __template__ = "Data is invalid: {reason} (amount={amount})"
      
      print(AmountError(reason="amount can't be negative", amount=-10))
      # Data is invalid: amount can't be negative (amount=-10)
      
      AmountError("amount can't be negative", -10)
      # TypeError: __init__() takes 1 positional argument but 3 were given
      AmountError("amount can't be negative", amount=-10)
      # TypeError: __init__() takes 1 positional argument but 2 were given
      
  • “class defaults” can be defined and used

    • “class defaults” must be type hinted with ClassVar annotation and provide static values

    • template “fields” may refer “class defaults”

class AmountError(Error):
    LIMIT: ClassVar[int] = 10_000
    __template__ = "Amount is too large: amount={amount} limit={LIMIT}"
    amount: int

print(AmountError(amount=10_500))
# Amount is too large: amount=10500 limit=10000
  • “instance attributes” are populated from relevant kwargs

class AmountError(Error):
    amount: int

print(AmountError(amount=-10).amount)
# -10
  • instance and class attribute types from annotations are not validated or enforced (izulu uses type hints just for attribute discovery and only ClassVar marker is processed for instance/class segregation)

class AmountError(Error):
    amount: int

print(AmountError(amount="lots of money").amount)
# lots of money
  • static “instance defaults” can be provided regularly with instance type hints and static values

class AmountError(Error):
    amount: int = 500

print(AmountError().amount)
# 500
  • dynamic “instance defaults” are also supported

    • they must be type hinted and have special value

    • value must be a callable object wrapped with factory helper

    • factory provides 2 modes depending on value of the self flag:

      • self=False (default): callable accepting no arguments

        class AmountError(Error):
            ts: datetime = factory(datetime.now)
        
        print(AmountError().ts)
        # 2024-01-23 23:18:22.019963
        
      • self=True: provide callable accepting single argument (error instance)

        class AmountError(Error):
            LIMIT = 10_000
            amount: int
            overflow: int = factory(lambda self: self.amount - self.LIMIT, self=True)
        
        print(AmountError(amount=10_500).overflow)
        # 500
        
  • “instance defaults” and “instance attributes” may be referred in __template__

class AmountError(Error):
    __template__ = "[{ts:%Y-%m-%d %H:%M}] Amount is too large: {amount}"
    amount: int
    ts: datetime = factory(datetime.now)

print(AmountError(amount=10_500))
# [2024-01-23 23:21] Amount is too large: 10500
  • Pause and sum up: defaults, attributes and template

class AmountError(Error):
    LIMIT: ClassVar[int] = 10_000
    __template__ = "[{ts:%Y-%m-%d %H:%M}] Amount is too large: amount={amount} limit={LIMIT} overflow={overflow}"
    amount: int
    overflow: int = factory(lambda self: self.amount - self.LIMIT, self=True)
    ts: datetime = factory(datetime.now)

err = AmountError(amount=15_000)

print(err.amount)
# 15000
print(err.LIMIT)
# 10000
print(err.overflow)
# 5000
print(err.ts)
# 2024-01-23 23:21:26

print(err)
# [2024-01-23 23:21] Amount is too large: amount=15000 limit=10000 overflow=5000
  • kwargs overlap “instance defaults”

class AmountError(Error):
    LIMIT: ClassVar[int] = 10_000
    __template__ = "[{ts:%Y-%m-%d %H:%M}] Amount is too large: amount={amount} limit={LIMIT} overflow={overflow}"
    amount: int = 15_000
    overflow: int = factory(lambda self: self.amount - self.LIMIT, self=True)
    ts: datetime = factory(datetime.now)

print(AmountError())
# [2024-01-23 23:21] Amount is too large: amount=15000 limit=10000 overflow=5000

print(AmountError(amount=10_333, overflow=42, ts=datetime(1900, 1, 1)))
# [2024-01-23 23:21] Amount is too large: amount=10333 limit=10000 overflow=42
  • izulu provides flexibility for templates, fields, attributes and defaults

    • “defaults” are not required to be __template__ “fields”

      class AmountError(Error):
          LIMIT: ClassVar[int] = 10_000
          __template__ = "Amount is too large"
      
      print(AmountError().LIMIT)
      # 10000
      print(AmountError())
      # Amount is too large
      
    • there can be hints for attributes not present in error message template

      class AmountError(Error):
          __template__ = "Amount is too large"
          amount: int
      
      print(AmountError(amount=500).amount)
      # 500
      print(AmountError(amount=500))
      # Amount is too large
      
    • “fields” don’t have to be hinted as instance attributes

      class AmountError(Error):
          __template__ = "Amount is too large: {amount}"
      
      print(AmountError(amount=500))
      # Amount is too large: 500
      print(AmountError(amount=500).amount)
      # AttributeError: 'AmountError' object has no attribute 'amount'