Source code for aries_cloudagent.messaging.util

"""Utils for messages."""

import logging
import re

from datetime import datetime, timedelta, timezone
from hashlib import sha256
from math import floor
from typing import Any, Union


LOGGER = logging.getLogger(__name__)
I32_BOUND = 2**31


[docs]def datetime_to_str(dt: Union[str, datetime, None]) -> Union[str, None]: """Convert a datetime object to an indy-standard datetime string. Args: dt: May be a string or datetime to allow automatic conversion """ if isinstance(dt, datetime): dt = dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00", "Z") return dt
[docs]def str_to_datetime(dt: Union[str, datetime]) -> datetime: """Convert an indy-standard datetime string to a datetime. Using a fairly lax regex pattern to match slightly different formats. In Python 3.7 datetime.fromisoformat might be used. Args: dt: May be a string or datetime to allow automatic conversion """ if isinstance(dt, str): match = re.match( r"^(\d{4})-(\d\d)-(\d\d)[T ](\d\d):(\d\d)" r"(?:\:(\d\d(?:\.\d+)?))?([+-]\d\d:?\d\d|Z|)$", dt, ) if not match: raise ValueError("String does not match expected time format") year, month, day = match[1], match[2], match[3] hour, minute, second = match[4], match[5], match[6] tz = match[7] if second: flt_second = float(second) second = floor(flt_second) microsecond = round((flt_second - second) * 1_000_000) else: second = 0 microsecond = 0 result = datetime( int(year), int(month), int(day), int(hour), int(minute), int(second), microsecond, timezone.utc, ) if tz != "Z" and tz != "": tz_sgn = int(tz[0] + "1") tz_hours = int(tz[1:3]) tz_mins = int(tz[-2:]) if tz_hours or tz_mins: result = result - timedelta(minutes=tz_sgn * (tz_hours * 60 + tz_mins)) return result return dt
[docs]def str_to_epoch(dt: Union[str, datetime]) -> int: """Convert an indy-standard datetime string to epoch seconds. Args: dt: May be a string or datetime to allow automatic conversion """ return int(str_to_datetime(dt).timestamp())
[docs]def epoch_to_str(epoch: int) -> str: """Convert epoch seconds to indy-standard datetime string. Args: epoch: epoch seconds """ return datetime_to_str(datetime.fromtimestamp(epoch, tz=timezone.utc))
[docs]def datetime_now() -> datetime: """Timestamp in UTC.""" return datetime.now(tz=timezone.utc)
[docs]def time_now() -> str: """Timestamp in ISO format.""" return datetime_to_str(datetime_now())
[docs]def encode(orig: Any) -> str: """Encode a credential value as an int. Encode credential attribute value, purely stringifying any int32 and leaving numeric int32 strings alone, but mapping any other input to a stringified 256-bit (but not 32-bit) integer. Predicates in indy-sdk operate on int32 values properly only when their encoded values match their raw values. Args: orig: original value to encode Returns: encoded value """ if isinstance(orig, int) and -I32_BOUND <= orig < I32_BOUND: return str(int(orig)) # python bools are ints try: i32orig = int(str(orig)) # don't encode floats as ints if -I32_BOUND <= i32orig < I32_BOUND: return str(i32orig) except (ValueError, TypeError): pass rv = int.from_bytes(sha256(str(orig).encode()).digest(), "big") return str(rv)
[docs]def canon(raw_attr_name: str) -> str: """Canonicalize input attribute name for indy proofs and credential offers. Args: raw_attr_name: raw attribute name Returns: canonicalized attribute name """ if raw_attr_name: # do not dereference None, and "" is already canonical return raw_attr_name.replace(" ", "").lower() return raw_attr_name