PySimpleGUI : surviving the rug pull of licence part I

I liked pySimpleGUI, because as a coder that likes tkinter (the Tk/Tcl bindings) and as a former tcl/tk coder I enoyed the syntaxic sugar that was avoiding all the boiler plates required to build the application.

The main advantage was about not having to remember in wich order to make the pack and having to do the mainloop call. It was not a revolution, just a simple, elegant evolution, hence I was still feeling in control.

However, the projet made a jerk move by relicensing in full proprietary license that requires a key to work functionnaly.

I will not discuss this since the point have been made clearly on python mailing list.

Luckily I want to raise 2 points :
  • we have been numerous coders to fork the project for doing pull requests
  • it higlights once more the danger of too much dependencies


If you have a working copy of the repository



Well, you can still install a past version of pysimpleGUI, but unless you can do
pip install  git+https://github.com/jul/PySimpleGUI#egg=pysimpleGUI


Pro: if that version suited you, your old code will work
Con: there will be no update for the bugs and it is pretty much a no-go.

Expect free alternative



One of the power of free software is the power to fork, and some coders already forked in a « free forever » version of pysimpleGUI.
One of this fork is : Free Simple GUI.

Pro: migration is as simple as :

pip install FreeSimpleGUI
and then in the source :
- import PySimpleGUI as sg
+ import FreeSimpleGUI as sg

Con: a project is as useful as the capacity of the community to keep up with the job of solving issues and the strength of the community to follow up.

Migrating to tkinter



This will be covered in the week to come by translating some examples to real life migration by myself.

Since tkinter has improved a lot and his a pillar of python distribution when it is not broken by debian, it is fairly easy.

Pro: diminises the need for a dependency and empower you with the poweful concept of tk such as variables binded to a widget (an observer pattern).
Con: PySimpleGUI is a multi-target adaptor to not only tkinter but also remi (web), wx, Qt. If you were using the versatility of pysimpleGUI and its multi-platform features you really need to look at the « free forever » alternatives.

Housekeeping an mp3 collection with python and postgres (for fun)

When you have backup, it's always easier to clean your backuped directories with mind at ease.
I could give the final script right there it's less than 30 lines of code and pretty clear : you move file from the origin if there is nothing in destination formed with the name of the artist
#!/usr/bin/env python

from mutagen.mp3 import MP3
from mutagen.flac import FLAC
from mutagen.easyid3 import EasyID3
from  pathlib import Path
import shutil

root=Path(Path.home() / "Musique" /"Music" / "incoming")
dest_root_dir=Path(Path.home() / "Musique")

import os
for r,d, f in os.walk(root):
    for file in f:
        try:
            reader = lambda fl: MP3(x, ID3=EasyID3)
            if file.endswith("flac") or file.endswith("FLAC"):
                reader=FLAC
            mp3= reader(os.path.join(r,file))
            fn= os.path.join(r , file)
            artist=mp3.get('artist', ["tosort"])[0].strip()
            album= mp3.get('album', ["tosort"])[0].strip()
            dst_dir = Path(dest_root_dir / artist / album )
            destination=Path(dst_dir / file)
            print(f"{fn}->{destination}")
            try:
                os.makedirs(dst_dir)
            except:
                pass
            if not destination.is_file():
                shutil.move( fn, destination )
                
        except Exception as e:
            print("Arg %s for file «%s»"  % (repr(e), file))



But, what if you had more than one choice for solving the doublons ? One being good and the other one corrupted or worse ... truncated ?

For the fun, we are gonna try to see how bad the situation is before moving files and if it's worhty. First out of my small playlist of 5300 do I have the ID3 tags album and artist defined for everyone ?
#!/usr/bin/env python

from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
from archery import mdict

import os
SEEN=mdict()
for r,d, f in os.walk("/home/jul/Musique"):
    for file in f:
        try:
            SEEN += mdict({ k: 1 for k in MP3(os.path.join(r,file), ID3=EasyID3).keys() })
        except Exception as e:
            print("Arg %s for file «%s»"  % (repr(e), file))
from json import dumps
print(dumps(dict(reversed(sorted(SEEN.items(), key=lambda item: item[1]))), indent=4))


The answer is ...
{
    "title": 3891,
    "artist": 3879,
    "genre": 3394,
    "album": 3248,
    "tracknumber": 2830,
    "date": 2572,
...
}
20% of the files don't seem to have any MP3 tags at all (metadata). My mp3 have been gathered since 1996 so it's quite normal :D
Second, how much collision do we have ? Either by hash or by filename ?
It's a tad overkill, but since I have postgres running, let's put all the data in base :

DROP TABLE IF EXISTS public.mp3;

CREATE TABLE IF NOT EXISTS public.mp3
(
    filename text COLLATE pg_catalog."default" NOT NULL,
    artist text COLLATE pg_catalog."default",
    album text COLLATE pg_catalog."default",
    title text COLLATE pg_catalog."default",
    date text COLLATE pg_catalog."default",
    tracknumber text COLLATE pg_catalog."default",
    destination text COLLATE pg_catalog."default",
    hash text COLLATE pg_catalog."default",
    
    CONSTRAINT mail_pkey PRIMARY KEY (filename)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS public.mp3
   OWNER to jul;
And then we insert all data in the database
#!/usr/bin/env python
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
from  pathlib import Path
import psycopg2
import os
conn = psycopg2.connect("dbname=mp3 user=jul")
root=Path(Path.home() / "Musique")
for r,d, f in os.walk(root):
    for file in f:
        try:
            if file.lower().endswith("mp3"):
                mp3= MP3(os.path.join(r,file), ID3=EasyID3)
                with conn.cursor() as sql:
                    fn= os.path.join(r , file)
                    artist=mp3.get('artist', ["unk"])[0].strip()
                    album= mp3.get('album', ["unk"])[0].strip()
                    title=mp3.get('title', ["unk"])[0].strip()
                    destination=Path(root / artist / album / file)
                    #from pdb import set_trace;set_trace()
                    sql.execute("""
                        INSERT INTO mp3
                                (filename, artist, album, title, tracknumber, "date", destination, hash
                                )
                                VALUES ( %s, %s, %s, %s, %s, %s,  %s, %s);
                        """,(
                            fn,
                            artist,
                            album,
                            title,
                            mp3.get('tracknumber', ["unk"])[0].strip(), 
                            mp3.get('date', ["unk"])[0].strip(), 
                            str(destination),
                            hash(open(fn,"rb").read()),
                    ))
                    conn.commit()
        except Exception as e:
            print("Arg %s for file «%s»"  % (repr(e), file))
The python hash function beats cryptographic hash in speed and is so used it bulletproof. For detecting collision, it is « good enough ».
Now, let's see if it's worthy to clean up my mess ?
select count(*) from mp3 m 
    INNER JOIN 
        (select count(*) as ord, hash from mp3 GROUP BY hash ) sec 
    ON m.hash=sec.hash 
    WHERE  sec.ord > 1 ;

count | 358
Nearly 10% ! That's a good catch. Actually we count some files more than once. Let's try to see how much files we expect in the end.

First, let's count files unique by hash :
select count(*) from mp3 m 
    inner join 
        (select count(*) as ord, hash from mp3 GROUP BY hash  HAVING  count(hash) = 1 ) sec
    on m.hash = sec.hash;

count 
-------
  5101
Let's count unique expected destination for multiple hash
select count(distinct(m.destination)) from mp3 m 
    inner join 
        (select count(*) as ord, hash from mp3 GROUP BY hash  HAVING  count(hash) != 1 ) sec
    on m.hash = sec.hash;

 count 
-------
   185
So .... We expect 5286 mp3 files in the destination directory :
find . -type f | grep -i "mp3$"| wc -l
5281
Well, it accounts for the OS errors due to me having not checked if the mp3 files where actually not misnamed format.

The world being not about perfection but pragmatism, I call it a win for half a day of having fun and cleaning up :D

what if we stopped using multi-dimensionnal arrays to store views on linear arrays ?

What of my first lesson of C was about matrices in C. Why ? I was studying physics and we were taught what we needed.
Since I left school, this first lesson still seems weired nowadays. Take for instance a bitmap, either the one from a picture or the raw video RAM you use, the matrice is a view.

You want the point at column 10 row 21 on a 1980 x 1200 screen ? Just get the value at the offset of 21 x 1980 + 10 and you have your pixel in 2D notation.

So that's exactly what I coded : a matrix that is a view on a linear table hence kicking a lot of possible optimization :
  • first think : by allocating a linear array you are less likely to spread your allocation on faraway memory pages (less page fault)
  • for rank by rank operation (logical, addition, substraction, research of occurences) you can probably use SIMD optimization (requires a better backend than a « list » (ex : array))
In my actual life I never saw any usage of any matrices of more than 2 dimensions, so as a lazy person I did not generalized for nth dimension. During the coding, I decided to have both
matrix[x][y]
and
matrix[(x,y)]
notation being equivalent. However in the process, I discovered that for addressing it may change nothing, but for slice it is another story :
matrix[(x,y):(dx,dy):(sx, sy)]
return a sub matrix of dimension dx, dy starting from coordinate x, y by step of sx, sy. It does not look phenomenal, but, how do you express a subset of n nth dimensional array with the current [::] slice notation ?
Because, of course, reading the code I see a door for extrapolating a sane consistent behaviour with 2 arbitrary dimensions to arbitrary sized array.

After all, « natural » Nth dimension array makes sense like a ... video frambuffer wich is a matrix view in terms of pixel, but remember a pixel is an array of values (R,G,B, alpha).

With this notation getting the Blue followed by the alpha from a frambuffer would look like :
screen = matrix(map(int,open("/dev/fb0", "rb").read()), dim = (1980,1020, 4))

# get blue (rgba[3]) and alpha (4) at position 243, 743 would be written : 
blue, alpha = screen[(243,743, 3):(243, 743, 4) ]

You could also imagine with a 3D variant do « CPU intensive and slow » blitting (which does beat the purpose of a blitter (video instruction used to manipulate fast memory zone, including sprite handling).
Juste imagine we could implement an elliptic safe behaviour such as we could write :
screen[(dx, dy):(dx+width, dy+height)] = sprite([ [1, 2, ... ]) # sprite being width x height size
All the code being a view, we could actually memory map (mmap) my inner property __val__ to an actual raw framebuffer. which could be usefull for my hello world done by manipulating the raw framebuffer to actually print each letters :D

By writing about something I coded on a wit, without finality, I discover it may actually be usefull in stopping to waste gazillions of Joule dereferencing references of data scattered among memo provoking huge « page fault » storms. The actual solution is not perfect, because it eliminate only one level of dereferentiation, however by using the venerable array (to try), I expect significant gains without even thinking of importing numpy.

For now I have the following test passing regarding ACTUAL usage of the lib regarding addressing memory consistently and extracting patches of memory.
ex :
matrix([[1,2], [3,4]])[(0,0):(0,1)] == [[1,2]] # return first row
matrix([[1,2], [3,4]])[(0,0):(1,0)] == [[1],[3]] # return first column

m= matrix([[1,2],[3,4], [5,6], [7,8],[9,10]])

assert m[1][0] == m[(1,0)]
assert m[3][1] == m[(3,1)]
assert m[0] == [1,2]
assert m[(0,0)]==1
assert m[(0,1)]==2
assert m[(1,0)]==3
assert m[(1,1)]==4
assert m[(0,0):(0,1)]==[m[0],]
# cannot be expressed with regular slice 
assert m[(0,0):(1,0)] ==  [[1], [3]]
assert m[(0,0):(1,1)]==[[1,2], [3,4]]

assert m[(0,0):(2,1):(2,1)] == [[1, 2], [], [5, 6]]
assert m[(0,0):(3,0)] ==  [[1], [3], [5], [7]]
assert m[(0,1):(3,1)] == [[2], [4], [6], [8]]
assert m[(0,0):(3,0):(2,1)] == [[1], [], [5]]
assert m[(0,0):(3,0):(3,1)] == [[1], [], [], [7]]

This kind of notation for writing matrices makes writing matrix multiplication a little more readable :
def mul(m,n):
    mx,my=m.dim
    nx,ny=n.dim
    v = mdim([0,] * (mx * ny),  dim = (mx, ny))
    stride=my-1
    for x in range(mx):
        for y in range(ny):
            zz  = sum(map(lambda e: e[0]*e[1],zip(
                m[(x,0):(x,0+stride)][0],  # xth line from m
                mdim(n[(0,y):(0+stride,y)]).__val__, # yth row from n
            )))
            v[(x,y)]=zz
    return v
It is useless, but I'm pretty proud because, it really is useful, was coded in a day and passed most tests I could think of.

Annex

The code :
class DimensionError(Exception):
  pass

class matrix(object):
    def __eq__(self, tocomp):
        other=tocomp
        if type(tocomp) != type(self):
            other=matrix(tocomp)
        return (self.__val__, self.dim) == (other.__val__, other.dim)

    def __init__(self,a,**kw):
        if kw.get("dim"):
            self.__val__ = a
            self.dim = kw["dim"]
        else:
            self.__val__,self.dim = list(self.flattener(a))

    def flattener(self, v):
        dimy=len(v[0])
        dimx=len(v)
        ret = [ j for i in v for j in i ]
        assert len(ret) == dimx*dimy
        return  ret,(dimx, dimy)

    def unflattener(self):
        return self[(0,0): (self.dim[0]-1, self.dim[1]-1)]

    def __setitem__(self, k, v):
        if type(k)==int:
            # set nth line with slice
            raise NotImplementedError()
        for e, p in  enumerate(zip(k,self.dim)):
            i,j=p
            try:
                assert  i < = j, f"ooupla dim:{e+1} {i}>{j} "
            except:
                raise IndexError
        x,y=k
        dy=self.dim[1]
        offby=x*(dy)+y
        self.__val__[offby]=v

    def __repr__(self):
        return repr(self.unflattener())

    def __getitem__(self, k):
        x,y = 0,0
        dx, dy=self.dim
        step=(1,1)
        if type(k)==int:
			return self[(k,0):(k,dy-1)][0]

        if not isinstance(k,slice):
            x, y=k
            for e, p in  enumerate(zip(k,self.dim)):
                i,j=p
                try:
                    assert  i <= j, f"ooupla dim:{e+1} {i}<={j} k={k}"
                except:
                    raise IndexError
            off=x*dy+y
            return self.__val__[off]

        start, stop, step=k.start,k.stop,k.step
        step = step or (1,1)
        x0, xn, xd=start[1], stop[1]+1, step[1]
        if x0 > xn or xn < 0:
            raise NotImplementedError()
        y0, yn, yd=start[0], stop[0]+1, step[0]
        if y0 > yn or yn < 0:
            raise NotImplementedError()

        ret = []
        
        for y in range(y0,yn, yd):
            ret+=[[],]
            for x in range(x0, xn, xd):
                # row padding
                # I HATE PYTHON for [[],] * n => shallow copies !
                # if not for this bug, padding would be as simple as 
                # if y-y0>=len(ret):
                #    ret+=[[],] * (y-y0-len(ret)+1)
                for k in range(len(ret)-1, y-y0):
                    ret+=[[],]
                ret[y-y0]+=[self[(y,x)]]
        return ret

def transpose(md):
    res = []
    for x in range(md.dim[1]):
        res+=[[0] * md.dim[0],]
        for y in range(md.dim[0]):
            res[x][y]=md[(y,x)]
    return matrix(res)

def mul(m,n):
    mx,my=m.dim
    nx,ny=n.dim
    v = matrix([0,] * (mx * ny),  dim = (mx, ny))
    stride=my-1
    for x in range(mx):
        for y in range(ny):
            zz  = sum(map(lambda e: e[0]*e[1],zip(
                m[(x,0):(x,0+stride)][0],  # xth line from m
                matrix(n[(0,y):(0+stride,y)]).__val__, # yth row from n
            )))
            v[(x,y)]=zz
    return v

m= matrix([[1,2],[3,4], [5,6], [7,8],[9,10]])

assert m[1][0] == m[(1,0)]
assert m[2][0] == m[(2,0)]
assert m[1][1] == m[(1,1)]
assert m[3][1] == m[(3,1)]
assert m[(-1,-1)]==8
assert m[0] == [1,2]
assert m[1] == [3,4]
assert m[2] == [5,6]
assert m[3] == [7,8]
assert m[(0,0)]==1
assert m[(0,1)]==2
assert m[(1,0)]==3
assert m[(1,1)]==4
assert m[(2,0)]==5
assert m[(2,1)]==6
assert m[(0,0):(0,1)]==[m[0],]
assert m[(0,0):(1,0)] ==  [[1], [3]]
assert m[(0,0):(1,1)]==[[1,2], [3,4]]

assert m[(0,0):(2,1):(2,1)] == [[1, 2], [], [5, 6]]
assert m[(0,0):(3,0)] ==  [[1], [3], [5], [7]]
assert m[(0,1):(3,1)] == [[2], [4], [6], [8]]
assert m[(0,0):(3,0):(2,1)] == [[1], [], [5]]
assert m[(0,0):(3,0):(3,1)] == [[1], [], [], [7]]
assert m[(0,0)] == 1
assert m[(1,0)]==3

try:
    print(m[(6,1)])
except IndexError:
    pass

from time import time
m= matrix([[1,2],[3,4], [5,6], [7,8],[9,10]])
n= matrix([[11,12],[13,14], [15,16], [17,18],[19,20]])
start=time()
for i in range(100_000):
    c=matrix(list(map(lambda x:x[0]+x[1], zip(m.__val__, n.__val__))), dim=m.dim)
print("addition with zip")
print(time() - start)
start=time()
for i in range(100_000):
    dx,dy=m.dim
    c=[]
    for x in range(dx):
        c+=[[0] * (dy), ]
        for y in range(dy):
            c[x][y]=m[x][y]+n[x][y]
print("addition with full loops")
print(time() - start)

m = matrix([[1,2,0],[4,3,-1]])
n = matrix([[5,1],[2,3],[3,4]])
assert mul(m,n)==[[9, 7], [23, 9]]

n,m = m,n
assert mul(m,n) == [[9, 13, -1], [14, 13, -3], [19, 18, -4]]
m=matrix([[1],[-1],[1], [1]])
n=matrix([[-10,2,3, 4],])
assert mul(m,n)==[[-10, 2, 3, 4], [10, -2, -3, -4], [-10, 2, 3, 4], [-10, 2, 3, 4]]
assert mul(n,m)== [[-5]]
m=matrix([[1],[-1],[1]])
n=matrix([[-7,2,3],[1,-2,3],])
assert mul(m,n)==[[-7, 2, 3], [7, -2, -3], [-7, 2, 3]]
assert mul(n,m) == [[-6], [6]]
m= matrix([[1,2],[3,4], [5,6], [7,8],[9,10]])
assert m[1] == [3,4]
assert mul(transpose(m),m)==[[165, 190], [190, 220]]
assert transpose(m) == [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]
assert m[1] == [3,4]

Python oddity #12

In [31]: [[],[]] == [[],] * 2
Out[31]: True

In [32]: a,b=[[],[]], [[],] * 2

In [35]: a[0]+=[1]

In [36]: a
Out[36]: [[1], []]

In [37]: b[0]+=[1]

In [38]: b
Out[38]: [[1], [1]]
Basically python tells that x + x = 2 * x but since [[],] * 2, does not do a deepcopy but a shallow one, modifying b[0] also modifies b[1]. I get caught by this infuriating inconsistency once every 4 years and I always swear it won't catch me again... until next time. The why and how at cpython level is detailed here

Student (Gosset W) about biodiversity : the good, the bad and the better

I love Student, I hate Student.

I love William Gosset aka Student

I love his most brilliant paper : The probable error of a mean. This paper revolutionized experimental science : it tells randomness from correlation with so few samples required it is a piece of cake to do science.

Basically, the paper states that there are random variations on controlled axis, like when you brew a beer (Student was QA for guiness) regarding the quality of grain, the quality of the yeast, the temperature of the water, the room .... and that if a mean tries to sabotage your QA in Guiness through a simple test we are gonna detect you.

Ok, the first time I read the paper, it was my feeling that basically it is an applied mathematics HOWTO detect non randomness.

Easy to read, nice examples, and at the end a table which modus operandi is given on how to estimate your condidence given your numbers of parameters and how much samples you have. The kind of math physicists expect from mathematicians : given with a notice that is simple to follow : THANK YOU Gosset !

This paper let you read for each input parameters, each measures a matrix of « is random/maybe correlated ». If your measure looks like a galton board it's random, if you have more than one peak, ALERT !

Multimodal is the way we call these camels' backs that appears with non randomness.

The beauty of Student is that thanks to his mathematical studies we can now cross X input times Y parameters in N samples and have X times Y tests made.

It's a massive parallelisation of screening for results.

Student's Test are probably massively used by all the controlers of casinos and « opérateurs de jeux de hasard » around the world to check if by any chance randomness is biased.

This paper is one of the many breakage that followed Darwin paper, when science WAS political. Queen's and King's saw randomness as a perfectly suitable mechanism for selection as an attack on their genome.

Kings et Queens love species deterministicsly sorted, hierarchized, that shows the human at the top of the tree, and the nobility at the top of the human through cross breeding.

It's probably Darwin that inspired physicist to have more interest in telling randomness from non randomness, because they probably came out with the first exploitable data.


You see after Mendel, and before, the catholic church had a habit of noting details about their wine and cultures (what, when they flower, how much they produce). The catholic church was bureaucratic before bureaucracy was invented.

These are the same records that states that grapes are flowering sooner, producing more sugar and mass thus being correlated to the parameter « HOTTER ».

With Student methodology, it is really hard to be climatoscepticists. It is also good for detecting bullshits, that's the derived work by Fischer called the p-values used as the kind of CRC-32 of what to expect in randomness if we try to measure by ourselves. So much information encoded in such a small data is what real intelligence is compared to artifical ones.

I hate Student aka William Gosset



Having a stand on the usefulness of randomness in selection of the species was risky, especially in England. The bitch Victoria was colonizing the world in the name of the superiority of her race, and had a hobby of applied racism with dogs, cats and betting on her horses « pur sang » the week end. The free mason, thought of themselves as (pseudo) scientifics too, applying a certain physics to a certain society. The church too had a word in science since they were basically the best places in the world providing the teaching of it.

Randomness in science came as a mawashigeri in the face of some biggots having the pretention to know everything by determining the root causes of everything.

Let's say that when for making explosives you need good quality insurance and thus taboo or not taboo, you teach the math of probabilty and statistic.

Having a stand on randomness in 1900 was not apolotical, it was tainted by the pseudo-science of justifying the racism (cf this motherfucker of Cuvier). Basically Darwin was seen as an anti-slavery/anti-colonialist/anti-aristocrat with or without his consent or willingness.

So let's say that Victoria loved her point of view of race superiority so she found a scientific paper scientific paper on the topic : « eugenics review » that was applied science for selecting better species.

Don't let be fooled by the absence of already tabooed experimentation on humans, because 1) Victora was an assumed racist, 2) eugenism was coming with another « science » called bio-socialisme, a society where the proven most optimal laws of Nature are applied for the « better good ».

If Darwin is right, the royalist inspired bio-socialisme dies in tremendous agony.

In this context of a certain fear on the mad men beginning to play with the genome of corns (maïze), wheat in a contexte of famine, Student was asked :

« hey, what about bio-diversity, if Darwin is right about natural selection, are we not taking risks reducting the variations pool given by genetic variability ? »

To wich he answered :
To sum up: Winter has in this experiment succeeded, by continuous mass selection, in producing two races of maize, one of which has more than twice, and the other less than one-third, the normal oil content.

...

And so we reach the conception of species patiently accumulating a store of genes, of no value under existing conditions and for the most part neutralized by other genes of opposite sign. When, conditions change
the species finds in this store genes which give rise to just the variation which will enable it to adapt itself to the change.

...

It follows that the change appears to have produced the variation which it has merely selected from among those potentially present. ‘Thus we can reconcile the view held, amongst other people, by the late Walter Heape, that the environ- ment produces the required variation, with the older Darwinian selection of random variations, to which it appears at first sight to be diametrically opposed.
No worries, eugnism is risk less because the loss of biodiversity is compensated by mother Nature (How do you measure ?) Hence agricultural eugenism (that still prevails as we speak) is good because without a risk.

And I love william again



Remember, the part I have hidden ?

When, however, conditions change, unless too suddenly or drastically, the species finds in this store genes which give rise to just the variation which will enable it to adapt itself to the change.
There is a stark reminder by the eugenist William Gosset that lack of biodiversity is threatening us right now.

When this guy is the corner stone of the justification for human selection of species of agricultural interest it may be a good idea to listen ot him.

Human selections of agricultural species DO NOT WORK when the changes are too drastic according to him. And the actual climate changes can be considered as drastic for plants.

If we trust him, we should check with experimental solid evidences that our selective model beats random selections in case of drastic changes.

L'entreprise est toujours à visage familiale.

Je me souviens d'un épisode de « twilight » (la 4é dimension en Français) où des extra terrestres descendaient sur terre pleins de bonnes et de belles promesses, disant qu'ils étaient comme une famille venue « pour servir les humains ». Et la chûte amenée par les cryptanalistes et linguistes est que leur devise est un adage de cuisine humaine.

Et bien, si vos dirigeants viennent de famille dysfonctionnelles, il est normal que le visage « familiale » de l'entrerprise soit dysfonctionnel.

Pensant à mon exemple familiale dont notre devise intergénérationnelle partagée est On choisit ses amis, pas sa famille, ça fait du sens.

Tenez, le père de mon père, il était le petit costaud d'une famille creusoise. Lui il aimait les études et il était doué. Juste, sa famille pour récupérer quelque terre et un peu de reconnaissance familiale d'un riche cousin il voulait l'envoyer devenir ouvrier agricole sous payé chez le cousin.
Alors papy, quand on lui a fait le coup de l'âne qu'avance plus le jour du brevet du collège, il a fugué pour faire sa vie.

Vagabond, il a été un peu fourni de petit job par les marines stationnés en France, puis, de fil en aiguille il est devenu maçon, puis tailleur de pierre, puis ingénieur d'étude BTP en cours du soir loin des siens qu'il n'a jamais revu.

Mamie Simone, elle aussi son daron était gratiné : un gros con de collabo action française, violent, drogué cocaïnomane et consommateur de parties fines à Genêve. Un Haute Société Protestante violent gratiné au Gotha qui menaçait sa propre fille avec un 9mm quand il avait trop bu.

Je vous épargne la gallerie familiale, mais disons que même si ça fait longtemps que ne parle plus à mes parents pour raisons familiales, je dois admettre qu'il y a un mieux perceptible dans les valeurs familiales qu'ils ont eu comparé à certains aînés. J'ai des reproches à leur faire certes, mais rien d'aussi extrême, et je vous en ai épargné des pires.
Étant moi même parent, je suis redevable à la tradition de pouvoir comme mes parents être potentiellement plus affreux que la moyenne mais pouvoir dire : comme mes parents je ne suis pas parfaits, mais comme eux, j'ai l'intention de faire mieux que la génération précédente, et que devenu vieux mon enfant me parle.

Bref, certaines traditions familiales présentent un avantage concurrentiel quand il s'agit de s'auto congratuler car on fait mieux que les générations d'avant.
Vous voyez, l'entreprise ne ment pas quand elle parle de famille, elle oublie qu'elle est plus dans la tradition de la haute société protestante, de la bourgeoisie dont la tradition conservatrice est loin de toute valeurs de recherche de la réalisation de soi même, mais plus proche de celle de la compétition entre membres de la famille.

Une bonne famille chauvine, fière d'elle même dont chaque enfant qui se plaît à se dire unique et émancipé ouvre toujours la conversation en disant « dans la famille on a que des ingénieurs et des médecins, et les ingénieurs c'est ceux qui ont échoué ». Quelle preuve de détermination à être émancipé quand on s'assied comme digne résultat de la reproduction sociale.

J'ai plus de respect pour daddy le vagabond devenu ingénieur que tous ces branleurs qui ne se sont comme seule mérite que celui de naître, j'ai plus de respect pour ma grand mère qui a choisi le déclassement social plutôt que la reproduction familiale mortifère que pour ces glandus.

Dire que l'entreprise est à caractère familiale est pour moi qui suis idiot une annonce claire de la couleur de ce qui m'arrive dans les dents : un endroit qu'il faut tenter de quitter au plus tôt car il est basé sur de la compétition entre-frères et sœurs pour être le chouchou du patron.

Écoutez, amies entreprises, si il y a bien une chose que je demande à l'entreprise c'est de ne pas ressembler à une famille, ça me stresse avant même de rentrer dans vos locaux.

Moi ma tradition familiale, c'est celle de papy René qui m'a appris que pour réaliser ses rêves et ne pas se retrouvé exploité dans un champs pour le profit de ses proches, il faut savoir se barrer de sa famille radicalement.

Visulazing your ldap tree structure.

I made « bla » as a tool for sysadmins. One of the interest of using python as a DSL is being terse.

As previously said, my toy LDAP domain is dc=home.
In |0]: list(map(print,walk(
	"dc=home",
	lambda e:"--".join(
    	map(
        	lambda e:"\"%s\""%e,e.entry_dn.split(",")[::-1])
))))

"dc=home"--"ou=group"
"dc=home"--"ou=group"--"cn=staff"
"dc=home"--"ou=people"
"dc=home"--"ou=people"--"uid=boss"
"dc=home"--"ou=people"--"uid=louis"
"dc=home"--"ou=people"--"uid=manager"
which copy pasted in this graphviz template :
graph toto {
    concentrate=true;
    node [shape=rectangle]
# paste your stuff here
}
gives you a tree representation like this :
And if you load your credentials for the cn=config you will have :
It really is a cheap trick, but cheap tricks are helpful when oversold as a consultant and needing to understand the structure fast.