-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
372 lines (280 loc) · 11.6 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
<!doctype html>
<!--Reveal.js:
- Press 's' to open speaker-notes pop-up window
- Press ESC to enter the slide overview- Press 'b' to fade out to black screen
- Change the background on a slide: <!-- .slide: data-background="#ff0000" -- >
- PDF export using Chrome: https://github.com/hakimel/reveal.js/#pdf-export
+ append 'index.html?print-pdf&showNotes' and scroll to the end before printing
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Pipelines luigi & tests behave</title>
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/solarized.css">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
<style>
.reveal {
background-image: url(../../VSCT_logo.png);
background-size: 10%;
background-repeat: no-repeat;
background-position: 95% 10%;
transition: background-size .5s ease-in-out;
}
.first-slide .reveal {
background-size: 15%;
}
.reveal section img {
border: none;
vertical-align: middle;
}
p > code, li > code, span > code {
color: #D05;
}
figure {
display: flex !important;
justify-content: center;
}
figcaption {
writing-mode: vertical-rl;
font-size: 1rem !important;
}
</style>
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
</head>
<body>
<div class="reveal">
<div class="slides">
<section data-markdown data-separator="^\n\n\n" data-separator-vertical="^\n\n" data-notes="^Note:" data-charset="utf-8">
<textarea data-template>
<!-- .element: data-state="first-slide" -->
## Pipelines luigi & tests behave
<figure>
<img src="ceci-nest-pas-un-pipe.jpg" alt="Ceci n'est pas un pipe, mais un tuyau dans Mario" style="max-height: 30rem">
<figcaption>Modification d'un <a href="https://www.threadless.com/product/543/this_is_not_a_pipe">motif de T-shirt</a> par <a href="https://www.facebook.com/littlegdesign">Little g Design</a></figcaption>
</figure>
<small>3/10/2017</small>
Note: qui utilise "pipeline" au féminin ?
## Bonsoir !
<div style="display: flex; align-items: center">
<img src="avatar.jpg" alt="Une image d'avatar" style="width: 30%; height: 30%; margin-right: 2rem">
<div style="display: flex; flex-direction: column; text-align: left">
<p>**Lucas Cimon**</p>
<p>https://chezsoi.org + [<img src="shaarli-icon.png" style="margin: 0; width: 3rem">](https://chezsoi.org/shaarli)</p>
</div>
</div>
Note: à VSCT depuis mars 2015
## Voyages-Sncf Technologies
<img src="vsct.png" alt="Liste des projets VSCT" style="max-height: 35rem">
Note:
- voyages-sncf.com: 1200 collaborateurs (dont 51% de femmes), 34 ans de moyen d'âge
- VSCT = branche de Voyages-Sncf.com:
* +de 700 personnes, internes & prestas compris
* 3 sites: Paris, Lille, Nantes
## Au menu
[<img src="luigi-logo.png" alt="Logo luigi" style="box-shadow: none">](http://luigi.readthedocs.io/en/stable/index.html)
+
[![Logo behave](behave_logo1.png)](http://pythonhosted.org/behave/)
##
<img src="xsell-widget.png" alt="Capture d'écran du widget Xsell" style="max-height: 40rem">
Note: capture d'écran de notre widget Xsell
-> construction du référentiel d'offres ?
## ETL
![Schéma de fonctionnement d'un ETL](ETL-Process.png) <img src="elasticsearch-logo.png" alt="Logo ElasticSearch" class="fragment" style="max-width: 10rem">
Note: batch d'agrégation & validation, avec comme destination un index ElasticSearch
### 😞
<!-- .slide: data-background="black" -->
<img src="pipelines-screensaver.jpg" alt="L'économiseur d'écran Windows avec des tuyaux" style="max-height: 40rem">
## luigi
<figure>
<img src="luigi-in-car.png" alt="Le personnage de jeu vidéo Luigi dans une voiture" style="max-height: 35rem">
<figcaption><a href="https://dribbble.com/shots/3460043-Luigi">Illustration</a> de <a href="https://dribbble.com/Dmitrij">Dmitrij</a> de Fireart Studio</figcaption>
</figure>
Note:
- initiallement conçu par Erik Bernhardsson pour de la recommendation de chansons
- open-sourcé par lui et Elias Freider à Spotify en 2013
- utilisé par des centaines d'entreprises dont: Foursquare, Stripe, Groupon, Red Hat...
## luigi
![Description des principaux concepts de luigi](luigi-concepts.jpg)
Note:
- direct acyclic graph
- idéal pour du batch
- fournit: découpage en tâches imposées, gestion des dépendances, visualisation, et surtout: `resume` tasks on error
- + supporte multi-process-workers
- limites: pas de scheduler "builtin" -> `cron`
## Task breakdown
<img src="task_breakdown.png" alt="Code source d'une Task luigi expliqué" style="max-height: 35rem">
## Défis rencontrés
<ul>
<li class="fragment">séparer <span style="color:#2aa198">données</span> & <span style="color:#6c71c4">métadonnées</span></li>
<li class="fragment">exécution sélective:<ul>
<li>une seule tâche</li>
<li>une seule branche</li>
</ul></li>
<li class="fragment">tâches "tampons" de cache (_staging_)</li>
<li class="fragment">historisation & purge des anciennes données</li>
</ul>
Note: métadonnées = status, timestamp, nombre d'offres en sortie, erreurs & warnings... + offres rejetées
## luigi-utils
Implémente tout ça, plus:
- supervision des tâches
- centralisation des logs
- upload dans Elasticsearch
- lecture / écriture de JSON en flux avec `ijson`
Note:
- supervision = durée d'exécution, ressources consommées...
- centralisation des logs: capture d'exceptions + envoie dans Graphite / Flume
- upload dans Elasticsearch : en batch + avec bascule d'alias
##
<img src="Luigi_Task_Visualiser.png" alt="Exemple de graph luigi" style="height: 40rem">
Note: pointer les tâches de _staging_
Transition: comment tester le code métier ?
+ nécessité d'uniformisation
## En prod
<img src="sonatype-nexus-logo.png" alt="Logo Nexus Sonatype" style="max-width: 15rem; box-shadow: none">
+
<img src="logo-anaconda.png" alt="Logo Anaconda" style="max-width: 15rem; box-shadow: none">
<p class="fragment">+ ![Logo Tornado](tornado-logo.png) <span style="font-size: 1.5rem">pour exposer nos <span style="color:#6c71c4">métadonnées</span> JSON</span></p>
Note:
- le Nexus nous sert de cache à Pypi
- Anaconda est employé car prisé de nos _data-scientists_
- pq Tornado ? -> utilisé par luigi
## behave
<figure>
<img src="cucumber-snake.jpg" alt="Serpent en concombres" style="max-height: 35rem">
<figcaption>Photo issue du blog <a href="http://catholiccuisine.blogspot.fr/2012/03/ssss-snack-for-st-patricks-day.html">
Catholic Cuisine</a></figcaption>
</figure>
= tests <em>Cucumber</em> pour **Python**
Note: test runner (not compatible with `pytest`)
D'après la doc, utilisable avec Django
## behave : gherkin
```gherkin
Feature: showing off behave
@fixture-integ
Scénario: Import des offres KrazyGlue
Soit les informations de mapping suivantes :
| RRCode | regionId | cityName |
| FRPAR | 2734 | Paris |
| FRNTE | 2603 | Nantes |
Quand le catalogue exécute la tâche KrazyGlueTransformationTask
Alors on récupère 4 offres KrazyGlue
```
Note: supporte aussi les _scenario outlines_
## behave : glue
```python
@given('les informations de mapping suivantes')
def set_destinations(ctx):
destis = {row[0]: {'regionId': row[1], 'cityName': row[2]}
for row in ctx.table}
ctx.rid_mapping = create_fixture(destis)
@when('le catalogue exécute la tâche KrazyGlueTransformationTask')
def process_transformation(ctx):
ctx.task_name = 'KrazyglueTransformationTask'
run_luigi(ctx.task_name, '--rid-mapping', ctx.rid_mapping)
@then('on récupère {counter:Int} offres KrazyGlue')
def assert_number_of_offers(ctx, counter):
with open(get_task_data(ctx.task_name), 'r') as data:
assert len(json.load(data)) == counter
```
## Runner luigi
```python
def run_luigi(etl_module, task_module, *luigi_args):
args = ['--local-scheduler', '--no-lock']
args += ['--module', etl_module, task_module]
args.extend(luigi_args)
Register.clear_instance_cache() # very important
with supervision(disable=True): # in-house
luigi.interface._run(args)
```
## Bonus: validation de types
```python
from behave import use_step_matcher, register_type
use_step_matcher('cfparse')
register_type(String=lambda text: text)
register_type(Int=lambda text: int(text))
register_type(List=lambda text: [w.strip() for w in text.split(',')])
```
##
[<img src="zucchini-report.png" alt="Exemple de rapport Zucchini">](zucchini-report.png)
Note: cf. our Github + merci à Pierre Gentile
## hooks
```python
def before_scenario_hook(context, scenario):
# Using a test config in luigi
luigi_cfg = luigi.configuration.get_config()
luigi_cfg._config_paths = 'path/to/test-luigi.cfg'
luigi_cfg.reload()
context.global_luigi_cfg = luigi_cfg
switch_els_to_fixture_alias(context, scenario)
prepare_fixture(context, scenario)
```
Note:
- exemple issue directement de notre code
- `after_all_hook` restaure l'alias Elasticsearch
- fixtures basées sur tags -> pratique au début, puis pas simple (trop d'indirections, + gherkin indépendant des données de test)
## CLI
```bash
behave --outfile report.json \
--format=json.pretty \
--no-capture --no-capture-stderr \
--tags=-@fixture-integ
```
<img src="behave-street-art.jpg" alt="Graff BEHAVE" style="max-height: 20rem">
Note: cucumber-compatible output:
* https://github.com/behave/behave/issues/267
* notre solution: https://gist.github.com/Lucas-C/2ba14f253186e8df079219a3038e7d94
## Bonus point: coverage
```bash
cd geo && coverage erase && py.test && cd ..
cd catalog && coverage erase && py.test && cd ..
cd geo-bdd && coverage erase && coverage run -m behave && cd ..
cd catalog-bdd && coverage erase && coverage run -m behave && cd ..
coverage report --show-missing
coverage html
```
Note: Combine 2 ELT tests + unit tests coverage
Options utiles: `coverage run --rcfile=.coveragerc --source=dir1,dir2`
## Merci !
Rejoignez-nous: [jobs.![voyages-sncf.com](voyages-sncf-logo.png)](https://jobs.voyages-sncf.com/)
Et suivez notre actu sur [![Logo OpenVSC](openVSC-logo.jpg)](https://open.voyages-sncf.com/)
Sources de cette présentation: <https://github.com/Lucas-C/meetup-afpy-nantes-luigi>
Note: comparaison avec Airflow: http://bytepawn.com/luigi-airflow-pinball.html
</textarea data-template>
</section>
</div>
</div>
<script src="lib/js/head.min.js"></script>
<script src="js/reveal.js"></script>
<script>
// More info about config & dependencies:
// - https://github.com/hakimel/reveal.js#configuration
// - https://github.com/hakimel/reveal.js#dependencies
var options = {
history: true,
dependencies: [
{ src: 'plugin/markdown/marked.js' },
{ src: 'plugin/markdown/markdown.js' },
{ src: 'plugin/notes/notes.js', async: true },
{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }
]
};
if (/showNotes/.test(location.search)) {
options.showNotes = true; // for PDF export to include notes
}
Reveal.initialize(options);
document.onclick = function (event) {
Reveal.next();
};
</script>
</body>
</html>