Lino utilities

We have lino.core.utils and lino.utils. The latter is theoretically for things that don't require Django.

Import and setup execution context for demonstration purposes.

>>> from lino import startup
>>> startup('lino.projects.std.settings_test')
>>> from lino.utils import *

Nested dictionaries

Using AttrDict for managing nested dictionary like objects:

>>> a = AttrDict()
>>> a.define('foo', 1)
>>> a.define('bar', 'baz', 2)
>>> a == {"bar": {"baz": 2}, "foo": 1}
True
>>> print(a.foo)
1
>>> print(a.bar.baz)
2
>>> print(a.resolve('bar.baz'))
2
>>> print(a.bar)
{'baz': 2}

Inline If

Given three arguments to the iif(), where the first argument is a condition, returns the second argument if the condition holds otherwise returns the second argument.

Examples:

>>> print("Hello, %s world!" % iif(1+1==2, "real", "imaginary"))
Hello, real world!
>>> iif(True, "true")
'true'
>>> iif(False, "true")

Join words

Usage example of the function join_words():

>>> print(join_words('This','is','a','test'))
This is a test
>>> print(join_words('This','is','','another','test'))
This is another test
>>> print(join_words(None, None, None, 'Third', 'test'))
Third test

Making summaries

The SumCollector class is used for managing sums of values over some attribute.

Usage examples:

>>> sc = SumCollector()
>>> sc.collect("a", 12)
>>> sc.collect("b", 23)
>>> sc.collect("a", 34)
>>> sc
OrderedDict([('a', 46), ('b', 23)])
>>> sc = SumCollector()
>>> sc.collect("a", 12)
>>> sc.collect("a", None)
>>> sc.collect("a", 5)
>>> sc.a
17
>>> from lino.utils.quantities import Duration
>>> sc = SumCollector()
>>> sc.collect("a", Duration("0:30"))
>>> sc.collect("a", Duration("0:35"))
>>> sc.collect("b", Duration("0:00"))
>>> sc.a
Duration('1:05')
>>> sc.b
Duration('0:00')

Working with Chooser

Following examples illustrates the usages of re.pattern (regular expression patterns) choosers.GFK_HACK to extract useful information about a lino.utils.choosers choice from a javascript action tag created by lino:

>>> import json
>>> from lino.utils.choosers import GFK_HACK
>>> s = ('<a href="javascript:Lino.pcsw.Clients.detail.run(' +
... 'null,{ &quot;record_id&quot;: 116 })">BASTIAENSEN Laurent (116)</a>')
>>> print(json.dumps(GFK_HACK.match(s).groups()))
["pcsw.Clients", "116"]
>>> s = ('<a href="javascript:Lino.cal.Guests.detail.run(' +
... 'null,{ &quot;record_id&quot;: 6 })">Gast #6 ("Termin #51")</a>')
>>> print(json.dumps(GFK_HACK.match(s).groups()))
["cal.Guests", "6"]

Analyzing a python package

lino.utils.code.codefiles(module_name)

Yield a list of the source files of the specified module or package.

This inspects the file system and yields all source files found, including those in subdirectories. It does not import them all.

lino.utils.code.codetime(*packages)

Return the modification time of the youngest source code in the specified packages.

Used e.g. by lino.modlib.extjs to avoid generating .js files if not necessary.

Inspired by the code_changed() function in django.utils.autoreload.

lino.utils.code.analyze_rst(*packages)

Return a statical description of the given packages in reSTructuredText.

>>> from lino.utils.code import analyze_rst
>>> print(analyze_rst('lino', 'lino_xl', 'lino_noi'))
========== ============ =========== =============== =============
 name       code lines   doc lines   comment lines   total lines
---------- ------------ ----------- --------------- -------------
 lino       38.7k        21.1k       9.1k            84.0k
 lino_xl    41.6k        13.5k       9.9k            77.0k
 lino_noi   1.0k         0.4k        0.7k            2.6k
 total      81.3k        34.9k       19.7k           163.7k
========== ============ =========== =============== =============

Using the Cycler

Usage examples of the cycler.Cycler class, when instantiating, it takes an iterable or some arbitrary values (while latter, the cycler.Cycler converts them into an iterable) then iterates over them in loops:

>>> def myfunc():
...     yield "a"
...     yield "b"
...     yield "c"
>>> c = Cycler(myfunc())
>>> s = ""
>>> for i in range(10):
...     s += c.pop()
>>> print (s)
abcabcabca

An empty Cycler or a Cycler on an empty list will endlessly pop None values:

>>> c = Cycler()
>>> print (c.pop(), c.pop(), c.pop())
None None None
>>> c = Cycler([])
>>> print (c.pop(), c.pop(), c.pop())
None None None
>>> c = Cycler(None)
>>> print (c.pop(), c.pop(), c.pop())
None None None

Using Counter in jinja

Setting up a jinja environment:

You can add the Counter class either to your local context or to the global namespace.

>>> from jinja2 import Environment
>>> from lino.utils.jinja import *
>>> env = Environment()
>>> env.globals.update(Counter=Counter)

Basic usage in a template:

Using the Counter in your template is easy: You instantiate a template variable of type Counter, and then call that counter each time you want a new number. For example:

>>> s = """
... {%- set art = Counter() -%}
... Easy as {{art()}}, {{art()}} and {{art()}}!
... """

Here is how this template will render :

>>> print(env.from_string(s).render())
Easy as 1, 2 and 3!

Counter parameters:

When defining your counter, you can set optional parameters.

>>> s = """
... {%- set art = Counter(start=17, step=2) -%}
... A funny counter: {{art()}}, {{art()}} and {{art()}}!
... """
>>> print(env.from_string(s).render())
A funny counter: 19, 21 and 23!

Resetting a counter:

When calling your counter, you can pass optional parameters. One of them is value, which you can use to restart numbering, or to start numbering at some arbitrary place:

>>> s = """
... {%- set art = Counter() -%}
... First use: {{art()}}, {{art()}} and {{art()}}
... Reset: {{art(value=1)}}, {{art()}} and {{art()}}
... Arbitrary start: {{art(value=10)}}, {{art()}} and {{art()}}
... """
>>> print(env.from_string(s).render())
First use: 1, 2 and 3
Reset: 1, 2 and 3
Arbitrary start: 10, 11 and 12

Nested counters:

Counters can have another counter as parent. When a parent increases, all children are automatically reset to their start value.

>>> s = """
... {%- set art = Counter() -%}
... {%- set par = Counter(art) -%}
... = Article {{art()}}
... == # {{par()}}
... == # {{par()}}
... = Article {{art()}}
... == # {{par()}}
... == # {{par()}}
... == # {{par()}}
... Article {{art()}}.
... == # {{par()}}
... """
>>> print(env.from_string(s).render())
= Article 1
== # 1
== # 2
= Article 2
== # 1
== # 2
== # 3
Article 3.
== # 1

Generating JavaScript from python

Some examples of generating JavaScript from python leveraging the lino.utils.jsgen:

>>> from lino.utils.jsgen import *
>>> class TextField(Component):
...    declare_type = DECLARE_VAR
>>> class Panel(Component):
...    declare_type = DECLARE_VAR
>>> fld1 = TextField(fieldLabel="Field 1", name='fld1', xtype='textfield')
>>> fld2 = TextField(fieldLabel="Field 2", name='fld2', xtype='textfield')
>>> fld3 = TextField(fieldLabel="Field 3", name='fld3', xtype='textfield')
>>> p1 = Panel(title="Panel",name='p1', xtype='panel', items=[fld2, fld3])
>>> main = Component(title="Main", name='main', xtype='form', items=[fld1, p1])
>>> d = dict(main=main, wc=[1, 2, 3])
>>> for ln in declare_vars(d):
...   print(ln)
var fld11 = { "fieldLabel": "Field 1", "xtype": "textfield" };
var fld22 = { "fieldLabel": "Field 2", "xtype": "textfield" };
var fld33 = { "fieldLabel": "Field 3", "xtype": "textfield" };
var p14 = { "items": [ fld22, fld33 ], "title": "Panel", "xtype": "panel" };
>>> print(py2js(d))
{ "main": { "items": [ fld11, p14 ], "title": "Main", "xtype": "form" }, "wc": [ 1, 2, 3 ] }

Checking shell command output

The lino.utils.mdbtools provides a function mdbtools.check_output() either from the python package subprocess or it's own defined function (in case check_output is not available in subprocess).

Here are some examples of using check_output():

>>> from lino.utils.mdbtools import check_output, STDOUT
>>> check_output(["ls", "-l", "/dev/null"])
... 
b'crw-rw-rw- 1 root root 1, ... /dev/null\n'

The stdout argument is not allowed as it is used internally. To capture standard error in the result, use stderr=STDOUT.

>>> check_output(["/bin/sh", "-c",
...               "ls -l non_existent_file ; exit 0"],
...              stderr=STDOUT)
b"ls: cannot access 'non_existent_file': No such file or directory\n"

Reading .ods files

The following code reads a file https://gitlab.com/lino-framework/lino/blob/master/odsreader_sample.ods and prints a line of text for each row of data leveraging the class odsreader.OdsReader:

>>> from lino.utils.odsreader import OdsReader, srcpath
>>> class Sample(OdsReader):
...     filename = srcpath('odsreader_sample.ods')
...     headers = ["N°", "Prénom", "Last name", "Country", "City", "Language"]
...     column_names = 'no first_name last_name country city language'.split()
...
>>> for row in Sample().rows():
...     print( "%(first_name)s %(last_name)s from %(city)s" % row)
Rudi Rutté from Eupen
Romain Raffault from Liège
Rando Roosi from Tallinn
Rik Rozenbos from Antwerpen
Robin Rood from London

(Note: these are fictive person names from lino.modlib.users.fixtures.demo).

Working with ranges

The following examples demonstrates the usage of some functions provided by the lino.utils.ranges module.

Using the ranges.constrain() function to constrain a range into some given value:

>>> from lino.utils.ranges import constrain, encompass, overlap
>>> constrain(-1, 2, 5)
2
>>> constrain(1, 2, 5)
2
>>> constrain(0, 2, 5)
2
>>> constrain(2, 2, 5)
2
>>> constrain(3, 2, 5)
3
>>> constrain(5, 2, 5)
5
>>> constrain(6, 2, 5)
5
>>> constrain(10, 2, 5)
5

Using the ranges.encompass() function to check if a given ranges encompasses another:

>>> encompass((1, 4), (2, 3))
True
>>> encompass((1, 3), (2, 4))
False
>>> encompass((None, None), (1, 4))
True
>>> encompass((1, None), (1, 4))
True
>>> encompass((2, None), (1, None))
False
>>> encompass((1, None), (2, None))
True
>>> encompass((1, 4), (1, 4))
True
>>> encompass((1, 2), (1, None))
False

Using the ranges.overlap() function to check if two ranges overlap eachother:

>>> overlap(1,2,3,4)
False
>>> overlap(3,4,1,2)
False
>>> overlap(1,3,2,4)
True
>>> overlap(2,4,1,3)
True
>>> overlap(1,4,2,3)
True
>>> overlap(2,3,1,4)
True
>>> overlap(1,None,3,4)
True
>>> overlap(3,4,1,None)
True
>>> overlap(1,2,3,None)
False
>>> overlap(3,None,1,2)
False
>>> overlap(None,2,3,None)
False
>>> overlap(3,None,None,2)
False
>>> overlap(1,3,2,None)
True

Touching but non-overlapping examples:

>>> overlap(1,2,2,3)
False
>>> overlap(2,3,1,2)
False

Demo Names

Using the lino.utils.demonames.bel:

The first five Belgians:

>>> from lino.utils.demonames.bel import *
>>> for i in range(5):
...     print(LAST_NAMES_BELGIUM.pop())
Adam
Adami
Adriaen
Adriaensen
Aelter
>>> from lino.utils.demonames import LAST_NAMES_RUSSIA

Next comes a group of five Russians:

>>> for i in range(5):
...     print(LAST_NAMES_RUSSIA.pop())
Abezgauz
Aleksandrov
Altukhov
Alvang
Ankundinov

Or here is a mixture of nationalities, for each Belgian comes one foreigner:

>>> from lino.utils.demonames import LAST_NAMES_MUSLIM
>>> LAST_NAMES = Cycler(LAST_NAMES_BELGIUM,
...     LAST_NAMES_RUSSIA, LAST_NAMES_BELGIUM, LAST_NAMES_MUSLIM)
>>> for i in range(10):
...     print(LAST_NAMES.pop())
Aelters
Arent
Aelterman
Abad
Aerens
Arnold
Aerts
Abbas
Aertsens
Arshan

Sources:

The raw data was originally copied from:

Names of some street:

>>> for s in list(streets_of_eupen())[:5]:
...     print(s)
Aachener Straße
Akazienweg
Alter Malmedyer Weg
Am Bahndamm
Am Berg
>>> for s in list(streets_of_liege())[:5]:
...     print(s)
Place du 20-Août
Rue de l'Abattoir
Rue des Abeilles
Rue des Acacias
Rue de l'Académie

Using the lino.utils.demonames.est:

Some fictive Estonians (each couple one male & one female):

>>> from lino.utils.demonames.est import *
>>> for i in range(5):
...    he = (MALE_FIRST_NAMES_ESTONIA.pop(), LAST_NAMES_ESTONIA.pop())
...    she = (FEMALE_FIRST_NAMES_ESTONIA.pop(), LAST_NAMES_ESTONIA.pop())
...    print("%s %s & %s %s" % (he + she))
Aadu Ivanov & Adeele Tamm
Aare Saar & Age Sepp
Aarne Mägi & Age-Kaie Smirnov
Aaro Vasiliev & Aili Petrov
Aaron Kask & Aili Kukk
>>> streets = Cycler(streets_of_tallinn())
>>> print(len(streets))
1523
>>> for i in range(5):
...     print("%s (%s)" % streets.pop())
Aarde tn (Põhja-Tallinn)
Aasa tn (Kesklinn)
Aate tn (Nõmme)
Abaja põik (Pirita)
Abaja tn (Pirita)

Sources:

  • Estonian last names were originally extracted from www.ekspress.ee (I manually added some less frequent names).

  • Estonian first names are extracted from my personal database.

  • Streets of Tallinn are originally from www.eki.ee (Peter Päll confirmed permission to use it here on 2014-11-07).