pycroft.helpers
This package contains certain parts of the business logic which do not depend on our model.
This can include logic concerning ip addresses, intervals, internationalization, etc.
-
diff_month(d1: date, d2: date) → int[source]
Calculate the difference in months ignoring the days
If d1 > d2, the result is positive.
-
last_day_of_month(d: date) → date[source]
pycroft.helpers.errorcode
-
digits(n: int, base: int = 10) → Iterator[int][source]
Generate all digits of number in a given base starting with the least
significant digit.
- Parameters
n – An integral number
base – Defaults to 10.
-
class ErrorCode[source]
Error detection code abstract base class.
Subclasses must implement at least the calculate method.
-
abstract calculate(number: int) → int[source]
-
is_valid(number: int, code: int) → bool[source]
Validates a (number, code) pair by calculating the code and comparing.
-
class DigitSumModNCode(mod: int)[source]
Digit sum mod-n error detection code.
Does not catch digit transposition errors.
-
calculate(number: int) → int[source]
-
class Mod97Code[source]
Expand a number on the right so that its mod-97 is 1.
This is a more advanced error detection code based on the IBAN check digits
scheme.
-
calculate(number: int) → int[source]
-
is_valid(number: int, code: int) → bool[source]
Validates a (number, code) pair by calculating the code and comparing.
-
get_locale() → Locale[source]
-
set_translation_lookup(lookup_func: Callable[[], Translations]) → None[source]
-
class Message(domain: str | None = None)[source]
-
classmethod from_json(json_string: str) → Message[source]
-
domain
-
args: Iterable[None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
kwargs: dict[str, None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
to_json() → str[source]
-
format(*args: None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval, **kwargs: None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval) → Self[source]
-
localize(options: dict[type, dict[str, Any]] = None) → str[source]
-
localized(json_string: str, options: dict[type, dict[str, Any]] | None = None) → str[source]
-
gettext(message: str) → str[source]
-
dngettext(domain: str, singular: str, plural: str, n: int) → str[source]
-
ngettext(singular: str, plural: str, n: int) → str[source]
-
dgettext(domain: str, message: str) → str[source]
-
deferred_gettext(message) → SimpleMessage[source]
-
deferred_dngettext(domain, singular, plural, n) → NumericalMessage[source]
-
deferred_ngettext(singular, plural, n) → NumericalMessage[source]
-
deferred_dgettext(domain: str, message: str) → SimpleMessage[source]
pycroft.helpers.i18n.babel
-
get_locale() → Locale[source]
-
get_translations() → Translations[source]
-
set_locale_lookup(lookup_func: Callable[[], Locale]) → None[source]
-
set_translation_lookup(lookup_func: Callable[[], Translations]) → None[source]
-
gettext(message: str) → str[source]
-
dgettext(domain: str, message: str) → str[source]
-
ngettext(singular: str, plural: str, n: int) → str[source]
-
dngettext(domain: str, singular: str, plural: str, n: int) → str[source]
pycroft.helpers.i18n.deferred
-
deferred_gettext(message) → SimpleMessage[source]
-
deferred_dgettext(domain: str, message: str) → SimpleMessage[source]
-
deferred_ngettext(singular, plural, n) → NumericalMessage[source]
-
deferred_dngettext(domain, singular, plural, n) → NumericalMessage[source]
-
class Formattable(*args, **kwargs)[source]
-
class Formatter(*args, **kwargs)[source]
-
format_number(n, insert_commas=True)[source]
-
format_decimal(d, format=None)[source]
-
format_currency(money: Money, format=None)[source]
-
format_date(d, format='medium')[source]
-
format_datetime(d, format='medium', tzinfo=None)[source]
-
format_time(t, format='medium', tzinfo=None)[source]
-
format_timedelta(delta, granularity='second', threshold=0.85, add_direction=False, format='medium')[source]
-
format_bool(v)[source]
-
format_none(n)[source]
-
format_interval(interval: Interval, **options: dict[str, Any])[source]
-
identity(x)[source]
-
format_param(p, options: dict[type, dict[str, Any]]) → Formattable[source]
pycroft.helpers.i18n.message
-
class Message(domain: str | None = None)[source]
-
classmethod from_json(json_string: str) → Message[source]
-
domain
-
args: Iterable[None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
kwargs: dict[str, None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
to_json() → str[source]
-
format(*args: None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval, **kwargs: None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval) → Self[source]
-
localize(options: dict[type, dict[str, Any]] = None) → str[source]
-
class ErroneousMessage(text)[source]
-
domain
-
args: Iterable[None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
kwargs: dict[str, None | bool | str | int | float | Decimal | Money | date | datetime | time | timedelta | Interval]
-
class SimpleMessage(message, domain=None)[source]
-
message
-
class NumericalMessage(singular, plural, n, domain=None)[source]
-
singular
-
plural
-
n
pycroft.helpers.i18n.options
pycroft.helpers.i18n.serde
-
identity(x)[source]
-
deserialize_money(v)[source]
-
serialize_interval(interval: Interval) → dict[str, Any][source]
-
deserialize_interval(value: dict[str, Any]) → Interval[source]
-
serialize_param(param)[source]
-
deserialize_param(param)[source]
pycroft.helpers.i18n.types
-
namedtuple Money(value, currency)
Money(value, currency)
- Fields
value – Alias for field number 0
currency – Alias for field number 1
pycroft.helpers.i18n.utils
-
qualified_typename(type_: type) → str[source]
-
PositiveInfinity = pycroft.helpers.interval.PositiveInfinity
+∞
-
class PositiveInfinityType[source]
dummy placeholder type to work around mypy limitations
-
NegativeInfinity = pycroft.helpers.interval.NegativeInfinity
-∞
-
class NegativeInfinityType[source]
dummy placeholder type to work around mypy limitations
-
class Bound(value: TWithInfinity, is_closed: bool)[source]
Represents a bound of an interval, i.e. value + closedness.
The value is either:
-
property value: TWithInfinity
-
property closed: bool
-
property pg_identifier: str
-
property unbounded: bool
-
class Interval(lower_bound: Bound[T], upper_bound: Bound[T])[source]
Represents an bounded or unbounded interval.
Bounds may be any comparable types. If lengths should be calculated, bounds
must also implement subtraction and the return type of the subtraction must
implement addition.
If a bound is None it means unbound, so a None begin is conceptually
negative infinity and a None end positive infinity.
This class is immutable.
Intervals are ordered lexicographically according to begin and end per
default.
It implements the relations from Allen’s interval algebra, i.e. before,
during, overlaps, starts, finishes and equals.
Todo
replace by generic namedtuple once we’re on
Python 3.11
for nicer typing behavior
-
classmethod from_explicit_data(lower: T | None, lower_closed: bool, upper: T | None, upper_closed: bool) → t.Self[source]
-
property lower_bound: Bound[T]
- Returns
The lower bound object
-
property upper_bound: Bound[T]
- Returns
The upper bound object
-
property begin: T | None
-
property end: T | None
-
property unbounded: bool
-
property empty: bool
Tests whether the interval is empty
-
property length: Any
Compute the interval’s length
- Returns
None
if the interval is unbounded else end - begin
-
strictly_before(other: Interval[T]) → bool[source]
Tests if this interval is strictly before another interval.
An interval is strictly before another if its end is less than the
other’s begin.
Note
This is not the same as self < other
.
-
before(other: Interval[T]) → bool[source]
Tests if this interval is before another interval.
An interval is before another if its end is less than or equal to the
other’s begin.
Note
This is not the same as self <= other
.
-
strictly_after(other: Interval[T]) → bool[source]
Tests if this interval is strictly after another interval.
An interval is strictly after another if its begin is greater than the
other’s end.
Note
This is not the same as self > other
.
-
after(other: Interval[T]) → bool[source]
Tests if this interval is after another interval.
An interval is after another if its begin is greater than or equal to
the other’s end.
Note
This is not the same as self >= other
.
-
meets(other: Interval[T]) → bool[source]
Tests if this interval meets another interval.
Two intervals meet if the end of the first interval is equal to the
begin of the second interval and at least one of the bounds is closed.
This means that the intervals do not necessarily have to overlap.
-
strictly_overlaps(other: Interval[T]) → bool[source]
Tests if this interval overlaps strictly with another interval.
Two intervals overlap if each begin is strictly before the other’s end.
This means that the intervals may not be equal.
-
overlaps(other: Interval[T]) → bool[source]
Tests if this interval overlaps with another interval.
Two intervals overlap if each begin is before the other’s end.
-
strictly_during(other: Interval[T]) → bool[source]
Tests if this interval is strictly during (strictly contained in)
another interval.
An interval is strictly during another if its begin is greater than
the other’s and its end is less than the other’s.
-
during(other: Interval[T]) → bool[source]
Tests if this interval is during (contained in) another interval.
An interval is during another if its begin is greather than or equal to
the other’s and its end is less than or equal to the other’s.
-
strictly_contains(other: Interval[T]) → bool[source]
Tests if this interval strictly contains another interval.
An interval strictly contains another if its begin is less than the
other’s and its end is greater than the other’s.
-
contains(other: Interval[T]) → bool[source]
Tests if this interval contains another interval.
An interval contains another if its begin is less than or equal to the
other’s and its end greater than or equal to the other’s.
-
starts(other: Interval[T]) → bool[source]
Tests whether both intervals start at the same value.
-
finishes(other: Interval[T]) → bool[source]
Tests whether both intervals finish at the same value.
-
intersect(other: Interval[T]) → Interval[T] | None[source]
Intersect this interval with another one.
- Returns
None
if the intervals do not overlap else the intersection
-
join(other: Interval[T]) → Interval[T] | None[source]
Join this interval with an interval that overlaps or meets this one.
- Returns
None if the intervals do not overlap or meet else the union
-
property closure: Interval[T]
Return a closed variant of this interval
-
map(f: t.Callable[[T], U]) → Interval[U][source]
Apply a monotonic function to the interval’s bounds.
For instance, the following would turn a datetime
interval into a
date
interval by mapping dt↦dt.date()
:
>>> from datetime import datetime, date
>>> i = starting_from(datetime.fromisoformat("2020-01-01T00:00:00Z"))
>>> i_dates = i.map(lambda dt: dt.date())
>>> assert i_dates == starting_from(date(2020, 1, 1))
Note
This turns Interval
into a functor from the category of
linearly ordered types (i.e., SupportsAllComparisons
) to the category of types.
In other words, for every map \(f:T→U\),
we get a corresponding map \(Ff: \mathrm{Interval}[T]→\mathrm{Interval}[U]\)
in a way which
preserves identity, i.e. i.map(lambda x: x) == i
satisfies \(F(f\circ g) = Ff \circ Fg\),
i.e. i.map(f).map(g) == i.map(lambda x: g(f(x))
Hint
monotonicity is necessary because otherwise there is some pair of values
\((x, y)\) such that the values \((f(x), f(y))\) are invalid
as bounds for an interval.
- Parameters
f – A monotonic function (that is, a function satisfying \(x≤y⇒f(x)≤f(y)\)
for all \(x, y: T\))
- Returns
A new interval with f applied to its bounds
-
closed(begin: T | None, end: T | None) → Interval[source]
Create a closed interval.
- Returns
closed interval [begin, end]
-
closedopen(begin: T | None, end: T | None) → Interval[source]
Create a left-closed/right-open interval.
- Returns
left-closed/right-open interval [begin, end)
-
starting_from(when: T) → Interval[source]
Alias for closedopen(when, None)
- Returns
[when, ∞)
-
openclosed(begin: T | None, end: T | None) → Interval[source]
Create a left-open/right-closed interval.
- Returns
left-open/right-closed interval (begin, end]
-
open(begin: T | None, end: T | None) → Interval[source]
Create an open interval.
- Returns
open interval (begin, end)
-
single(point: T) → Interval[source]
Create an interval containing only a single point.
-
empty(point: T) → Interval[source]
Create an empty interval positioned at the given point.
It may seem a bit confusing that an empty interval has a location, but it
eases the implementation of an interval a lot, as the empty interval
does not need to be handled specially.
-
UnboundedInterval = ((pycroft.helpers.interval.NegativeInfinity, False), (pycroft.helpers.interval.PositiveInfinity, False))
-
class IntervalSet(intervals: pycroft.helpers.interval.IntervalSetSource = None)[source]
-
property length
Compute the total length of the interval set, i.e. the sum of all
interval lengths.
The length is None if the set contains an unbounded interval.
Note: This is not the same as len(self), which is the number of
non-overlapping intervals.
-
complement()[source]
-
union(other: IntervalSetSource) → IntervalSet[T][source]
-
intersect(other: IntervalSetSource) → IntervalSet[T][source]
-
difference(other: IntervalSetSource) → IntervalSet[T][source]
-
mac_regex = re.compile('\n # MAC-Addresses are in big endian, hence we rightmost is the highest\n # byte.\n\n # Begin of string:\n \\A\n # First, most significant byte:\n (?P<byte1>(?:[a-fA-F0-9]{2}))\n , re.IGNORECASE|re.VERBOSE)
Regular expression object for matching MAC addresses in different formats.
A valid MAC address is a sequence of 6 bytes coded as hexadecimal digits
separated by a symbol after either one, two or three bytes. It is also possible
that there is no separating symbol.
The following examples all encode the same MAC address:
001122334455
00-11-22-33-44-55
00:11:22:33:44:55
0011.2233.4455
001122-334455
After a successful match, the individual bytes bytes, as well as the
separator symbols can be accessed using the following symbolic group names
(see groupdict()
):
- byte1, byte2, byte3, byte4, byte5, byte6
The n-th byte
- sep1, sep2, sep3
The one, two or three byte separator char or None
-
ip_regex = re.compile('^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$')
regex matching (not necessarily valid) ip addresses
-
port_name_sort_key(port_name: str) → int[source]
-
reverse_pointer(ip_address: IPAddress) → str[source]
-
get_interface_manufacturer(mac: str) → str | None[source]
-
generate_password(length: int) → str[source]
Generate a password of a certain length.
The password is generated as length
independent choices
of a certain charset. The charset does not include ambiguous
characters like l
, 1
, 0
and O
.
- Param
length
-
hash_password(plaintext_passwd: str) → str[source]
Generate a RFC 2307 complaint hash from given plaintext.
The passlib CryptContext is configured to generate the very secure
ldap_sha512_crypt hashes (a crypt extension available since glibc 2.7).
-
cleartext_password(plaintext_passwd: str) → str[source]
Generate a RFC 2307 complaint hash from given plaintext.
The passlib CryptContext is configured to generate the very secure
ldap_sha512_crypt hashes (a crypt extension available since glibc 2.7).
-
verify_password(plaintext_password: str, hash: str) → bool[source]
Verifies a plain password string against a given password hash.
It uses a crypt_context to verify RFC 2307 hashes.
-
login_hash(login: str) → bytes[source]
Hashes a login with sha512, as is done in the User.login_hash generated column.
-
generate_random_str(length: int) → str[source]
Generates an aplhanumeric random string
-
class TimeTz
A time with timezone information
alias of time
-
class DateTimeTz
A datetime with timezone information
alias of datetime
-
class DateTimeNoTz
A datetime without timezone information
alias of datetime
-
time_min() → TimeTz[source]
-
time_max() → TimeTz[source]
-
datetime_min() → DateTimeTz[source]
-
datetime_max() → DateTimeTz[source]
-
ensure_tz(d: datetime) → DateTimeTz[source]
-
with_min_time(d: date) → DateTimeTz[source]
Return the datetime corresponding to 00:00 UTC at the given date.
-
with_max_time(d: date) → DateTimeTz[source]
-
safe_combine(d: date, t: TimeTz) → DateTimeTz[source]
-
ensure_tzinfo(t: time) → TimeTz[source]
-
combine_ensure_tzinfo(d: date, t: time) → DateTimeTz[source]
-
combine_or_midnight(d: date, t: time | None) → DateTimeTz[source]