-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Registre d'arrêts : première ébauche #4393
Conversation
2acbef3
to
274dd93
Compare
Supporte que les GTFS.
Extract code shared with the `Transport.Jobs.GtfsToDB` job.
274dd93
to
604202c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
J'ai fait une première passe rapide sur la PR (en mode draft
mais comme vu avec @ptitfred ça n'a pas bougé depuis quelques temps).
Notes de relecture
(ça pourra servir au reste de @etalab/transport-tech qui lira sûrement cette PR à un moment).
- Voici les logs suite à un démarrage en local https://gist.github.com/thbar/85b123e8a2c819a93d9a8c53fbb141a9
- On itèrera un prochain tour sur les différents types d'erreurs (je vois qu'il y a des soucis d'encodage latin1 dans certains fichiers etc), comme écrit c'est un premier tour.
- Voilà le fichier généré en local, pour relecture registre-arrets.csv.zip
- Et un court extrait de l'output
main_id,display_name,data_source_id,data_source_format,parent_id,latitude,longitude,projection,stop_type
main:FR:52121:StopPlace:genARCOM@CHTDEG:CHT,De Gaulle,PAN:resource:80411,netex,,48.073291,5.1465855,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTOUDI:CHT,Oudinot,PAN:resource:80411,netex,,48.0784048,5.1455719,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTQUEL:CHT,Quellemele,PAN:resource:80411,netex,,48.07965605,5.14367025,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTBROT:CHT,Brottes,PAN:resource:80411,netex,,48.0821128,5.1281127,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTGROU:CHT,Groupama,PAN:resource:80411,netex,,48.0855051,5.13067385,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTTHBIS:CHT,THOMAS,PAN:resource:80411,netex,,48.0818889,5.135882,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTPAQU:CHT,Paquerettes,PAN:resource:80411,netex,,48.08639315,5.13853285,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTFARA:CHT,Faraday,PAN:resource:80411,netex,,48.08933075,5.13727315,utm_wgs84,stop
main:FR:52121:StopPlace:genARCOM@CHTROCH:CHT,Rochotte,PAN:resource:80411,netex,,48.0917009,5.1381297,utm_wgs84,stop
Points de review
- Je vois que ça a fini en erreur qui stoppe le processing, je me demande si on peut l'intercepter.
[debug] extract_from_archive Elixir.Transport.Registry.GTFS PAN:resource:82277 /var/folders/tl/1p_8qmn13cbgh_539g1yxyyc0000gn/T/db8c162d-7fa4-4cc8-bffb-d791b569bf61.dat
[debug] Valid Zip archive
** (NimbleCSV.ParseError) unexpected escape character " in "\"MAISON-PONTHIEU - 40\",,50.204352,2.039905,,,,,,0\r\n"
(nimble_csv 1.2.0) lib/nimble_csv.ex:583: NimbleCSV.RFC4180.escape/6
(nimble_csv 1.2.0) lib/nimble_csv.ex:453: anonymous fn/4 in NimbleCSV.RFC4180.parse_stream/2
(elixir 1.16.2) lib/stream.ex:990: Stream.do_transform_user/6
(elixir 1.16.2) lib/stream.ex:943: Stream.do_transform/5
(elixir 1.16.2) lib/enum.ex:4396: Enum.reverse/1
(elixir 1.16.2) lib/enum.ex:3728: Enum.to_list/1
(transport 0.0.1) lib/registry/gtfs.ex:30: Transport.Registry.GTFS.extract_from_archive/2
(elixir 1.16.2) lib/stream.ex:613: anonymous fn/4 in Stream.map/2
(elixir 1.16.2) lib/enum.ex:4839: Enumerable.List.reduce/3
(elixir 1.16.2) lib/stream.ex:1027: Stream.do_transform_inner_list/7
(elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
(elixir 1.16.2) lib/stream.ex:943: Stream.do_transform/5
(elixir 1.16.2) lib/stream.ex:1828: Enumerable.Stream.do_each/4
(elixir 1.16.2) lib/stream.ex:585: Stream.do_into/4
(elixir 1.16.2) lib/stream.ex:690: Stream.run/1
scripts/registre-arrets.exs:1: (file)
- J'ai déployé sur
prochainement
pour aller tester - Je trouverais ça plus pratique d'avoir en identifiant de la ressource, le UUID datagouv, que notre identifiant "entier" à nous @ptitfred - ça m'a régulièrement évité de passer par un script / indirection pour retrouver le UUID derrière
Beau boulot otherwise, on en reparle cet après-midi !
|
||
case value do | ||
nil -> default_value | ||
"" -> default_value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Est-ce qu'il y a un trim
en amont ? J'imagine qu'on pourrait dans certains cas tomber sur des valeurs comme " "
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(et je vois après-coup que c'est un refacto d'un code existant https://github.com/etalab/transport-site/pull/4393/files#diff-6ddccc89023492259f2e7204a077024fb50745d892994fdd85f2293a639f8887).
Transform the stream outputed by Unzip to a stream of maps, each map | ||
corresponding to a row from the CSV. | ||
""" | ||
def to_stream_of_maps(file_stream) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je me suis demandé si file_stream
"is a" %File.Stream{}.
Avec l'arrivée de Elixir 1.18+ et du typage, j'ai l'impression que mettre les types de structs en paramètre va être une bonne idée.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C'est un enumerable de iodata (pas sûr du type en elixir).
# transform the stream to a stream of maps %{column_name1: value1, ...} | ||
|> Stream.transform([], fn r, acc -> | ||
if acc == [] do | ||
{%{}, r |> Enum.map(fn h -> h |> String.replace_prefix("\uFEFF", "") end)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je ne sais pas si tu as vu qu'on peut trimmer le BOM directement à l'ouverture du stream:
https://hexdocs.pm/elixir/1.18.1/File.html#stream!/3-byte-order-marks-and-read-offset
File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])
Mais peut-être que tu as déjà vu et que ce n'est pas pratique etc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non ; j'ai repris naïvement du code existant.
def error(message), do: {:error, message} | ||
|
||
@spec cat_results(Stream.t(t(term()))) :: Stream.t(term()) | ||
def cat_results(enumerable), do: Stream.flat_map(enumerable, &keep_ok/1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Le nommage m'a un peu pris de court : en voyant cat
je crois que j'avais associé ça à une opération sans side-effect (comme cat
dans le shell), mais en pratique ça reformatte la donnée.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require Logger | ||
|
||
@spec execute(output_file :: Path.t(), list()) :: :ok | ||
def execute(output_file, opts \\ []) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vu que c'est l'entry point du script, quand ça sera fini fini j'ajouterais bien une doc sur la méthode.
J'attrape l'erreur. C'est pas forcément la meilleure solution mais ça évite de saborder le reste de l'export. |
J'ai fait. A noter qu'avec le resource_id on a la même traçabilité (voire même meilleure avec le resource_history_id). Peut-être est-ce moins pratique pour un tiers ? |
Merci ! Oui c'est moins pratique, avec l'identifiant data gouv on peut plus facilement récupérer la ressource elle-même via l'API data gouv. Un deuxième argument + secondaire, c'est l'homogénéité avec d'autres consolidations, où dans certains cas on n'a même pas de |
@ptitfred merci pour les updates ! Comme vu en DM, ça lève une autre erreur à présent, liée je pense à une limite en file descriptor sur Mac (mais ça serait très probablement idem ailleurs avec la même limite). C'est intéressant de l'avoir vu en local, car on a un "long running process" où ça leakerait en production aussi.
Je pense directement à quelque chose d'analogue à: avec un leak de fichiers ouverts pour la lecture des GTFS, vu les logs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sur la machine où ça plantait, tes derniers changements @ptitfred font que ça va au bout ! Plus d'erreur "emfile" en bout de script.
En relisant le code je pense qu'il reste des cas où ça va leaker et je propose un refacto pour supprimer les leaks restants (approche utilisée sur le parseur NeTEx).
Extract stops from GTFS ressource. | ||
""" | ||
def extract_from_archive(data_source_id, archive) do | ||
case file_stream(archive) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Le nouveau code va traiter la grande majorité des leaks, mais il va en rester avec cette nouvelle version à mon avis. Le cas {:error, ...}
au minimum va leaker, et notamment dans les cas où l'archive peut être ouverte avec succès, mais sa lecture zip technique ici échoue pour X raison:
Le point pernicieux c'est que ça sera dur à constater (beaucoup moins visible) sur la production, mais ça finira par se produire et redémarrer toute l'application (qui aura épuisé les file descriptors disponibles), de façon régulière.
Je te propose @ptitfred d'utiliser l'approche (voire, le code même, il est pas spécifique à NeTEx en pratique) ici, qui fait un wrapping systématique avec after
dans 100% des cas:
transport-site/apps/transport/lib/netex/netex_archive_parser.ex
Lines 52 to 61 in 4897113
def with_zip_file_handle(zip_file_name, cb) do | |
zip_file = Unzip.LocalFile.open(zip_file_name) | |
try do | |
{:ok, unzip} = Unzip.new(zip_file) | |
cb.(unzip) | |
after | |
Unzip.LocalFile.close(zip_file) | |
end | |
end |
Pas sûr que mon explication soit super claire, hésite pas si besoin !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je voulais faire ça au début mais on doit attendre que le fichier soit utilisé dans le prochain itérateur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
J'ai poussé le close en cas d'erreur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On en a discuté en point dév, sounds good !
e187dbf
to
d5cd80a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On est bon !
Note pour @etalab/transport-tech : cette PR crée un fichier mais le code n'est pas invoqué en production. On le déploie ce qui permettra d'aller tester sur la production en console, et on va aussi travailler sur un job qui va "pousser" ce fichier à un endroit déterminé sur S3 (et qui poussera d'ailleurs aussi les artefacts produits par #4397.
Voir l'epic #4354.
Formats supportés :
Résultat : fichier csv sans déduplication (les ids sont ceux issus des ressources) ni rapprochement géographique.
Manquent des tests unitaires.