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,{ "record_id": 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,{ "record_id": 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_imported(pattern='*')¶
Yield a list of the source files corresponding to the currently imported modules that match the given pattern
- 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 38k 22k 9k 84k
lino_xl 42k 13k 10k 77k
lino_noi 1.0k 0.4k 0.7k 3k
total 81k 35k 20k 164k
========== ============ =========== =============== =============
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:
Belgian last names from http://www.lavoute.org/debuter/Belgique.htm
French last names from http://www.nom-famille.com/noms-les-plus-portes-en-france.html
Russian last names from http://www.meetmylastname.com/prd/articles/24
French first names from http://meilleursprenoms.com/site/LesClassiques/LesClassiques.htm
African, Muslim and Russian names from http://www.babynames.org.uk and http://genealogy.familyeducation.com
Streets of Liège (STREETS_OF_LIEGE) are from http://fr.wikipedia.org/wiki/Liste_des_rues_de_Li%C3%A8ge
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).