Quantcast
Channel: AFPy's Planet
Viewing all 3409 articles
Browse latest View live

[tshirtman] Position/Size of widgets in kivy.

$
0
0

I see it’s a common struggle for people to understand how to manage size and positions of the widgets in kivy.

There is not much to it, really, just a few principles to understand, but they are better explained by example, i believe.

dumb positionning

Let’s create a root widget, and put two buttons in it.

root = Widget()
b1 = Button()
b2 = Button()
root.add_widget(b1)
root.add_widget(b2)

What will happen when i return root to be used as my root widget? As a root widget, it will have all the space for itself, the two buttons, however, will only use their default size, which is (100, 100), and will use their default position, which is (0, 0), the bottom-left corner of the screen.

We can change b1 and b2 positions to absolute values, let’s change line 2 and 3 to:

b1 = Button(pos=(100, 100))
b2 = Button(pos=(200, 200))

something a little smarter

Now, that doesn’t make for a very flexible UI, if you ask me, it would be better to use the parent size to decide where to place the buttons, no?

b1 = Button(pos=(root.x, root.height / 2))
b2 = Button(pos=(root.width - 100, root.height / 2))

No, that’s not much smarter, it’s still quite dumb actually, it doesn’t even work, why? because we just instanciated root, it’s not even the root widget yet, it doesn’t have its final size, so our widgets will use the default value of (100, 100), for their calculations, no - no.

Let’s fix this with bindings.

b1 = Button()
root.bind(on_size=reposition_b1, on_pos=reposition_b1)
b2 = Button()
root.bind(on_size=reposition_b2, on_pos=reposition_b2)

Where these two functions will be along the lines of:

def reposition_b1(root, *args):
    b1.pos = root.x, root.height / 2 - b1.height / 2

Also, widget have nice alias properties for things like height / 2, x + width, x + width / 2, here is a quick table:

right = x + width
top = y + height
center_x = x + width / 2
center_y = y + height / 2
center = (center_x, center_y)

and actually, pos is just an alias property for (x, y). Alias Properties works both ways, so we can use them to set our positions in a simpler way.

def reposition_b1(root, *args):
    b1.x = root.x
    b1.center_y = root.center_y

A lot of work for not so much, right? That’s because we are not using the right tools!

Let’s jump on a layout, specifically FloatLayout.

FloatLayout to the rescue

Layouts are here to make our life easier when constructing an UI.

FloatLayout is very flexible, it lets you set rules for positions, or do things in the absolute way.

root = FloatLayout()
b1 = Button(pos_hint={'x': 0, 'center_y': .5})
b2 = Button(pos_hint={'right': 1, 'center_y': .5})

pos_hint will make the values relative to the size/position of the parent. So here, for b1 x will be the x of the parent, and center_y will be at the middle between y and top.

Now, if you run this, you may get a surprise, because FloatLayout also made b1 and b2 sizes relative to root.size, and the default value being (1, 1), they both have the same size as the root widget, sometime you want to keep that relative, maybe with a different value, sometime you want to have them keep the value passed in size (or default size value), if so, you can pass (None, None) to size_hint.

b1 = Button(pos_hint={'x': 0, 'center_y': .5}, size_hint=(None, None))

Conclusion

I’ll probably do other posts on the subject, because there is much more to it, but the basic principles are here. I anyway really encourage you to look at this section of the documentation, which go further with the various kind of layouts you have access to.


[logilab] Lecture de données tabulées avec Numpy -- Retour d'expérience sur dtype

$
0
0

Ce billet est un petit retour d'expérience sur l'utilisation de Numpy pour lire et extraire des données tabulées depuis des fichiers texte.

Chaque section, hormis les objectifs ou la conclusion, correspond soit à une difficulté rencontrée, une remarque technique, des explications et références vers la documentation officielle sur un point précis qui m'a fait patauger quelques temps. Il y a de forte chance, pour certains d'entre vous, que les points décrits ici vous paraissent évidents, que vous vous disiez "mais qui ne sait pas ça ?!". J'étais moi-même le premier étonné, depuis que je connais Numpy, de ne pas savoir ce genre de choses. Je l'étais moins quand autour de moi, mes camarades ne semblaient pas non plus connaître les petites histoires numpysiennes que je vais vous conter.

http://www.logilab.org/file/203839/raw/numpylogo.png

Objectifs

Le Pourquoi et le Où on va au fait ?

J'avais sous la main des fichiers aux données tabulées, type CSV, où les types de données par colonne étaient clairement identifiés. Je ne souhaitais pas passer du temps avec le module csv de la bibliothèque standard à convertir chaque élément en type de base, str, flottants ou dates. Numpy étant déjà une dépendance du projet, et connaissant la fonction np.genfromtxt, j'ai évidemment souhaité l'utiliser.

Il était nécessaire de ne lire que certaines colonnes. Je souhaitais associer un nom à chaque colonne. L'objectif était ensuite d'itérer sur ces données ligne par ligne et les traiter dans des générateurs Python. Je n'utilise pas ici Numpy pour faire des opérations mathématiques sur ces tableaux à deux dimensions avec des types hétérogènes. Et je ne pense d'ailleurs pas qu'il soit pertinent d'utiliser ce type de tableau pour faire ces opérations.

dtypes différents, str et extraction de chaînes vides

On utilise ici l'argument dtype des fonctions telles que np.genfromtxt pour lire des fichiers tabulés dont les colonnes sont de types différents.

Attention au dtype à passer à np.genfromtxt ou np.recfromtxt quand on parse des données tabulée (file ou stream). Pour parser une colonne de chaînes de caratères, lui passer [('colname', str)] renvoie des chaînes vides si les autres dtypes sont de types différents.

Il faut préciser la taille :

dtype=[('colname', str, 10)]
# or
dtype=[('colname', 'S10')]

Ou alors prendre un "vrai" objet str Python :

dtype=[('colname', object)]

aussi équivalent à:

dtype=[('colname', 'object')]

Et oui, je suis littéralement tombé sur l'évidence, les "types Numpy", c'est du type C. Et les chaînes, c'est du char * et il y a donc besoin de la taille. La solitude s'est fait moindre quand j'ai su que je n'étais pas le seul à être tombé sur des données tronquées voire vides.

dtype et tableau à zéro dimension

Attention au tableau Numpy 0D quand le contenu tabulé à parser n'a qu'une seule ligne (cas d'un np.[rec]array avec plusieurs dtypes). Impossible d'itérer sur les éléments puisque dimension nulle.

Supposons que vous ayez un fichier tabulé d'une seule ligne :

Name,Play,Age
Coltrane,Saxo,28

J'utilise np.genfromtxt en précisant le type des colonnes que je souhaite récupérer (je ne prends pas en compte ici la première ligne).

data = np.genfromtxt(filename, delimiter=',',
                     dtype=[('name', 'S12'), ('play', object), ('age', int)],
                     skip_header=1)

Voici la représentation de mon array :

array(('Coltrane', 'Saxo', 28),
    dtype=[('name', 'S12'), ('play', 'O'), ('age', '<i8')])

Si dans votre code, vous avez eu la bonne idée de parcourir vos données avec :

for name, instrument, age in data:
    # ...

vous pourrez obenir un malheureux TypeError: 'numpy.int64' object is not iterable par exemple. Vous n'avez pas eu de chance, votre tableau Numpy est à zéro dimension et une shape nulle (i.e. un tuple vide). Effectivement, itérer sur un objet de dimension nulle n'est pas chose aisée. Ce que je veux, c'est un tableau à une dimension avec un seul élément (ici un tuple avec mes trois champs) sur lequel il est possible d'itérer.

Pour cela, faire:

>>> print data
array(('Coltrane', 'Saxo', 28),
      dtype=[('name', 'S12'), ('play', 'O'), ('age', '<i8')])array(('babar', 42.), dytpe=[('f0', 'S5'), ('f1', '<f8')])
>>> print data.shape, data.ndim
(), 0
>>> data = data[np.newaxis]
>>> print data.shape, data.ndim
(1,), 1

dtype et str : chararray ou ndarray de strings ?

Pour les chararray, lire help(np.chararray) ou http://docs.scipy.org/doc/numpy/reference/generated/numpy.chararray.html. En particulier:

The chararray class exists for backwards compatibility with Numarray, it is not recommended for new development. Starting from numpy 1.4, if one needs arrays of strings, it is recommended to use arrays of dtype object_, string_ or unicode_, and use the free functions in the numpy.char module for fast vectorized string operations.

On fera donc la distinction entre:

# ndarray of str
na = np.array(['babar', 'celeste'], dtype=np.str_)
# chararray
ca = np.chararray(2)
ca[0], ca[1] = 'babar', 'celeste'

Le type de tableau est ici différent : np.ndarray pour le premier et np.chararray pour le second. Malheureusement pour np.recfromtxt et en particulier pour np.recarray, si on transpose le label de la colonne en tant qu'attribut, np.recarray il est transformé en chararray avec le bon type Numpy --- |S7 dans notre cas --- au lieu de conserver un np.ndarray de type |S7.

Exemple :

from StringIO import StringIO
rawtxt = 'babar,36\nceleste,12'
a = np.recfromtxt(StringIO(rawtxt), delimiter=',', dtype=[('name', 'S7'), ('age', int)])
print(type(a.name))

Le print rend bien un objet de type chararray. Alors que :

a = np.genfromtxt(StringIO(rawtxt), delimiter=',', dtype=[('name', 'S7'), ('age', int)])
print(type(a['name']))

affiche ndarray. J'aimerais que tout puisse être du même type, peu importe la fonction utilisée. Au vue de la documentation et de l'aspect déprécié du type charray, on souhaiterait avoir que du ndarray de type np.str. J'ai par ailleurs ouvert le ticket Github 3993 qui n'a malheureusement que peu de succès :-(

Tableau de chaînes : quel dtype ?

Si certains se demandent quoi mettre pour représenter le type "une chaîne de caractères" dans un tableau numpy, ils ont le choix :

np.array(['coltrane', 'hancock'], dtype=np.str)
np.array(['coltrane', 'hancock'], dtype=np.str_)
np.array(['coltrane', 'hancock'], dtype=np.string_)
np.array(['coltrane', 'hancock'], dtype='S')
np.array(['coltrane', 'hancock'], dtype='S10')
np.array(['coltrane', 'hancock'], dtype='|S10')

Les questions peuvent être multiples : est-ce la même chose ? pourquoi tant de choses différentes ? Pourquoi tant de haine quand on lit la doc Numpy et que l'info ne saute pas aux yeux ? À savoir que le tableau construit sera identique dans chacun des cas. Il existe peut-être même d'autres moyens de construire un tableau de type identique en lui passant encore un n-ième argument dtype différent.

  • np.str représente le type str de Python. Il est converti automatiquement en type chaines de caractère Numpy dont la longueur correspond à la longueur maximale rencontrée.
  • np.str_ et np.string_ sont équivalents et représentent le type "chaîne de caractères" pour Numpy (longueur = longueur max.).
  • Les trois autres utilisent la représentation sous forme de chaîne de caractères du type np.string_.
    • S ne précise pas la taille de la chaîne (Numpy prend donc la chaîne la plus longue)
    • S10 précise la taille de la chaîne (données tronquées si la taille est plus petite que la chaîne la plus longue)
    • |S10 est strictement identique au précédent. Il faut savoir qu'il existe cette notation <f8 ou >f8 pour représenter un flottant. Les chevrons signifient little endian ou big endian respectivement. Le | sert à dire "pas pertinent". Source: la section typestr sur la page http://docs.scipy.org/doc/numpy/reference/arrays.interface.html

À noter que j'ai particulièrement apprécié l'utilisation d'un symbole pour spécifier une information non pertinente --- depuis le temps que je me demandais ce que voulait bien pouvoir dire ce pipe devant le 'S'.

Conclusion (et pourquoi pas pandas ?)

http://www.logilab.org/file/203841/raw/pandas_logo.png

Pandas, bibliothèque Python d'analyse de données basée sur Numpy, propose, via sa fonction read_csv, le même genre de fonctionnalités. Il a l'avantage de convertir les types par colonne sans lui donner d'information de type, qu'on lise toutes les colonnes ou seulement quelques unes. Pour les colonnes de type "chaîne de caractères", il prend un dtype=object et n'essaie pas de deviner la longueur maximale pour le type np.str_. Vous ne rencontrerez donc pas "le coup des chaînes vides/tronquées" comme avec dtype='S'.

Je ne m'étalerai pas sur tout le bien que je pense de cette bibliothèque. Je vous invite par ailleurs à lire/ parcourir un billet de novembre qui expose un certain nombre de fonctionnalités croustillantes et accompagné d'un IPython Notebook.

Et pourquoi pas Pandas ? Il ne me semble pas pertinent de dépendre d'une nouvelle bibliothèque, aussi bien soit-elle, pour une fonction, aussi utile soit-elle. Pandas est un projet intéressant, mais jeune, qui ne se distribue pas aussi bien que Numpy pour l'instant. De plus, le projet sur lequel je travaillais utilisait déjà Numpy. Je n'avais besoin de rien d'autre pour réaliser mon travail, et dépendre de Pandas ne me semblait pas très pertinent. Je me suis donc contenté des fonctions np.{gen,rec}fromtxt qui font très bien le boulot, avec un dtype comme il faut, tout en retenant les boulettes que j'ai faites.

[logilab] What's New in Pandas 0.13?

$
0
0
http://www.logilab.org/file/203841/raw/pandas_logo.png

Do you know pandas, a Python library for data analysis? Version 0.13 came out on January the 16th and this post describes a few new features and improvements that I think are important.

Each release has its list of bug fixes and API changes. You may read the full release note if you want all the details, but I will just focus on a few things.

You may be interested in one of my previous blog post that showed a few useful Pandas features with datasets from the Quandl website and came with an IPython Notebook for reproducing the results.

Let's talk about some new and improved Pandas features. I suppose that you have some knowledge of Pandas features and main objects such as Series and DataFrame. If not, I suggest you watch the tutorial video by Wes McKinney on the main page of the project or to read 10 Minutes to Pandas in the documentation.

Refactoring

I congrat the refactoring effort: the Series type, subclassed from ndarray, has now the same base class as DataFrame and Panel, i.e. NDFrame. This work unifies methods and behaviors for these classes. Be aware that you can hit two potential incompatibilities with versions less that 0.13. See internal refactoring for more details.

Timeseries

to_timedelta()

Function pd.to_timedelta to convert a string, scalar or array of strings to a Numpy timedelta type (np.timedelta64 in nanoseconds). It requires a Numpy version >= 1.7. You can handle an array of timedeltas, divide it by an other timedelta to carry out a frequency conversion.

from datetime import timedelta
import numpy as np
import pandas as pd

# Create a Series of timedelta from two DatetimeIndex.
dr1 = pd.date_range('2013/06/23', periods=5)
dr2 = pd.date_range('2013/07/17', periods=5)
td = pd.Series(dr2) - pd.Series(dr1)

# Set some Na{N,T} values.
td[2] -= np.timedelta64(timedelta(minutes=10, seconds=7))
td[3] = np.nan
td[4] += np.timedelta64(timedelta(hours=14, minutes=33))
td
0   24 days, 00:00:00
1   24 days, 00:00:00
2   23 days, 23:49:53
3                 NaT
4   24 days, 14:33:00
dtype: timedelta64[ns]

Note the NaT type (instead of the well-known NaN). For day conversion:

td / np.timedelta64(1, 'D')
0    24.000000
1    24.000000
2    23.992975
3          NaN
4    24.606250
dtype: float64

You can also use the DateOffSet as:

td + pd.offsets.Minute(10) - pd.offsets.Second(7) + pd.offsets.Milli(102)

Nanosecond Time

Support for nanosecond time as an offset. See pd.offsets.Nano. You can use N of this offset in the pd.date_range function as the value of the argument freq.

Daylight Savings

The tz_localize method can now infer a fall daylight savings transition based on the structure of the unlocalized data. This method, as the tz_convert method is available for any DatetimeIndex, Series and DataFrame with a DatetimeIndex. You can use it to localize your datasets thanks to the pytz module or convert your timeseries to a different time zone. See the related documentation about time zone handling. To use the daylight savings inference in the method tz_localize, set the infer_dst argument to True.

DataFrame Features

New Method isin()

New DataFrame method isin which is used for boolean indexing. The argument to this method can be an other DataFrame, a Series, or a dictionary of a list of values. Comparing two DataFrame with isin is equivalent to do df1 == df2. But you can also check if values from a list occur in any column or check if some values for a few specific columns occur in the DataFrame (i.e. using a dict instead of a list as argument):

df = pd.DataFrame({'A': [3, 4, 2, 5],
                   'Q': ['f', 'e', 'd', 'c'],
                   'X': [1.2, 3.4, -5.4, 3.0]})
   A  Q    X
0  3  f  1.2
1  4  e  3.4
2  2  d -5.4
3  5  c  3.0

and then:

df.isin(['f', 1.2, 3.0, 5, 2, 'd'])
       A      Q      X
0   True   True   True
1  False  False  False
2   True   True  False
3   True  False   True

Of course, you can use the previous result as a mask for the current DataFrame.

mask = _
df[mask.any(1)]
      A  Q    X
   0  3  f  1.2
   2  2  d -5.4
   3  5  c  3.0

When you pass a dictionary to the ``isin`` method, you can specify the column
labels for each values.
mask = df.isin({'A': [2, 3, 5], 'Q': ['d', 'c', 'e'], 'X': [1.2, -5.4]})
df[mask]
    A    Q    X
0   3  NaN  1.2
1 NaN    e  NaN
2   2    d -5.4
3   5    c  NaN

See the related documentation for more details or different examples.

New Method str.extract

The new vectorized extract method from the StringMethods object, available with the suffix str on Series or DataFrame. Thus, it is possible to extract some data thanks to regular expressions as followed:

s = pd.Series(['doe@umail.com', 'nobody@post.org', 'wrong.mail', 'pandas@pydata.org', ''])
# Extract usernames.
s.str.extract(r'(\w+)@\w+\.\w+')

returns:

0       doe
1    nobody
2       NaN
3    pandas
4       NaN
dtype: object

Note that the result is a Series with the re match objects. You can also add more groups as:

# Extract usernames and domain.
s.str.extract(r'(\w+)@(\w+\.\w+)')
        0           1
0     doe   umail.com
1  nobody    post.org
2     NaN         NaN
3  pandas  pydata.org
4     NaN         NaN

Elements that do no math return NaN. You can use named groups. More useful if you want a more explicit column names (without NaN values in the following example):

# Extract usernames and domain with named groups.
s.str.extract(r'(?P<user>\w+)@(?P<at>\w+\.\w+)').dropna()
     user          at
0     doe   umail.com
1  nobody    post.org
3  pandas  pydata.org

Thanks to this part of the documentation, I also found out other useful strings methods such as split, strip, replace, etc. when you handle a Series of str for instance. Note that the most of them have already been available in 0.8.1. Take a look at the string handling API doc (recently added) and some basics about vectorized strings methods.

Interpolation Methods

DataFrame has a new interpolate method, similar to Series. It was possible to interpolate missing data in a DataFrame before, but it did not take into account the dates if you had index timeseries. Now, it is possible to pass a specific interpolation method to the method function argument. You can use scipy interpolation functions such as slinear, quadratic, polynomial, and others. The time method is used to take your index timeseries into account.

from datetime import date
# Arbitrary timeseries
ts = pd.DatetimeIndex([date(2006,5,2), date(2006,12,23), date(2007,4,13),
                       date(2007,6,14), date(2008,8,31)])
df = pd.DataFrame(np.random.randn(5, 2), index=ts, columns=['X', 'Z'])
# Fill the DataFrame with missing values.
df['X'].iloc[[1, -1]] = np.nan
df['Z'].iloc[3] = np.nan
df
                   X         Z
2006-05-02  0.104836 -0.078031
2006-12-23       NaN -0.589680
2007-04-13 -1.751863  0.543744
2007-06-14  1.210980       NaN
2008-08-31       NaN  0.566205

Without any optional argument, you have:

df.interpolate()
                   X         Z
2006-05-02  0.104836 -0.078031
2006-12-23 -0.823514 -0.589680
2007-04-13 -1.751863  0.543744
2007-06-14  1.210980  0.554975
2008-08-31  1.210980  0.566205

With the time method, you obtain:

df.interpolate(method='time')
                   X         Z
2006-05-02  0.104836 -0.078031
2006-12-23 -1.156217 -0.589680
2007-04-13 -1.751863  0.543744
2007-06-14  1.210980  0.546496
2008-08-31  1.210980  0.566205

I suggest you to read more examples in the missing data doc part and the scipy documentation about the module interpolate.

Misc

Convert a Series to a single-column DataFrame with its method to_frame.

Misc & Experimental Features

Retrieve R Datasets

Not a killing feature but very pleasant: the possibility to load into a DataFrame all R datasets listed at http://stat.ethz.ch/R-manual/R-devel/library/datasets/html/00Index.html

import pandas.rpy.common as com
titanic = com.load_data('Titanic')
titanic.head()
  Survived    Age     Sex Class value
0       No  Child    Male   1st   0.0
1       No  Child    Male   2nd   0.0
2       No  Child    Male   3rd  35.0
3       No  Child    Male  Crew   0.0
4       No  Child  Female   1st   0.0

for the datasets about survival of passengers on the Titanic. You can find several and different datasets about New York air quality measurements, body temperature series of two beavers, plant growth results or the violent crime rates by US state for instance. Very useful if you would like to show pandas to a friend, a colleague or your Grandma and you do not have a dataset with you.

And then three great experimental features.

Eval and Query Experimental Features

The eval and query methods which use numexpr which can fastly evaluate array expressions as x - 0.5 * y. For numexpr, x and y are Numpy arrays. You can use this powerfull feature in pandas to evaluate different DataFrame columns. By the way, we have already talked about numexpr a few years ago in EuroScipy 09: Need for Speed.

df = pd.DataFrame(np.random.randn(10, 3), columns=['x', 'y', 'z'])
df.head()
          x         y         z
0 -0.617131  0.460250 -0.202790
1 -1.943937  0.682401 -0.335515
2  1.139353  0.461892  1.055904
3 -1.441968  0.477755  0.076249
4 -0.375609 -1.338211 -0.852466
df.eval('x + 0.5 * y - z').head()
0   -0.184217
1   -1.267222
2    0.314395
3   -1.279340
4   -0.192248
dtype: float64

About the query method, you can select elements using a very simple query syntax.

df.query('x >= y > z')
          x         y         z
9  2.560888 -0.827737 -1.326839

msgpack Serialization

New reading and writing functions to serialize your data with the great and well-known msgpack library. Note this experimental feature does not have a stable storage format. You can imagine to use zmq to transfer msgpack serialized pandas objects over TCP, IPC or SSH for instance.

Google BigQuery

A recent module pandas.io.gbq which provides a way to load into and extract datasets from the Google BigQuery Web service. I've not installed the requirements for this feature now. The example of the release note shows how you can select the average monthly temperature in the year 2000 across the USA. You can also read the related pandas documentation. Nevertheless, you will need a BigQuery account as the other Google's products.

Take Your Keyboard

Give it a try, play with some data, mangle and plot them, compute some stats, retrieve some patterns or whatever. I'm convinced that pandas will be more and more used and not only for data scientists or quantitative analysts. Open an IPython Notebook, pick up some data and let yourself be tempted by pandas.

I think I will use more the vectorized strings methods that I found out about when writing this post. I'm glad to learn more about timeseries because I know that I'll use these features. I'm looking forward to the two experimental features such as eval/query and msgpack serialization.

You can follow me on Twitter (@jazzydag). See also Logilab (@logilab_org).

[tshirtman] Kivy - Mouse cursor on Rapsberry Pi

$
0
0

It’s no news that kivy have been ported to Rapsberry Pi, it happened nearly a year ago, and works quite well, especially if you start the app without an X server, since kivy draw directly to EGL (don’t ask me :)), it doesn’t need it.

However, there is a little issue about it if you don’t have a touch screen, you can use your mouse, but you don’t see any cursor, whever you are in X or not, because in X, kivy display is actually on top of the X cursor, and without X, nothing even try to draw a mouse cursor.

A few months ago, i wrote a quick hack, adding a few graphic instructions to the egl window provider, and made the probsys driver used on rpi directly update the position of this instruction. It worked, but it wasn’t really a general solution.

So after procrastinating a proper solution for a long time, i finally got my act together and added the cursor display as an option to the touchring module of kivy, along with options of what image to use, offsets and so on, and added the code that made the mouse driver dispatch pos to window’s mouse_pos property.

So here it is, you can find it here until the PR is merged.

[cubicweb] Cubicweb sprints winter/spring 2014

[logilab] Retrieve Quandl's Data and Play with a Pandas

$
0
0

This post deals with the Pandas Python library, the open and free access of timeseries datasets thanks to the Quandl website and how you can handle datasets with pandas efficiently.

http://www.logilab.org/file/186707/raw/scrabble_data.jpg http://www.logilab.org/file/186708/raw/pandas_peluche.jpg

Why this post?

There has been a long time that I want to play a little with pandas. Not an adorable black and white teddy bear but the well-known Python Data library based on Numpy. I would like to show how you can easely retrieve some numerical datasets from the Quandl website and its API, and handle these datasets with pandas efficiently trought its main object: the DataFrame.

Note that this blog post comes with a IPython Notebook which can be found at http://nbviewer.ipython.org/url/www.logilab.org/file/187482/raw/quandl-data-with-pandas.ipynb

You also can get it at http://hg.logilab.org/users/dag/blog/2013/quandl-data-pandas/ with HG.

Just do:

hg clone http://hg.logilab.org/users/dag/blog/2013/quandl-data-pandas/

and get the IPython Notebook, the HTML conversion of this Notebook and some related CSV files.

First Step: Get the Code

At work or at home, I use Debian. A quick and dumb apt-get install python-pandas is enough. Nevertheless, (1) I'm keen on having a fresh and bloody upstream sources to get the lastest features and (2) I'm trying to contribute a little to the project --- tiny bugs, writing some docs. So I prefer to install it from source. Thus, I pull, I do sudo python setup.py develop and a few Cython compiling seconds later, I can do:

import pandas as pd

For the other ways to get the library, see the download page on the official website or see the dedicated Pypi page.

Let's build 10 brownian motions and plotting them with matplotlib.

import numpy as np
pd.DataFrame(np.random.randn(120, 10).cumsum(axis=0)).plot()

I don't very like the default font and color of the matplotlib figures and curves. I know that pandas defines a "mpl style". Just after the import, you can write:

pd.options.display.mpl_style = 'default'
http://www.logilab.org/file/186714/raw/Ten-Brownian-Motions.png

Second Step: Have You Got Some Data Please ?

Maybe I'm wrong, but I think that it's sometimes a quite difficult to retrieve some workable numerial datasets in the huge amount of available data over the Web. Free Data, Open Data and so on. OK folks, where are they ? I don't want to spent my time to through an Open Data website, find some interesting issues, parse an Excel file, get some specific data, mangling them to get a 2D arrays of floats with labels. Note that pandas fits with these kinds of problem very well. See the IO part of the pandas documentation --- CSV, Excel, JSON, HDF5 reading/writing functions. I just want workable numerical data without making effort.

A few days ago, a colleague of mine talked me about Quandl, a website dedicated to find and use numerical datasets with timeseries on the Internet. A perfect source to retrieve some data and play with pandas. Note that you can access some data about economics, health, population, education etc. thanks to a clever API. Get some datasets in CSV/XML/JSON formats between this date and this date, aggregate them, compute the difference, etc.

Moreover, you can access Quandl's datasets through any programming languages, like R, Julia, Clojure or Python (also available plugins or modules for some softwares such as Excel, Stata, etc.). The Quandl's Python package depends on Numpy and pandas. Perfect ! I can use the module Quandl.py available on GitHub and query some datasets directly in a DataFrame.

Here we are, huge amount of data are teasing me. Next question: which data to play with?

Third Step: Give some Food to Pandas

I've already imported the pandas library. Let's query some datasets thanks to the Quandl Python module. An example inspired by the README from the Quandl's GitHub page project.

import Quandl
data = Quandl.get('GOOG/NYSE_IBM')
data.tail()

and you get:

              Open    High     Low   Close    Volume
Date
2013-10-11  185.25  186.23  184.12  186.16   3232828
2013-10-14  185.41  186.99  184.42  186.97   2663207
2013-10-15  185.74  185.94  184.22  184.66   3367275
2013-10-16  185.42  186.73  184.99  186.73   6717979
2013-10-17  173.84  177.00  172.57  174.83  22368939

OK, I'm not very familiar with this kind of data. Take a look at the Quandl website. After a dozen of minutes on the Quandl website, I found this OECD murder rates. This page shows current and historical murder rates (assault deaths per 100 000 people) for 33 countries from the OECD. Take a country and type:

uk_df = Quandl.get('OECD/HEALTH_STAT_CICDHOCD_TXCMILTX_GBR')

It's a DataFrame with a single column 'Value'. The index of the DataFrame is a timeserie. You can easily plot these data thanks to a:

uk_df.plot()
http://www.logilab.org/file/186711/raw/GBR-oecd-murder-rates.png

See the other pieces of code and using examples in the dedicated IPython Notebook. I also get data about unemployment in OECD for the quite same countries with more dates. Then, as I would like to compare these data, I must select similar countries, time-resample my data to have the same frequency and so on. Take a look. Any comment is welcomed.

So, the remaining content of this blog post is just a summary of a few interesting and useful pandas features used in the IPython notebook.

  • Using the timeseries as Index of my DataFrames
  • pd.concat to concatenate several DataFrames along a given axis. This function can deal with missing values if the Index of each DataFrame are not similar (this is my case)
  • DataFrame.to_csv and pd.read_csv to dump/load your data to/from CSV files. There are different arguments for the read_csv which deals with dates, mising value, header & footer, etc.
  • DateOffset pandas object to deal with different time frequencies. Quite useful if you handle some data with calendar or business day, month end or begin, quarter end or begin, etc.
  • Resampling some data with the method resample. I use it to make frequency conversion of some data with timeseries.
  • Merging/joining DataFrames. Quite similar to the "SQL" feature. See pd.merge function or the DataFrame.join method. I used this feature to align my two DataFrames along its Index.
  • Some Matplotlib plotting functions such as DataFrame.plot() and plot(kind='bar').

Conclusion

I showed a few useful pandas features in the IPython Notebooks: concatenation, plotting, data computation, data alignement. I think I can show more but this could be occurred in a further blog post. Any comments, suggestions or questions are welcomed.

The next 0.13 pandas release should be coming soon. I'll write a short blog post about it in a few days.

The pictures come from:

[Biologeek] Demain j’arrête

$
0
0

Je suis donc devenu un sale con méprisant plutôt qu’un introverti refoulé. Victoire.

Demain j’arrête de parler aux gens

Je lis les planches de STPo depuis quelques années et je ne suis jamais déçu par son trait, son humour… son aigreur. Il a décidé de publier un livre de qualité en ayant le courage de s’auto-éditer grâce au crowdfunding.

Même si la plupart des histoires sont déjà disponibles en ligne, le support papier donne assurément une autre dimension et permet de décrocher un peu l’écran le temps d’une soirée, de le prêter, de l’offrir ou au pire d’allumer la cheminée (ouais je suis pas mauvais en comm).

Demain j’arrête les billets même pas sponsorisés.

[logilab] A Salt Configuration for C++ Development

$
0
0
http://www.logilab.org/file/204916/raw/SaltStack-Logo.png

At Logilab, we've been using Salt for one year to manage our own infrastructure. I wanted to use it to manage a specific configuration: C++ development. When I instantiate a Virtual Machine with a Debian image, I don't want to spend time to install and configure a system which fits my needs as a C++ developer:

This article is a very simple recipe to get a C++ development environment, ready to use, ready to hack.

Give Me an Editor and a DVCS

Quite simple: I use the YAML file format used by Salt to describe what I want. To install these two editors, I just need to write:

vim-nox:
  pkg.installed

emacs23-nox:
  pkg.installed

For Mercurial, you'll guess:

mercurial:
 pkg.installed

You can write these lines in the same init.sls file, but you can also decide to split your configuration into different subdirectories: one place for each thing. I decided to create a dev and editor directories at the root of my salt config with two init.sls inside.

That's all for the editors. Next step: specific C++ development packages.

Install Several "C++" Packages

In a cpp folder, I write a file init.sls with this content:

gcc:
    pkg.installed

g++:
    pkg.installed

gdb:
    pkg.installed

cmake:
    pkg.installed

automake:
    pkg.installed

libtool:
    pkg.installed

pkg-config:
    pkg.installed

colorgcc:
    pkg.installed

The choice of these packages is arbitrary. You add or remove some as you need. There is not a unique right solution. But I want more. I want some LLVM packages. In a cpp/llvm.sls, I write:

llvm:
 pkg.installed

clang:
    pkg.installed

libclang-dev:
    pkg.installed

{% if not grains['oscodename'] == 'wheezy' %}
lldb-3.3:
    pkg.installed
{% endif %}

The last line specifies that you install the lldb package if your Debian release is not the stable one, i.e. jessie/testing or sid in my case. Now, just include this file in the init.sls one:

# ...
# at the end of 'cpp/init.sls'
include:
  - .llvm

Organize your sls files according to your needs. That's all for packages installation. You Salt configuration now looks like this:

.
|-- cpp
|   |-- init.sls
|   `-- llvm.sls
|-- dev
|   `-- init.sls
|-- edit
|   `-- init.sls
`-- top.sls

Launching Salt

Start your VM and install a masterless Salt on it (e.g. apt-get install salt-minion). For launching Salt locally on your naked VM, you need to copy your configuration (through scp or a DVCS) into /srv/salt/ directory and to write the file top.sls:

base:
  '*':
    - dev
    - edit
    - cpp

Then just launch:

> salt-call --local state.highstate

as root.

And What About Configuration Files?

You're right. At the beginning of the post, I talked about a "ready to use" Mercurial with some HG extensions. So I use and copy the default /etc/mercurial/hgrc.d/hgext.rc file into the dev directory of my Salt configuration. Then, I edit it to set some extensions such as color, rebase, pager. As I also need Evolve, I have to clone the source code from https://bitbucket.org/marmoute/mutable-history. With Salt, I can tell "clone this repo and copy this file" to specific places.

So, I add some lines to dev/init.sls.

https://bitbucket.org/marmoute/mutable-history:
    hg.latest:
      - rev: tip
      - target: /opt/local/mutable-history
      - require:
         - pkg: mercurial

/etc/mercurial/hgrc.d/hgext.rc:
    file.managed:
      - source: salt://dev/hgext.rc
      - user: root
      - group: root
      - mode: 644

The require keyword means "install (if necessary) this target before cloning". The other lines are quite self-explanatory.

In the end, you have just six files with a few lines. Your configuration now looks like:

.
|-- cpp
|   |-- init.sls
|   `-- llvm.sls
|-- dev
|   |-- hgext.rc
|   `-- init.sls
|-- edit
|   `-- init.sls
`-- top.sls

You can customize it and share it with your teammates. A step further would be to add some configuration files for your favorite editor. You can also imagine to install extra packages that your library depends on. Quite simply add a subdirectory amazing_lib and write your own init.sls. I know I often need Boost libraries for example. When your Salt configuration has changed, just type: salt-call --local state.highstate.

As you can see, setting up your environment on a fresh system will take you only a couple commands at the shell before you are ready to compile your C++ library, debug it, fix it and commit your modifications to your repository.


[Biologeek] Diversion numérique

$
0
0

En cette journée de la protection des données, je souhaitais revenir sur cet article :

Je voudrais ici oser une hypothèse. Une thèse même. Cette thèse serait ceci : la surveillance arachnéenne des citoyens-clients par ceux qui nous gouvernent « verticalement » (pouvoirs d’État tout autant que pouvoirs libéraux des multinationales des réseaux) n’est si étonnamment tolérée que parce qu’elle s’ancre, « horizontalement » sur des pratiques sociales de contrôles mutuels — quotidiennes, familières, devenues naturelles. Autrement dit : la NSA pousse sur un terreau sociétal qui a fait du contrôle de soi, des autres et du monde, par la technologie, une évidence du lien, un ethos, une manière de vivre. La tige croît sur des rhizomes.

[…]

Ce désir de contrôle, cette pulsion de surveillance et de sécurité frénétique, elle passe désormais par chacun d’entre nous. Elle prend corps et fait fibre dans nos nerfs. Chacun s’en fait le relai, le colporteur, la conduction jouissive et peureuse. Chacun y trouve son petit plaisir de flic, de gestionnaire en maîtrise, de voyeur à deux balles. Tu contrôles ta maison, ta voiture, tes achats ; il surveille les mails de sa femme, géolocalise sa fille, budgétise le temps de connexion de son fils. Elle contrôle son pouls, sa tension, compte ses calories et ses pas. Vous filtrez vos appels, cherchez votre ex sur Facebook, googueulisez la fille que vous avez rencontrée au bar hier plutôt que de la découvrir telle qu’elle se révèle. Et l’on vous offre tous les outils personnels et paresseux pour ça. Toutes les applis. Toute la quincaillerie clinquante du geek à portée de clics et de bips.

[…]

Vous avez remarqué ceci ? La pub ne vend plus rien au nom de la liberté. Mais tout, ou presque, au nom du confort et de la sécurité. Changement d’ethos majeur.

701 000 heures de garde-à-vue, Alain Damasio

Plus je réfléchis à ces histoires de confort et de sécurité, plus j’ai envie de dévier plutôt que de résister. Si le fond du problème est un désir de contrôle au niveau de la société, cela ne risque pas de s’arranger en ajoutant encore du contrôle au niveau personnel pour se cacher, hacker, etc. Ma stratégie (sur le long terme donc, vs. tactique) est de lâcher-prise sur le global et prendre soin du local.

It’s all about metadata after all. By itself, on one single website, this may seem harmless. But by collecting and merging the metadata of several websites comprehensive tracking becomes possible.

Google Webfonts, The Spy Inside?

Que les états, les renseignements généraux ou les multinationales puissent me profiler grossièrement n’est pas nouveau et je n’ai pas l’énergie pour lutter contre, même collectivement. Cela ne pourrait changer radicalement que par une révolution et nous en sommes encore assez éloignés, bien trop contents de notre confort.

En revanche, que mes voisins, ma famille ou mes collaborateurs aient accès à des informations que j’estime être personnelles et/ou confidentielles pourrait altérer des cercles de confiance qui me sont très chers. Ces données locales qui relèvent de l’intimité constituent une base de mes relations et des liens qui unissent les différents cercles de ma personnalité. Protéger ce maillage très fin me semble être beaucoup plus important d’autant qu’il est encore protégé par nos limitations techniques (mais plus pour longtemps).

L’opacité ou la transparence, propriétés équivalentes d’un même phénomène, n’exigent pas la totalité. La gradation est en fait leur atout le plus important.

Gestion de l’opacité, Karl Dubost

Comment y pallier alors ? En introduisant du bruit numérique, de l’entropie brouillant les pistes pour que ces micro-interactions restent floues, noyées dans la masse. Chiffrer, multiplier les identités, générer des traces numériques incohérentes. Tester des outils comme BitMessage ou Twister (qui consomment encore beaucoup trop de CPU pour mon usage mais j’espère que ça s’améliorera).

Mais en parallèle également, ne pas hésiter à chuchoter numériquement. Revenir à des outils moins intelligents, conserver certains cercles complètement déconnectés, discuter de visu et en pleine nature pour ne pas avoir l’impression d’être épié par un quelconque périphérique. Réduire son empreinte numérique pour privilégier les échanges de qualité, réduire le nombre de services dans le cloud qui enregistrent chacune de nos actions et de nos interactions, éduquer ses concitoyens sur ce que signifie le stockage illimité de nos échanges et de nos relations.

La frugalité numérique me semble être la meilleure amie de notre opacité, monter le son d’un côté pour pouvoir évoluer sur la pointe des pieds par ailleurs. Tromper les algorithmes en faisant diversion.

[tshirtman] Kivy image manipulations with Mesh and Textures

$
0
0

If you want to give a little life to interactive (or not) elements, it’s always nice to have more tricks to manipulate images for nifty effects.

One of such ways is mapping a Texture on a special canvas instruction, that will distort your texture based on the position of its points.

[kivy.graphics.Mesh](http://kivy.org/docs/api-kivy.graphics.html#kivy.graphics.Mesh) is such an instruction, and it has a simple interface, if not 100% straightforward, you basically call it this way.

Mesh:
vertices: (x1, y1, s1, v1, x2, y2, s2, v2, x3, y3, s3, v3...)
indices: (1, 2, 3...)
texture: some_texture
mode: some_mode

where all: x mean the horizontal coordinate of a point y mean the vertical coordinate of a point s mean the horizontal position of the texture attached to this point (between 0 and 1) v mean the vertical position of the texture attached to this point (between 0 and 1)

indices is useful if, for some (very good) reason, you don’t want to give the points in the order you’ll use them, or if you want to refer to a point multiple time (this won’t be demonstrated in this blog post, but try to think about a grid mapping, for example), now we will use it in the most simple way, just give the range of your number of points.

the texture needs to be a texture object, you can get such an object by getting the texture property of a [CoreImage](http://kivy.org/docs/api-kivy.core.image.html#kivy.core.image.Image) or an Image (or even a Label, if you want to).

Let’s say we have an image, for example, kivy logo.

from kivy.core.image import Image as CoreImage

texture = CoreImage('data/logo/kivy-icon-512.png').texture

(this will work wherever you are, because kivy look at his images anyway :))

This image is a circle, lets use that to cut it into a virtual pie and assign a part of the texture to each part, we’ll be using the triangle_fan mode of Mesh, that is a perfect fit for such an operation.

# the central point
points = [Window.width / 2, Window.height / 2, .5, .5]

# now go around
    i = 0
    while i < 2 * pi:
        i += 0.01 * pi
        points.extend([
    Window.width / 2 + cos(i) * 100,
    Window.height / 2 + sin(i) * 100,
    .5 + sin(i),
    .5 + cos(i)])

put that in a more complete program, and you get this:

from kivy.app import App
from kivy.lang import Builder
from kivy.core.image import Image as CoreImage
from kivy.properties import ListProperty, ObjectProperty
from kivy.clock import Clock
from kivy.core.window import Window
from math import sin, cos, pi


kv = '''
Widget:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Mesh:
            vertices: app.mesh_points
            indices: range(len(app.mesh_points) / 4)
            texture: app.mesh_texture
            mode: 'triangle_fan'
'''


class MeshBallApp(App):
    mesh_points = ListProperty([])
    mesh_texture = ObjectProperty(None)

    def build(self):
        self.mesh_texture = CoreImage('data/logo/kivy-icon-512.png').texture
        Clock.schedule_interval(self.update_points, 0)
        return Builder.load_string(kv)

    def update_points(self, *args):
        points = [Window.width / 2, Window.height / 2, .5, .5]
        i = 0
        while i < 2 * pi:
            i += 0.01 * pi
            points.extend([
                Window.width / 2 + cos(i) * 100,
                Window.height / 2 + sin(i) * 100,
                .5 + cos(i),
                .5 + sin(i)])

        self.mesh_points = points

if __name__ == '__main__':
    MeshBallApp().run()

Now, this is not very impressive, we just have an image, there were much simpler ways to do that!

But now, we can tweek the rendering a lot, lets add some offseting of the texture, some radius change, and some wobbling, with sliders to control all this.

from kivy.app import App
from kivy.lang import Builder
from kivy.core.image import Image as CoreImage
from kivy.properties import ListProperty, ObjectProperty, NumericProperty
from kivy.clock import Clock
from kivy.core.window import Window
from math import sin, cos, pi


kv = '''
BoxLayout:
    Widget:
        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Mesh:
                vertices: app.mesh_points
                indices: range(len(app.mesh_points) / 4)
                texture: app.mesh_texture
                mode: 'triangle_fan'
    BoxLayout:
        orientation: 'vertical'
        size_hint_x: None
        width: 100
        Slider:
            value: app.offset_x
            on_value: app.offset_x = args[1]
            min: -1
            max: 1
        Slider:
            value: app.offset_y
            on_value: app.offset_y = args[1]
            min: -1
            max: 1
        Slider:
            value: app.radius
            on_value: app.radius = args[1]
            min: 10
            max: 1000
        Slider:
            value: app.sin_wobble
            on_value: app.sin_wobble = args[1]
            min: -50
            max: 50
        Slider:
            value: app.sin_wobble_speed
            on_value: app.sin_wobble_speed = args[1]
            min: 0
            max: 50
            step: 1
'''


class MeshBallApp(App):
    mesh_points = ListProperty([])
    mesh_texture = ObjectProperty(None)
    radius = NumericProperty(500)
    offset_x = NumericProperty(.5)
    offset_y = NumericProperty(.5)
    sin_wobble = NumericProperty(0)
    sin_wobble_speed = NumericProperty(0)

    def build(self):
        self.mesh_texture = CoreImage('data/logo/kivy-icon-512.png').texture
        Clock.schedule_interval(self.update_points, 0)
        return Builder.load_string(kv)

    def update_points(self, *args):
        points = [Window.width / 2, Window.height / 2, .5, .5]
        i = 0
        while i < 2 * pi:
            i += 0.01 * pi
            points.extend([
                Window.width / 2 + cos(i) * (self.radius + self.sin_wobble * sin(i * self.sin_wobble_speed)),
                Window.height / 2 + sin(i) * (self.radius + self.sin_wobble * sin(i * self.sin_wobble_speed)),
                self.offset_x + sin(i),
                self.offset_y + cos(i)])

        self.mesh_points = points

if __name__ == '__main__':
    MeshBallApp().run()

Now we are talking!

[cyp] Le décorateur view_config de pyramid

$
0
0

Rappel

Pyramid utilise un système de routes prédéclarées dans le fichier __init__.py (voir précédent article sur les routes).

Les vues matchent ces routes et renvoient les paramètres aux moteurs de templates qui transforment en html (ou autre chose) et renvoient aux clients.

Les views

Par défaut, le fichier views.py rassemble les différentes vues :

from pyramid.view import view_config

@view_config(route_name='home', renderer='templates/home.pt')
def home(request):
    return {'project': 'foo'}

@view_config(route_name='controls', renderer='templates/controls.pt')
def controls(request):
    return {'status': 'ok',
            'values': [1, 2, 3]}

Les vues peuvent être éclatées dans plusieurs fichiers, peuvent être écrites sous forme de classes.

class ViewSample(object):
     def __init__(self, request):
         self._request = request

     @view_config(route_name='home', renderer='templates/home.pt')
     def home(self):
         return {'project': 'foo'}

     @view_config(route_name='controls', renderer='templates/controls.pt')
     def controls(request):
         return {'status': 'ok', 'values': [1, 2, 3]}

Une vue est une fonction décorée par view_config. La fonction prends en paramètre un objet request (abordé dans un prochain billet) qui correspond à la requête faite par l'utilisateur. La fonction retourne ou un objet Response (abordé dans un prochain billet : sans doute le même que request) ou objet manipulable par le moteur de rendu. Le décorateur view_config décrit le rendu de la vue et son appel.

@view_config

view_config peut prendre un grand nombre de paramètres :

  • accept,
  • attr,
  • check_csrf
  • containment,
  • context,
  • custom_predicates,
  • decorator,
  • effective_principals
  • header,
  • http_cache,
  • mapper,
  • match_param,
  • name,
  • path_info,
  • permission,
  • predicates
  • physical_path,
  • renderer,
  • request_method,
  • request_param,
  • request_type,
  • route_name,
  • wrapper,
  • xhr.

Soient 24 paramètres possibles... Je ne vais pas tous les détailler : j'ignore l'usage de certains et d'autres sont très pointus. Pour les plus curieux, la documentation de pyramid les détaille. Tous sont optionnels, il n'est donc pas nécéssaire de tous les spécifier sur une méthode.

La documenation de pyramid distingue deux types de paramètres à @view_config : les prédicats et les non prédicats.

Les non prédicats sont permission, attr, renderer, http_cache, wrapper, decorator et mapper. Mécaniquement, tous sont des prédicats.

route_name

route_name décrit quelle route est rendue par cette fonction. route_name corresponds au nom de la route rajouté dans le config.add_route du __init__.py. Plusieurs fonctions peuvent avoir le même route_name (voir plus bas.)

route_name est un prédicat : la fonction décorée par @view_config ne sera appelée que si la requête HTTP match cette route.

from pyramid.view import view_config


@view_config(route_name='home', renderer='json')
def home(request):
    return {'func': 'home'}

@view_config(route_name='foo', renderer='json')
def foo(request):
    return {'func': 'foo'}

@view_config(renderer='json')
def default(request):
    return {'func': 'default'}

Les fonctions home et foo seront appelées quand les routes home et foo seront matchées. défault ne sera jamais appelée. route_name est bien optionnel car il y a d'autre moyen de matcher une fonction mais dans les faits, il est vraiment nécéssaire : les cas où route_name n'est pas utilisé sont un peu plus compliqués et on verra peut être ça une autre fois.

Il est possible d'avoir plusieurs fois le décorateur view_config sur une même fonction.

Imaginons le fichier __init__.py suivant :

from pyramid.config import Configurator

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('foo', '/foo')
    config.add_route('baz', '/baz')

    config.scan()
    return config.make_wsgi_app()

et le fichier views.py suivant :

from pyramid.view import view_config


@view_config(route_name='home', renderer='json')
def home(request):
    return {'func': 'home'}

@view_config(route_name='foo', renderer='json')
@view_config(route_name='baz', renderer='json')
def default(request):
    return {'func': 'default'}

La méthode default matchera les route foo et baz :

$ curl  http://0.0.0.0:6543/ && echo
{"func": "home"}
$ curl  http://0.0.0.0:6543/foo && echo
{"func": "default"}
$ curl  http://0.0.0.0:6543/baz && echo
{"func": "default"}

renderer

renderer décrit le rendu utilisé lors de l'appel. Par défaut, pyramid est[1] livré avec trois moteurs de rendu : string, json et chameleon. D'autres moteurs sont disponibles tel que mako, genshi, jinja2 ou même un moteur maison.

[1]était serait plus juste. Depuis la version 1.5 de pyramid, chameleon n'est plus livré par défaut dans pyramid. Cependant, il reste celui utilisé par pcreate pour créer les templates ou exemple d'application.

Le renderer json transforme la réponse en json ; la réponse doit donc être une chaine de caractères, une liste, un tuple, un dictionnaire ou une combinaison des précédents.

string retourne la représentation str du return.

Différence entre string et json :

from pyramid.view import view_config


@view_config(route_name='fooString', renderer="string")
def fooString(request):
    return ["fooString"]

@view_config(route_name='fooJSON', renderer="json")
def fooJson(request):
    return ["fooJson"]

Les retours seront :

$ curl -i http://0.0.0.0:6543/foo/string && echo
HTTP/1.1 200 OK
Content-Length: 9
Content-Type: text/plain; charset=UTF-8
Date: Mon, 11 Nov 2013 11:17:25 GMT
Server: waitress

['fooString']
$ curl -i http://0.0.0.0:6543/foo/json && echo
HTTP/1.1 200 OK
Content-Length: 9
Content-Type: application/json; charset=UTF-8
Date: Mon, 11 Nov 2013 11:17:28 GMT
Server: waitress

["fooJson"]

Il est important de noter le changment de Content-Type selon le moteur de rendu.

renderer peut aussi prendre un chemin vers un template tel que templates/foo.pt. l'extention .pt signifie page template et indique que le moteur de rendu ici est chameleon. Les rendus chameleon prennent en paramètre des dictionnaires pouvant contenir des objets. Content-Type sera du HTML

Si le paramètre renderer est absent, il faut renvoyer un objet Response.

from pyramid.view import view_config

@view_config(route_name='baz')
def baz(request):
    request.response.body = 'baz'
    return request.response

Et la réponse renvoyée au client ne contiendra que baz :

$ curl  http://0.0.0.0:6543/baz && echo
baz

Request et Response feront l'oject d'un prochain billet (vraiment faut que je le fasse). Comme dans un prochain billet, je montrerais comment rajouter un moteur de rendu à pyramid.

Pyramid peut avoir plusieurs moteurs de rendu simultanément : certaines fonctions peuvent utiliser mako et d'autres chameleon ou json.

request_method

Le paramètre corresponds request_method aux verbes HTTP utilisés pour acceder à la page :

  • GET,
  • POST,
  • PUT,
  • DELETE,
  • HEAD.

NB : Les exemples suivant sont en JSON avec curl, ils s'appliquent tous aussi bien avec un rendu HTML et un navigateur. curl et JSON sont juste ici pour la facilité d'écriture.

from pyramid.view import view_config


@view_config(route_name='baz', renderer='json', request_method='GET')
def get(request):
    return 'GET'

@view_config(route_name='baz', renderer='json', request_method='POST')
def post(request):
    return 'POST'

@view_config(route_name='baz', renderer='json', request_method='HEAD')
def head(request):
    return 'HEAD'

@view_config(route_name='baz', renderer='json', request_method='PUT')
def put(request):
    return 'PUT'

@view_config(route_name='baz', renderer='json', request_method='DELETE')
def delete(request):
    return 'DELETE'

Ce qui donnera avec curl:

$ curl  -XGET http://0.0.0.0:6543/baz && echo
"GET"
$ curl  -XPOST http://0.0.0.0:6543/baz && echo
"POST"
$ curl  -XHEAD http://0.0.0.0:6543/baz && echo
curl: (18) transfer closed with 5 bytes remaining to read
$ curl  -XPUT http://0.0.0.0:6543/baz && echo
"PUT"
$ curl  -XDELETE http://0.0.0.0:6543/baz && echo
"DELETE"

Pour HEAD, c'est un non sens de renvoyer une valeur dans le body d'où l'erreur de curl.

request_method peut être également une liste de méthode acceptées :

from pyramid.view import view_config


@view_config(route_name='baz', renderer='json', request_method='GET')
def get(request):
    return 'GET'

@view_config(route_name='baz', renderer='json', request_method=['POST', 'PUT'])
def post(request):
    return 'POST or PUT'
$ curl  -XGET http://0.0.0.0:6543/baz && echo
"GET"
$ curl  -XPOST http://0.0.0.0:6543/baz && echo
"POST or PUT"
$ curl  -XPUT http://0.0.0.0:6543/baz && echo
"POST or PUT"

On aurait pu également écrire:

@view_config(route_name='baz', renderer='json', request_method='POST')
@view_config(route_name='baz', renderer='json', request_method='PUT')
def post(request):
    return 'POST or PUT'

Sans le paramètre request_method, la vue s'écrirait :

@view_config(route_name='baz', renderer='json')
def baz(request):
    if request.method in ['POST', 'PUT']:
        return 'POST or PUT'
    elif request.method == 'GET':
        return 'GET'

    return '???' #TODO: ici généré une 404
$ curl  -XGET http://0.0.0.0:6543/baz && echo
"GET"
$ curl  -XPUT http://0.0.0.0:6543/baz && echo
"POST or PUT"
$ curl  -XPOST http://0.0.0.0:6543/baz && echo
"POST or PUT"
$ curl  -XDELETE http://0.0.0.0:6543/baz && echo
"???"

Si on ne définit pas toutes les request_method, certains verbes vont générer des 404 (ce qui peut être un comportement légitime).

from pyramid.view import view_config


@view_config(route_name='baz', renderer='json', request_method='GET')
def get(request):
    return 'GET'

@view_config(route_name='baz', renderer='json', request_method='POST')
def post(request):
    return 'POST'
$ curl  -XGET http://0.0.0.0:6543/baz && echo
"GET"
$ curl  -XPOST http://0.0.0.0:6543/baz && echo
"POST"
$curl  -XPUT -v http://0.0.0.0:6543/baz >/dev/null && echo
* About to connect() to 0.0.0.0 port 6543 (#0)
*   Trying 0.0.0.0...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 0.0.0.0 (0.0.0.0) port 6543 (#0)
> PUT /baz HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 0.0.0.0:6543
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Length: 61026
< Content-Type: text/html; charset=UTF-8
< Date: Mon, 11 Nov 2013 11:36:21 GMT
< Server: waitress
<
{ [data not shown]
100 61026  100 61026    0     0  2436k      0 --:--:-- --:--:-- --:--:-- 2483k
* Connection #0 to host 0.0.0.0 left intact

Dans l'absolu, on peut utiliser ses propres verbes même si cela me parait une mauvaise idée[2] :

@view_config(route_name='baz', renderer='json', request_method='FOO')
def foo(request):
    return 'FOO'
$ curl  -XFOO  http://0.0.0.0:6543/baz  && echo
"FOO"
[2]sans compter qu'il faut que les reverses proxy, les serveurs WSGI acceptent ces nouveaux verbes. Dans une optique webservice REST, les verbes webdav, par contre, peuvent être utiles.

Pour faire un webservice REST, cornice est une bonne surcouche à pyramid qui permet de gagner du temps.

Pour finir, pyramid 1.5 a introduit not_ qui est utilisable dans request_method :

from pyramid.view import view_config
from pyramid.config import not_


@view_config(route_name='baz', renderer='json', request_method=not_('POST'))
def post(request):
    return 'not POST'
$ curl  -XGET  http://0.0.0.0:6543/baz  && echo
"not POST"
$ curl  -XPUT  http://0.0.0.0:6543/baz  && echo
"not POST"
$ curl  -XPOST  -v http://0.0.0.0:6543/baz  > /dev/null && echo
* About to connect() to 0.0.0.0 port 6543 (#0)
*   Trying 0.0.0.0...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to 0.0.0.0 (0.0.0.0) port 6543 (#0)
> POST /baz HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 0.0.0.0:6543
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Length: 61416
< Content-Type: text/html; charset=UTF-8
< Date: Mon, 11 Nov 2013 12:04:00 GMT
< Server: waitress
<
{ [data not shown]
100 61416  100 61416    0     0  4355k      0 --:--:-- --:--:-- --:--:-- 4613k
* Connection #0 to host 0.0.0.0 left intact

accept

accept liste les types mimes de réponses acceptées par le client :
application/json, application/xml, text/html...
from pyramid.view import view_config

@view_config(route_name='baz', renderer='json', accept='application/json')
def baz(request):
    return 'baz'

@view_config(route_name='baz', renderer='string', accept='application/xml')
def bazxml(request):
    return '<foo>baz</foo>'
$ curl -H "Accept: application/xml" http://0.0.0.0:6543/baz   && echo
<foo>baz</foo>
$ curl -H "Accept: application/json" http://0.0.0.0:6543/baz   && echo
"baz"

Attention ! Cela ne se spécifie pas le content type de la réponse ! il faut le faire explicitement ou via le moteur de rendu.

@view_config(route_name='baz', renderer='string', accept='application/xml')
def bazxml(request):
    request.response.content_type = 'application/xml'
    return '<foo>baz</foo>'

Le client peut passer un Accept sous la forme d'un mimetype ou sous la forme text/* ou */*.

from pyramid.view import view_config

@view_config(route_name='baz', renderer='json', accept='application/json')
def baz(request):
    return 'baz'

@view_config(route_name='baz', renderer='string', accept='text/xml')
def bazxml(request):
    return '<foo>baz</foo>'
$ curl -H "Accept: application/json" http://0.0.0.0:6543/baz   && echo
"baz"
$ curl -H "Accept: application/*" http://0.0.0.0:6543/baz   && echo
"baz"
$ curl -H "Accept: text/*" http://0.0.0.0:6543/baz   && echo
<foo>baz</foo>
$ curl -H "Accept: */*" http://0.0.0.0:6543/baz   && echo
"baz"
$ curl -H "Accept: */*" http://0.0.0.0:6543/baz   && echo

Le premier accept qui match est retourné. Si accept est absent, il n'y a pas de discrimination sur ce header HTTP.

http_cache

http_cache positionne dans les headers de la réponse la durée du cache coté client. La valeur du paramètre peut être un entier positif ou nul qui est le temps en secondes, un timedelta ou un tuple donc le premier paramètre est un entier ou un timedelta et le second un dictionnaire sur la politique du cache.

from datetime import timedelta
from pyramid.view import view_config


@view_config(route_name='baz', renderer='json', http_cache=2)
def baz(request):
    return 'baz'

@view_config(route_name='foo', renderer='json', http_cache=timedelta(days=1))
def foo(request):
    return 'foo'

@view_config(route_name='bar', renderer='json', http_cache=(4, {'public': True}))
def bar(request):
    return 'bar'
"baz"
$ curl -i http://0.0.0.0:6543/foo && echo
HTTP/1.1 200 OK
Cache-Control: max-age=86400
Content-Length: 5
Content-Type: application/json; charset=UTF-8
Date: Mon, 11 Nov 2013 13:47:14 GMT
Expires: Tue, 12 Nov 2013 13:47:14 GMT
Server: waitress

"foo"

$ curl -i http://0.0.0.0:6543/bar && echo
HTTP/1.1 200 OK
Cache-Control: max-age=4, public
Content-Length: 5
Content-Type: application/json; charset=UTF-8
Date: Mon, 11 Nov 2013 13:52:55 GMT
Expires: Mon, 11 Nov 2013 13:52:59 GMT
Server: waitress

"bar"

match_param

match_param est un prédicat sur le contenu du path.

Soit le fichier __init__.py suivant :

from pyramid.config import Configurator

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.add_route('foo', '/foo/{baz}')
    config.scan()
    return config.make_wsgi_app()

et le fichier views correspondant :

from pyramid.view import view_config


@view_config(route_name='foo', renderer='json', match_param='baz=foo')
def foo(request):
    return 'foo'

@view_config(route_name='foo', renderer='json', match_param='baz=bar')
def bar(request):
    return 'bar'

Ce qui donne lors des appels :

$ curl http://0.0.0.0:6543/foo/bar && echo
"bar"
$ curl http://0.0.0.0:6543/foo/foo && echo
"foo"

permission

permission liste les permissions pour acceder à la vue. Cela rentre dans les authentifications et les permissions de pyramid qui fera l'objet d'un billet dédié.

Combinaison des paramêtres

Après cette longue liste partielle de paramètres, l'intéret est de les combiner pour offrir une granularité fine pour les réponses.

@view_config(name="home", accept='application/json', renderer='json')
def repJson(request):
    """réponse en json"""
    return {'foo': 'bar'}

@view_config(name="home", request_method='GET', renderer='templates/home.pt')
def repGet(request):
    return {'params': 'foo'}

@view_config(name="home", request_method='POST', renderer='templates/home.pt')
def repPost(request):
    return {'params': 'bar'}

@view_config(name="home", request_method='PUT', renderer='templates/homeput.pt')
def repPut(request):
    return {'params': 'baz'}

Dans cet exemple, la même route home peut répondre selon les combinaisons de quatre manières différentes en utilisant s'il le faut différents rendus.

Ajout de paramètres

Si la liste ne suffit pas, il est possible de créer ses propres prédicats.

Il n'y a pas d'interface au sens ZCA[4] mais la classe doit respecter la signature suivante :

[4]yep y aura un billet sur la ZCA et pyramid s'en sert un peu. Ce n'est pas nécessaire pour comprendre les entrailles de pyramid mais ça peut aider.
class Predicate(object):
   def __init__(self, val, config):
       pass

   def text(self):
       return 'une signature'

   phash = text

   def __call__(self, context, request):
       return True # retourne un booléen

Exemple de prédicat sur le jour de la semaine.

class DayPredicate(object):
      _choice = {'monday': 0,
                 'thursday': 1,
                 'wedesnday': 2,
                 'thuesday' : 3,
                 'friday': 4,
                 'saturday': 5,
                 'sunday': 6}

      def __init__(self, val, config):
          self._val = val
          self._numVal = self._choice[val]

      def text(self):
          return 'predicat on %s' % self._val

      phash = text

      def __call__(self, context, request):
          return datetime.datetime.today().weekday() == self._numVal

Ajout du prédicat à pyramid:

config.add_view_predicate('day', DayPredicate)

et enfin l'utilisation.

@view_config(route_name='foo', renderer='json', day='monday')
def monday(request):
    return 'monday'

@view_config(route_name='foo', renderer=json', day='sunday')
def sunday(request):
    return 'sunday'

Ce qui donnera :

$ curl http://0.0.0.0:6543/foo && echo
"monday"

Une autre approche est d'utiliser le paramètre custom_predicate de view_config.

@view_config(route_name='foo', renderer='json', custom_predicate=(DayPredicate('monday', None), ))
def monday(request):
    return 'monday'

@view_config(route_name='foo', renderer='json', custom_predicate=(DayPredicate('sunday', None), ))
def sunday(request):
    return 'sunday'

Des usages plus pertinents seraient des prédicats logged ou admin pour filtrer si l'utilisateur est loggé ou admin.

C'est tout pour aujourd'hui

Le décorateur @view_config avec sa granularité et son extensibilité est une des choses que j'aime beaucoup dans pyramid.

[Biologeek] Éducation manuelle

$
0
0

Le changement de logique ne peut se réaliser sans que l’on revoie de fond en comble l’éducation des enfants. Celle qui prévaut aujourd’hui est déterminée et inspirée par les priorités de l’idéologie marchande et financière, et l’abandon passif à une caste enseignante. On sait de plus en plus l’importance que revêtent la conception, la gestation et la façon de mettre au monde un enfant. Trêve d’hypocrisie : ce que tout le monde appelle « éducation » est une machine à fabriquer des soldats de la pseudo-économie, et non de futurs êtres humains accomplis, capables de penser, de critiquer, de créer, de maîtriser et de gérer leurs émotions, ainsi que de ce que nous appelons spiritualité ; « éduquer » peut alors se résumer à déformer pour formater et rendre conforme. Le malaise grandissant de toute jeunesse condamnée au naufrage est dès lors que le système ne peut l’intégrer ou la prendre en charge témoigne de cette aliénation. L’équation qui a prévalu, en particulier lors des Trente Glorieuses, selon laquelle faire de bonnes études donnait une qualification garante d’un salaire ne fonctionne plus dans la société de la croissance illimitée. Alors, pourquoi s’obstiner dans cette option déjà obsolète ?

Dans le nouveau paradigme, il faut être en priorité attentif à l’enfant, en développant une pédagogie de l’être qui permette avant toute chose de le faire naître à lui-même, c’est-à-dire de l’aider à révéler sa personnalité unique, ses talents propres, pour répondre à la vocation que lui inspire sa présence au monde et à la société. C’est le doter d’une cohérence intérieure qui lui donnera le sentiment d’être à sa véritable place dans la diversité du monde. Pour que cette naissance à soi-même advienne réellement, il est indispensable d’abolir ce terrible climat de compétition qui donne à l’enfant l’impression que le monde est une arène, physique et psychique, produisant de l’angoisse d’échouer au détriment de l’enthousiasme d’apprendre.

La prépondérance donnée à l’intellect au détriment de l’intelligence des mains, auxquelles nous devons pourtant notre évolution, est une catastrophe qui fait de nous des infirmes sans que nous en ayons conscience ; elle a créé une sorte de hiérarchie arbitraire offrant aux concepts la clé d’un processus décisionnel que l’expérience tangible ne peut valider. Le rapport concret à la nature est également indispensable, car c’est à elle que l’enfant doit la vie, toute son existence durant ; tirer parti d’un principe vital sans le connaître est une lacune fondamentale.

L’éducation doit restaurer la complémentarité des aptitudes. Les établissements éducatifs devraient tous proposer de la terre à cultiver, des ateliers d’initiation manuelle, artistique… Des jardins biologiques permettraient de faire l’expérience tangible des lois intangibles du vivant : la fécondité de la terre, sa générosité à nous offrir les aliments qui nous font vivre, le mystère et la beauté des phénomènes qui régissent l’immense complexité de ce que nous appelons écologie. L’école doit être également le lieu privilégié de l’initiation à la complémentarité féminin/masculin et, bien entendu, celui d’une éducation à la sobriété peut-être décisive pour la vie entière. Car l’enfant, ignorant tout, en amont, du processus de production des biens dont il use abondamment dans la civilisation de la surabondance, ainsi que du devenir des déchets qu’il induit en aval, en est réduit à une stricte, et triste, fonction de petit consommateur gaspilleur. Il est inconscient de sa participation à l’outrance collective des nantis et des privilèges sans joie, alors que tant d’enfants vivent dans des pays où le quotidien est fait de frugalité — quand ce n’est pas de misère. Paradoxalement, j’ai souvent observé dans les yeux de ces derniers une étincelle encore ardente, comme lorsque l’espérance demeure vivante en dépit de tout. L’initiation à la modération est source de joie, car elle rend plus accessible la satisfaction, abolissant la frustration que produit le toujours-plus, entretenue en permanence par une publicité au talent pernicieux, dont tous les enfants devraient être protégés. Cette prise en otage produit des enfants blasés, désabusés, et, avec le « tout, tout de suite », c’en est fini de ce désir auquel la patience donne tant de saveur et de valeur. Dans le même ordre d’idées, on constate que l’industrie du jouet participe à l’ingérence de l’adulte dans l’imaginaire de l’enfant. Saturé d’outils ludiques prêts à la consommation, celui-ci est détourné de cette capacité naturelle commune à tous les enfants du monde de créer par eux-mêmes, et avec une fraîcheur incomparable, les objets nécessaires à leur amusement. Cette créativité, ennoblie de leur candeur, participerait très fortement à la sobriété par le fait qu’elle rend inutile la prolifération extravagante d’objets dont la fabrication est très dispendieuse en matières premières, souvent dérivées du pétrole, en énergie, en pollution, en recyclage, etc. Par ailleurs, on ne peut que déplorer le nombre de plus en plus exorbitant de jouets qui véhiculent des symboles pernicieux et pervers de la société contemporaine. Ils instillent dans des âmes innocentes les toxines de toutes les turpitudes : violence, meurtre, pornographie, etc. Il est du devoir urgent des Etats, et des parents, d’édicter des règles strictes pour protéger l’enfant, si vulnérable et manipulable, de toutes les convoitises qui portent atteinte à son intégrité. Il ne s’agit pas de traiter cette question avec un moralisme ou un manichéisme de circonstance, mais de donner à des faits objectifs des réponses objectives, qui doivent être apportées par les adultes, responsables du devenir des générations que la vie leur a confiées. Il ne suffit pas de se demander : « Quelle planète laisserons-nous à nos enfants ? » ; il faut également se poser la question : « Quels enfants laisserons-nous à notre planète ? »

Vers la sobriété heureuse, Pierre Rabhi.

Je m’interroge beaucoup sur l’éducation manuelle que je pourrais partager avec mon fils sachant le peu de savoir-faire que j’ai dans ce domaine. Comment faciliter sa découverte d’activités que je n’ai pas moi-même pratiquées ? Dans quelle mesure est-ce que l’on peut découvrir ensemble de nouvelles pratiques ? Comment attiser sa curiosité sur un domaine qui ne m’a pas déjà attiré ? Quelle part d’inné pourrait l’amener à emprunter les chemins non balisés de sa singularité ?

Il y a bien des écoles « permettant aux enfants d’apprendre à vivre ensemble » et qui doivent inclure l’agroécologie dans leur enseignement vu le lieu mais elles sont très rares. Il y a la famille ou les amis mais on reste dans des cercles relativement fermés. Il y a le Web mais ça reste trop théorique. Il y a les stages mais il faut avoir un certain âge. Il reste les camps/colonies/centres aérés qui sont peut-être la manière la plus simple de découvrir un nouveau domaine de manière candide. En voyez-vous d’autres ?

[sciunto] Supprimer la première page d'un pdf avec pyPDF2

$
0
0

Dans mon travail de recherche, je manipule beaucoup de PDF dont ceux provenant des sites des journaux scientiques. Le problème est qu'un certain nombre de ces journaux mettent une page "de couverture" au PDF à partir de métadonnées et cette page m'ennuie à plusieurs égards :

  • je dois descendre d'une page pour commencer ma lecture
  • je dois imprimmer une page supplémentaire et une fois sur deux utiliser une feuille supplémentaire
  • la pge étant la même pour tous les articles du journal, je ne repère pas au premier coup d'oeil de quel article il s'agit dans ma pile.

Pour toutes ces raisons, à chaque fois que j'ai un tel PDF entre les mains, je déchire la première page. Il y a sans doute des commandes ghostscripts magiques pour ça. Ici, je propose d'écrire un petit script python dont voici les sources.

C'est terriblement simple :

# Creation d'un fichier temporaire
tmp = tempfile.NamedTemporaryFile()
shutil.copy(filename, tmp.name)

# On ouvre le pdf et on compte les pages
output_file = PdfFileWriter()
input_file = PdfFileReader(open(tmp.name, 'rb'))
num_pages = input_file.getNumPages()

# On esquive la page 0, le reste, on copie
for i in range(1, num_pages):
    output_file.addPage(input_file.getPage(i))

# On ferme, on enregistre
tmp.close()
outputStream = open(filename, "wb")
output_file.write(outputStream)

Voilà, le travail est fait :)

[Biologeek] Refonte 2014

$
0
0

On commence à vieillir quand on finit d’apprendre.

Proverbe japonais

J’ai passé le mois de janvier à améliorer de manière incrémentale le confort de lecture (RX pour Reader eXperience ?) et d’interactions de ce site.

Premier gros changement, j’ai transformé geek en penseur dans mon menu car il n’y a plus vraiment de billets techniques sur le blog et j’ai constaté que ce terme pouvait être rébarbatif. J’aurais pu choisir blogueur (mais ma mère ne sait pas ce que ça veut dire) ou écrivain (mais ça faisait pompeux), finalement (petit) penseur me va mieux et j’ai d’ailleurs expliqué ce que j’entendais par ce terme sur la page d’accueil qui a été quasi-intégralement réécrite.

Concernant les billets, j’ai changé la police de caractères qui passe à quelque chose d’un peu plus fin et j’ai amélioré l’interlignage. J’ai également ajouté un support de la césure suite à l’article publié sur OpenWeb pour les navigateurs le supportant même si le texte n’est pas justifié je trouve que cela apporte un petit plus. J’ai cédé à la tentation de flatifier (sic) en enlevant la texture en background et en affinant le titre. Et enfin j’ai ajouté une image en fin d’article (non, pas en header flou par contre) dans le but avoué de (re)mettre à terme mes propres photos, on se motive comme on peut. Du coup j’en ai profité pour refaire aussi l’accueil du blog. Les images devraient s’afficher dans une qualité suffisante pour les riches possesseurs de terminaux à haute densité de pixels (Retina™) mais tous les autres payent le prix d’un téléchargement un peu plus long en attendant une meilleure solution.

La tête de page n’a pas encore été retouchée mais il faut que je m’en occupe, notamment car la solution responsive actuelle ne me convient pas avec sa spécificité iPhone™. Le pied de page a pas mal bougé avec l’apparition de boutons de partage pour faciliter les interactions en situation mobile (et car copier/coller une URL n’est pas à la portée de tout le monde, même aujourd’hui). J’ai ajouté des icônes en SVG pour voir ce qu’il était possible de faire avec cette technologie. J’ai aussi ajouté des liens pour continuer l’exploration et offrir des portes de sorties (incluant l’ajout d’un formulaire de Webmentions pour faciliter l’ajout de liens externes).

Niveau sémantique, j’ai ajouté le support des cartes Twitter pour un rendu avec extrait qui viennent compléter les informations de Schema.org. J’ai arrêté d’ajouter du RDFa malheureusement inutilisé.

Rien de révolutionnaire, ça reste relativement minimaliste et permet de se concentrer sur le texte. N’hésitez pas à me faire vos retours à ce sujet !

[sciunto] Ce que python m'a enseigné

$
0
0

Il y a près de deux ou trois ans, je suis passé à Python. A l'époque, je cherchais un langage permettant de scripter des tâches courantes (adminsys, utilitaires...). De Bash, je suis passé à perl qui m'a beaucoup séduit par l'expression du code et ses regexp. Il possède aussi beaucoup de bibliothèques (cf cpan), ce qui le rend intéressant.

Néanmoins, perl ne rime pas vraiment avec science, en tout cas dans mon domaine (il semble que perl ait une bonne communauté chez les bioinformaticiens). Matlab fait largement référence, mais puisque ce n'est pas éthiquement acceptable, je me suis tourné vers python qui se défend très bien. Il se trouve aussi que Python, contrairement à matlab, est un vrai langage de programmation, utilisé par des développeurs pour faire des outils bureau et web. C'est, selon moi, une (et non pas la) raison pour laquelle ce langage devrait être privilégié par rapport à matlab dans l'enseignement car il s'adresse potentiellement à une plus grande diversité de tâches, donc un plus large public.

Mais ce n'est pas ce que je veux traiter dans cet article, je veux plutôt parler de ce que Python m'a apporté et que je n'ai pas découvert au cours de mes études où j'ai appris C et C++.

  • Faire des tests unitaires. Je me limite encore aux situations simples, mais j'apprécie grandement cet investissement car sur le logn terme, retoucher un code ne donne plus la grosse sueur froide de tout casser. Pour une raison que j'ignore, je n'ai pas découvert ces tests avec perl.
  • Intégration continue. Contribuant à des projets sur github, j'ai découvert Travis-CI. Ce n'est pas libre, mais néanmoins plutôt pratique. Comme pour github, si ce service fermait, je pourrai le remplacer à terme par un service auto-hébergé et ce n'est pas critique pour moi. Cette découverte m'a poussé à faire de l'intégration continue pour latex.
  • Faire une documentation systématique. Découvert avec doxygen en C++, j'ai commencé à documenter mes fonctions quasi-systématiquement avec python et j'ai l'occasion d'apprendre à faire une doc correcte grâce aux contributions que je peux faire dans des libs scientifiques.

Ces éléments, je les ai appris ou perfectionné avec python. Je ne dis pas que je ne l'aurais pas appris autrement, mais le fait est que c'est grâce à Python !


[hautefeuille] Serveur de courriels sous OpenBSD

$
0
0

Serveur de courriels sous OpenBSD

Introduction

On souhaite mettre en place un serveur délivrant les courriels aux utilisateurs locaux mais aussi permettant l’envoi de messages vers l’extérieur au moyen d’un relais.

OpenBSD utilise Sendmail par défaut, nous allons donc le remplacer par le serveur OpenSMTPD.

Configuration préliminaire

On sauvegarde le fichier /etc/mailer.conf :

# mv /etc/mailer.conf /etc/mailer.conf.default

On édite le fichier /etc/mailer.conf pour remplacer les références de Sendmail vers OpenSMTPD :

sendmail        /usr/sbin/smtpctl
send-mail       /usr/sbin/smtpctl
mailq           /usr/sbin/smtpctl
makemap         /usr/libexec/smtpd/makemap
newaliases      /usr/libexec/smtpd/makemap

Configuration des aliases

Editer /etc/mail/aliases et configurer cette section :

# Well-known aliases -- these should be filled in!
root:anyuser@hotmail.com
manager:anyuser@hotmail.com
dumper:anyuser@hotmail.com

Mettre à jour les aliases :

# newaliases

Préparation du fichier secret

Ce fichier contient les informations de connexion smtp de votre compte courriel qui sera utilisé en guise de relais.

# touch /etc/mail/secrets 
# chmod 640 /etc/mail/secrets 
# chown root:_smtpd /etc/mail/secrets 
# echo "victor victor:sratFfg5lm" > /etc/mail/secrets 
# makemap /etc/mail/secrets

Configuration de OpenSMTPD

Editer /etc/mail.smtpd.conf :

listen on lo0 
table aliases db:/etc/mail/aliases.db 
table secrets db:/etc/mail/secrets.db 
accept for local alias <aliases> deliver to mbox 
accept for any relay via tls+auth://victor@mail.gandi.net \ 
auth <secrets> as victor@domaingandi.net

Vérification de la configuration

On peut vérifier la configuration du serveur :

# smtpd -n

Ou le lancer en mode debug :

# smtpd -dv

Démarrage au boot

Editer le fichier /etc/rc.conf.local :

sendmail_flags=NO
smtpd_flags=""

Utilitaire de monitoring

Quelques commandes utiles :

# smtpctl show queue
# smtpctl show stats

[Biologeek] Expérience et conseils

$
0
0

Being there — in the arena — gives you the clarity of experience; it’s a sixth sense that is the ability to know which pieces of advice are important. Unfortunately, the most important lessons you can learn from people with experience tend to be things you don’t think are important until you have experience.

Startup advice and the clarity of experience, Dustin Curtis

Cela me rappelle ce que l’on a vécu avec mixin il y a 6 ans, on avait tous lu Getting Real peu de temps avant, voire pendant, et pourtant cela ne nous a pas empêché de tomber dans les travers décrits : trop de temps avant de sortir le service, accorder trop d’importance à l’infrastructure technique, attendre le jour J pour un gros lancement, etc.

Est-ce que je réitèrerais ces mêmes erreurs aujourd’hui ? Certainement. Ce qui a changé par contre, c’est que je serais capable (je l’espère !) de réajuster beaucoup plus rapidement. Cette expérience m’aura donné 2 leçons :

  • accorder 100% de son temps et de son argent à de la technique est vain : il faut que les utilisateurs connaissent le service avant de pouvoir l’utiliser, il vaut mieux réduire le périmètre et augmenter la pertinence grâce à des tests utilisateurs, il vaut mieux un site qui ne tient pas la charge plutôt qu’une grappe de serveurs qui s’ennuient !
  • développer un produit sans être dans la même pièce est beaucoup plus compliqué : cela doit être possible lorsque l’équipe se connait bien et/ou qu’elle se réunit à intervalles très réguliers et/ou qu’elle utilise les bons outils. Toujours est-il que c’est source d’incompréhensions et de pertes de temps non négligeables.

Vous allez sûrement sourire en lisant ces conseils qui semblent évidents. Avec le recul, je souris aussi. Un peu jaune.

[tshirtman] Magnet: a widget to easily make your interface more lively

$
0
0

Kivy has a very nice Animation class, that allows you to move move widgets around (or other things), by updating any numeric property to a target value, just by setting a time and a transition function. Still, when you are building a complex interface, it can still be a bit cumbersome to manually trigger animations for all the elements, and to keep track of them.

After being frustrated about this issue for some time, i tried my luck some time ago, at doing a nicer, “90% use cases” interface, and the Magnet garden widget was born.

Its usage is simple, you simply use it to wrap your target widget, and give it rules about how to transition when the magnet is moved. As the magnet is moved or resized by the usual kivy logic, instead of making the wrapped widget immediately follow such constraints, it’ll create and keep track of animations to achieve a smooth transition to the new values for you.

As any garden “flower”, to install it, you need to install and use the garden project.

python setup.py install

(either in a virtualenv or system-wide)

then do:

garden install magnet

you can now import it a kivy application:

from kivy.garden.magnet import Magnet

Garden Magnet video

[sciunto] Pourquoi je soutiens l'April

$
0
0

L'April est une association pionnière en matière de Logiciel Libre. Créée en 1996, elle était une association d'informaticiens souhaitant promouvoir le Logiciel Libre, inspirée par l'éthique du discours de Richard Stallman.

Un peu comme pour des codes sources, certains sont oubliés, d'autres au contraire savent évoluer et s'ouvrir au monde. Ce dernier est à mon sens le cas de l'April. Un premier tournant fût la loi DADVSI qui souleva des réactions face aux atteintes qui pouvaient être portées aux Logiciels Libres (Ref).

La prise de conscience qu'il ne suffisait pas de promouvoir le Logiciel Libre mais aussi veiller à sa défense, a entraîné une croissance significative du nombre d'adhérents pour dépasser les 5000 en 2009. Outre les personnes physiques qui se sentent concernées, ce sont aussi des personnes morales : entreprises, associations ou des communes comme Toulouse qui rejoignent l'April.

Grâce à ses membres et aux permanents, les actions menées par l'April se sont succédées et multipliées depuis. DADVSI n'étant que le premier du nom, l'April a poursuivi ses actions sur les brevets logiciels, l'hadopi, la lopsi, l'acta... mais l'April sait aussi planter des graines. Elle est l'initiatrice et la coordinatrice de Libre en fête, elle se tourne vers les autres associations et vers la diversité, informe le citoyen sur la position des candidats aux élections ou oeuvre pour une education à l'informatique plus saine. L'April sait rassembler au-delà des frontières de l'informatique et c'est ce qui en fait une grande association.

J'ai eu la chance de côtoyer (la distance ne me le permettant plus) physiquement un certain nombre de bénévoles ainsi que l'équipe de permanents et je peux témoigner de la qualité de l'esprit et du savoir de ces personnes. Le travail abattu est immense tant les projets sont nombreux.

J'oublie bien sûr des facettes de l'April, je ne cite que celles qui me marquent le plus. Néanmoins, ce sont les raisons pour lesquelles je soutiens l'April.

Aujourd'hui, le Logiciel Libre doit réfléchir à de nouvelles problématiques : protection des données personnelles, informatique en nuage avec la variété de concept que cela contient, matériel libre et fablab, etc. Je suis certain que l'April saura trouver un chemin dans ces questions.

L'April a aussi besoin de votre soutien. Un membre de plus, c'est une voix de plus lors d'une prise de parole. C'est aussi permettre un travail continu de veille et d'expertise grâce aux salariés.

Pour ceux qui veulent donner de leur temps (à toutes les échelles), l'April est prête à recevoir. D'expérience, l'association étant assez imposante, il faut savoir prendre son temps pour prendre ses marques, il y a de la place pour tou(te)s et c'est véritablement enrichissant !

Page de la campagne d'adhésion

[cubicweb] CubicWeb using Postgresql at its best

$
0
0

We had a chat today with a core contributor to Postgresql from whom we may buy consulting services in the future. We discussed how CubicWeb could get the best out of Postgresql:

  • making use of the LISTEN/NOTIFY mechanism built into PG could be useful (to warn the cache about modified items for example) and PgQ is its good friend;
  • views (materialized or not) are another way to implement computed attributes and relations (see CWEP number 002) and it could be that the Entities table is in fact a view of other tables;
  • implementing RQL as an in-database language could open the door to new things (there is PL/pgSQL, PL/Python, what if we had PL/RQL?);
  • Foreign Data Wrappers written with Multicorn would be another way to write data feeds (see LDAP integration for an example);
  • managing dates can be tricky when users reside in different timezones and UTC is important to keep in mind (unicode/str is a good analogy);
  • for transitive closures that are often needed when implementing access control policies with __permissions, Postgresql can go a long way with queries like "WITH ... (SELECT UNION ALL SELECT RETURNING *) UPDATE USING ...";
  • the fastest way to load tabular data that does not need too much pre-processing is to create a temporary table in memory, then COPY-FROM the data into that table, then index it, then write the transform and load step in SQL (maybe with PL/Python);
  • when executing more than 10 updates in a row, it is better to write into a temporary table in memory, then update the actual tables with UPDATE USING (let's check if the psycopg driver does that when executemany is called);
  • reaching 10e8 rows in a table is at the time of this writing the stage when you should start monitoring your db seriously and start considering replication, partition and sharding.
  • full-text search is much better in Postgresql than the general public thinks it is and recent developments made it orders of magnitude faster than tools like Lucene or Solr and ElasticSearch;
  • when dealing with complex queries (searching graphs maybe), an option to consider is to implement a specific data type, use it into a materialized view and use GIN or GIST indexes over it;
  • for large scientific data sets, it could be interesting to link the numpy library into Postgresql and turn numpy arrays into a new data type;
  • Oh, and one last thing: the object-oriented tables of Postgresql are not such a great idea, unless you have a use case that fits them perfectly and does not hit their limitations (CubicWeb's is_instance_of does not seem to be one of these).

Hopin' I got you thinkin' :)

http://developer.postgresql.org/~josh/graphics/logos/elephant.png
Viewing all 3409 articles
Browse latest View live