qtils.formatting module¶
Formatting module¶
This module contains formatting helpers.
-
qtils.formatting.PRETTY_FORMAT¶ Format string presets to be used for
PrettyObjectclass attribute__pretty_format__. Presets are accessible as attributes, for examplePRETTY_FORMAT.FULL.Available presets:
Name Example PRETTY_FORMAT.FULL<mypackage.mysubmodule.MyObject object at 0x000000000 answer=42, hello='world'>PRETTY_FORMAT.BRIEF<MyObject object at 0x000000000 answer=42, hello='world'>PRETTY_FORMAT.SHORT<MyObject 0x000000000 answer=42, hello='world'>PRETTY_FORMAT.MINIMAL<MyObject answer=42, hello='world'>
-
class
qtils.formatting.NA¶ Bases:
objectThis class represents the ValueNotAvailable state.
This constant is meant to be used when
Nonehas a meaningful value, and it can not be used to represent the state of a value being absent.Examples
NAis always represented in a short format:>>> str(NA) '??' >>> repr(NA) 'NA'
NAonly exsists in class form, no instance can be created from it.>>> NA == NA() True >>> NA is NA() True >>> bool(NA) False >>> NA == False False >>> NA == True False
Usage example:
>>> data_from_an_api = {'field1': 'foo', 'field2': None} >>> def is_field_present(field_name): ... return data_from_an_api.get(field_name, NA) is not NA >>> is_field_present('field1') True >>> is_field_present('field2') True >>> is_field_present('field3') False
-
class
qtils.formatting.PrettyObject¶ Bases:
objectObject with pretty self printing ability
Self-formatting is implemented by overriding the
object.__str__()method.Example
Simple example with printing fields of the object:
>>> class MyObject(PrettyObject): ... __pretty_fields__ = [ ... 'hello', ... 'answer', ... ] ... def __init__(self, hello, answer): ... self.hello = hello ... self.answer = answer >>> obj = MyObject('world', 42) >>> print(obj) <qtils.formatting.MyObject object at ... hello='world', answer=42>
This is works automatically for objects with
__slots__>>> class MySlotObject(PrettyObject): ... __slots__ = [ ... 'hello', ... 'answer', ... ] ... def __init__(self, hello, answer): ... self.hello = hello ... self.answer = answer >>> obj = MySlotObject('world', 42) >>> print(obj) <qtils.formatting.MySlotObject object at ... hello='world', answer=42>
Implementation Notes:
A class level format string is created from the
__pretty_format__and__pretty_fieldsclass attributes. This class-level format is saved in the__pretty_format_str__class attribute.Object formatting happens when
PrettyObject.__str__()is called, for example when the object instance is being printed. During this formattinggetattr()is used to get values for all the fields in__pretty_fields__. Then the class-level format string in__pretty_format_str__will be formatted to create the object’s pretty string.Note
Because of the getattr calls during formatting, printing a PrettyObject() can cause side-effects in case properties that are executing calls in their getter functions are referred in the
__pretty_fields__.Use the following class level attributes in descendant classes to configure the formatting:
-
__pretty_format__¶ Format string for the object to be used with
str.format(). Defaults toPRETTY_FORMAT.FULLwhich is:<{cls.__module__}.{cls.__name__} object at 0x{{__self_id__:02x}} {fields}>Values available for py:meth:str.format in the creation of the class-level format string:
cls: Class which is being formattedfields: Format placeholders of the individual fields
Values available for py:meth:str.format at object level formatting. These fields needs to be double escaped, for example:
{{__self_id__}}.__self__: Object instance being formatted__self_id__: id(self) of the object being formatted
Type: str
-
__pretty_fields__¶ Object attribute names to be included when printing the objects. Default to None. This attribute must be declared in descendant classes.
List should contain attribute names. Each attribute with be read using
getattr(self, name, NA), please consider side-effects when listing properties with complicated getter functions.Advanced string formatting defined PEP 3101 can be applied to each field.
Examples:
Field format string Description 'my_field'Format the field’s value with object.repr(), default behaviour.'my_field:>10,.2f'Format string to be 10 digits wide and align to left, use ,as thousand separator and use 2 digit precision.'my_field!s'Use str(my_field)to format field value. This will returnmy_field=fooinstead ofmy_field='foo'. Note the missing single quotes aroundfooType: list
Examples
Returning
NAfor non-existent fields:>>> class MyObject(PrettyObject): ... __pretty_fields__ = [ ... "non_existent", ... ] ... >>> obj = MyObject() >>> print(obj) <qtils.formatting.MyObject object at ... non_existent=NA>
Exceptions encountered during formatting are printed as the value for the field. Please note that this can lead to problems with fields expecting a
floatorintvalue. However, exceptions during attribute reading should not happen in the first place.>>> class MyObject(PrettyObject): ... __pretty_fields__ = [ ... "foo", ... ] ... @property ... def foo(self): ... return non_existent # this will raise an exception >>> obj = MyObject() >>> print(obj) <qtils.formatting.MyObject object at ... foo=NameError("name 'non_existent' is not defined")>
Descendants of PrettyObject subclasses are inheriting parents’ field configurations by default.
>>> class BaseClass(PrettyObject): ... __pretty_fields__ = [ ... "foo", ... ] ... def __init__(self, foo, bar): ... self.foo = foo ... self.bar = bar ... >>> print(BaseClass('hello', 'world')) <qtils.formatting.BaseClass object at ... foo='hello'> >>> >>> class SubClassOne(BaseClass): ... pass ... >>> print(SubClassOne('hello', 'world')) <qtils.formatting.SubClassOne object at ... foo='hello'>
Subclasses can overwrite inherited field configuration by defining a new field configuration
>>> class SubClassTwo(BaseClass): ... __pretty_fields__ = [ ... "bar", ... ] >>> print(SubClassTwo('hello', 'world')) <qtils.formatting.SubClassTwo object at ... bar='world'>
Or Subclasses can extend/alter parents field configuration using simple list operations.
>>> class SubClassThree(BaseClass): ... __pretty_fields__ = BaseClass.__pretty_fields__ + [ ... "bar", ... ] >>> print(SubClassThree('hello', 'world')) <qtils.formatting.SubClassThree object at ... foo='hello', bar='world'>
-
-
class
qtils.formatting.DATA_UNIT_SYSTEM¶ Bases:
enum.EnumData Unit Systems
-
BINARY¶ Use powers of 2 for magnitudes
-
METRIC¶ Use powers of 10 for magnitudes
-
BINARY= 0
-
METRIC= 1
-
-
class
qtils.formatting.DataSize¶ Bases:
intInteger object representing data size with two-way conversion ability.
It stores the data size in bytes as int. It can display the value in different units. By default it uses the most suitable unit automatically. This behaviour can be changed by calling the
DataSize.format()directly, or by changing the default unit in theDEFAULT_UNITclass attribute.This class supports different systems of units. It supports the
BINARYsystem in which a magnitude is2**10=1024bytes, and theMETRICsystem in which a magnitude is10**3=1000bytes. By default it uses theMETRICsystem. Read more about the topic in the Units of information Wikipedia article.Examples
Pretty printing data size values with automatic unit and precision detection:
>>> print(DataSize(123000)) 123 k >>> print(DataSize(123456000)) 123.5 M >>> print(DataSize(23*10**8)) 2.30 G >>> print(DataSize(1000**8)) 1.00 Y
Parsing data sizes from strings using the
METRICunit system (default behaviour):>>> DataSize('256') 256 >>> DataSize('1.45 megabytes') 1450000 >>> DataSize('23.3G') 23300000000 >>> DataSize('1 T') 1000000000000 >>> DataSize('1,123,456.789 MB') 1123456789000
Parsing data sizes using the
BINARYunit system:>>> DataSize('1.45 mebibytes') 1520435 >>> DataSize('23.3gib') 25018184499 >>> DataSize('1 T', system=DATA_UNIT_SYSTEM.BINARY) 1099511627776 >>> DataSize('1,123,456.789 MB', system=0) 1178029825982
Comparison of the
BINARYand theMETRICunit systems:>>> binary_1k = DataSize("1 KiB") >>> metric_1k = DataSize("1 kB") >>> binary_1k 1024 >>> metric_1k 1000 >>> binary_1k.format(system=DATA_UNIT_SYSTEM.BINARY) '1 K' >>> metric_1k.format(system=DATA_UNIT_SYSTEM.BINARY) '1000 b'
DataSizeworks as a regularint:>>> size = DataSize('1 M') + DataSize('500k') >>> size 1500000 >>> print(size) 1.5 M >>> size * 2.5 3750000 >>> print(size * 2.5) 3.8 M >>> size / 3 500000 >>> print(size - 500000) 1.0 M
Throws
ValueErrorexception if data can not be parsed:>>> DataSize('invalid size data') Traceback (most recent call last): ... ValueError: Invalid data size literal: 'invalid size data'
Formatting can be controlled and customized using the
DataSize.format()method, or by changing the defaults set by class attributes.-
DEFAULT_UNIT_SYSTEM¶ Binary or Metric system to use, defaults to
DATA_UNIT_SYSTEM.METRICType: DATA_UNIT_SYSTEM
-
DEFAULT_UNIT¶ Sets the default unit to use (see more in
DataSize.format()documentation.).Nonemeans automatic unit choice.Type: str
-
DEFAULT_UNIT_FORMAT¶ Sets the default unit format (see more in
DataSize.format()documentation.)Type: int
-
DEFAULT_NUMBER_FORMAT= '{:.{precision}f} {:}'
-
DEFAULT_UNIT= None
-
DEFAULT_UNIT_FORMAT= 0
-
DEFAULT_UNIT_SYSTEM= 1
-
format(unit: str = None, precision: int = None, unit_format: object = None, number_format: str = None, system: qtils.formatting.DATA_UNIT_SYSTEM = None)¶ Returns a formatted data size in str
Parameters: - unit (str,int) –
Unit to use, defaults to
None- If None, the most suitable unit based on the size will be choosen automatically.
- Accepts a string with one or more letter SI prefix. For example
'k'or'kilo','G'or'giga', etc. - Accepts an integer referring to the SI magnitude, with
0meaning bytes and8meaning yottabytes.
- precision (int) –
How many fraction digits to display after the integers. Defaults to
None- If
None, precision will be chosen automatically based on the unit: 0 for bytes, 1 for kbytes and 2 for everything else. - Accepts an integer with the desired precision.
Note: The precision has to be utilized in the number_format string, otherwise it will be ignored.
- If
- unit_format (int) –
Sets how verbose the displayed unit should be. Defaults to
None.None: Use value fromDataSize.DEFAULT_UNIT_FORMAT(default behaviour)0: Single letter units, for example:b,k,M, etc.1: Short units, for example:B,kB,MB, etc.2: Verbose units, for example:bytes,kilobytes,megabytes, etc.
- number_format (str) –
Python format string to be used to format data size. Defaults to
NoneNone: Use value fromDataSize.DEFAULT_NUMBER_FORMAT(default behaviour)Accepts any valid python format string.
- File size converted to the requested magnitude is supplied as the first positional argument
- Unit as string is supplied as the second positional argument.
- Precision is supplied as
precisionkeyword argument.
Default format in
DataSize.DEFAULT_NUMBER_FORMATis:{:.{precision}f} {:} - system (
DATA_UNIT_SYSTEM) – Unit system to use for formatting. Accepts integers or values from theDATA_UNIT_SYSTEMEnum. IfNoneit will use the default value fromDataSize.DEFAULT_UNIT_SYSTEM.
Examples
Displaying the same
DataSizewith different formatting>>> size = DataSize('1.7654321 G') >>> size.format() '1.77 G' >>> size.format(precision=4) '1.7654 G' >>> size.format(unit='m') '1765.4 M' >>> size.format(unit='m', precision=2) '1765.43 M' >>> size.format(unit_format=1) '1.77 GB' >>> size.format(unit_format=2) '1.77 gigabytes' >>> size.format(unit='m', precision=0, unit_format=2) '1765 megabytes' >>> size.format(unit="k", number_format="{:>20,.3f} {}") ' 1,765,432.100 k' >>> size.format(unit="k", number_format="{:>20,.3f} {}", unit_format=2) ' 1,765,432.100 kilobytes'
Displaying the same data size using the BINARY unit system:
>>> size = DataSize('1.7654321 G', system=0) >>> size.format(system=0) '1.77 G' >>> size.format(precision=4, system=0) '1.7654 G' >>> size.format(unit='m', system=0) '1807.8 M' >>> size.format(unit=2, system=0) '1807.8 M' >>> size.format(unit='m', precision=2, system=0) '1807.80 M' >>> size.format(unit_format=1, system=0) '1.77 GiB' >>> size.format(unit_format=2, system=0) '1.77 gibibytes' >>> size.format(unit='m', precision=0, unit_format=2, system=0) '1808 mebibytes' >>> size.format(unit="k", number_format="{:>20,.3f} {}", system=0) ' 1,851,189.729 K' >>> size.format(unit="k", number_format="{:>20,.3f} {}", unit_format=2, system=0) ' 1,851,189.729 kibibytes'
Raises AttributeError if invalid arguments are supplied.
>>> size.format(unit='l', system=0) Traceback (most recent call last): ... AttributeError: Unknown DataSize unit: 'l'
Changing the default formatting settings
>>> sizes = [ ... DataSize('208 k'), ... DataSize('1.5 M'), ... DataSize('542 M'), ... DataSize('1.6 G'), ... ] >>> for size in sizes: print(size) 208 k 1.5 M 542.0 M 1.60 G >>> DataSize.DEFAULT_UNIT_FORMAT = 2 >>> for size in sizes: print(size) 208 kilobytes 1.5 megabytes 542.0 megabytes 1.60 gigabytes >>> DataSize.DEFAULT_UNIT_FORMAT = 1 >>> DataSize.DEFAULT_UNIT = "m" >>> for size in sizes: print(size) 0.2 MB 1.5 MB 542.0 MB 1600.0 MB >>> DataSize.DEFAULT_NUMBER_FORMAT = "{:>10,.3f} {}" >>> for size in sizes: print(size) 0.208 MB 1.500 MB 542.000 MB 1,600.000 MB >>> DataSize.DEFAULT_UNIT_SYSTEM = DATA_UNIT_SYSTEM.BINARY >>> for size in sizes: print(size) 0.198 MiB 1.431 MiB 516.891 MiB 1,525.879 MiB
Resetting
DataSize.DEFAULT_*to built-in defaults>>> DataSize.DEFAULT_UNIT_SYSTEM = DATA_UNIT_SYSTEM.METRIC >>> DataSize.DEFAULT_UNIT = None >>> DataSize.DEFAULT_UNIT_FORMAT = 0 >>> DataSize.DEFAULT_NUMBER_FORMAT = "{:.{precision}f} {:}"
>>> print(DataSize(0)) 0 b
- unit (str,int) –
-