first comit
This commit is contained in:
331
README.md
Normal file
331
README.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# TP : Traitement d'images par lots (multiprocessing)
|
||||||
|
|
||||||
|
## Informations générales
|
||||||
|
|
||||||
|
**Cours** : Python Avancé > Programmation concurrente > Parallélisation de tâches CPU-bound avec multiprocessing \
|
||||||
|
**Objectifs pédagogiques** :
|
||||||
|
- Python avancé : programmation fonctionnelle
|
||||||
|
- Python avancé : utilisation de bibliothèques spécialisées (pathlib, pandas, pillow, etc.)
|
||||||
|
- Programmation concurrente : parallélisation de tâches CPU-bound avec multiprocessing
|
||||||
|
- Cryptographie et sécurité : hachage et vérification de l'intégrité des données
|
||||||
|
- Gestion des erreurs et logging avancé : capture d'exceptions ciblées, logging
|
||||||
|
- Tests : découverte de pytest
|
||||||
|
- Outils modernes (poetry, PyCharm)
|
||||||
|
- Bonnes pratiques de l'entreprise
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
### Installation et configuration de l’environnement
|
||||||
|
|
||||||
|
Installer les dépendances via `poetry install` depuis un terminal PyCharm.
|
||||||
|
|
||||||
|
Pour lancer les tests unitaires :
|
||||||
|
|
||||||
|
- `poetry run pytest -p no:warnings`
|
||||||
|
- `poetry run pytest tests/test_calculate_new_height.py -p no:warnings`
|
||||||
|
- `poetry run pytest tests/test_calculate_new_height.py::test_calculate_new_height_various_scenarios`
|
||||||
|
|
||||||
|
### Connaissances préalables
|
||||||
|
|
||||||
|
- Connaissances de base en programmation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Énoncé
|
||||||
|
|
||||||
|
L'objectif de ce TP est de concevoir et de réaliser un script Python robuste et performant pour le traitement d'images (redimensionnement) en lots.
|
||||||
|
Vous partirez d'un **squelette de script `process.py`** pour progressivement implémenter la logique métier, paralléliser l'exécution pour des performances maximales, et enfin ajouter un système de cache pour optimiser les traitements répétitifs.
|
||||||
|
|
||||||
|
Plus tard, nous pourrons utiliser les images générées pour alimenter un site web responsive, afin de charger les images les plus adaptées à la taille de l'écran de l'utilisateur, et ainsi améliorer les performances SEO (référencement naturel), et réduire le coût du SEA (référencement payant type Google Ads).
|
||||||
|
|
||||||
|
### Ce que l'on cherche à accomplir
|
||||||
|
|
||||||
|
Redimensionner des images en plusieurs tailles et formats, en utilisant la bibliothèque Pillow.
|
||||||
|
|
||||||
|
#### Exemples de redimensionnement
|
||||||
|
|
||||||
|
Exemple : à partir d'une image source `PIA04921.jpg` de 5184x3456 pixels, le script va générer les images suivantes :
|
||||||
|
- `PIA04921_4000x2667.png` (4000 pixels de large, format PNG)
|
||||||
|
- `PIA04921_4000x2667.webp` (4000 pixels de large, format WebP)
|
||||||
|
- `PIA04921_2500x1667.png` (2500 pixels de large, format PNG)
|
||||||
|
- `PIA04921_2500x1667.webp` (2500 pixels de large, format WebP)
|
||||||
|
- `PIA04921_1928x1286.png` (1928 pixels de large, format PNG)
|
||||||
|
- `PIA04921_1928x1286.webp` (1928 pixels de large, format WebP)
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
#### Version sans hachage (plus simple, mais moins optimisé)
|
||||||
|
|
||||||
|
Pour vous donner une idée du résultat final du script côté console, voici des exemples de console d'exécution du script.
|
||||||
|
|
||||||
|
Lancement du script, avec le mode d'exécution séquentiel (sans multiprocessing) :
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO - --- Mode d'exécution sélectionné : SEQUENTIAL ---
|
||||||
|
INFO - Trouvé 6 fichier(s) dans 'input_images'.
|
||||||
|
INFO - -> 6 fichier(s) à traiter.
|
||||||
|
INFO - Démarrage de 'Redimensionnement des images' (132 tâches) en mode séquentiel...
|
||||||
|
INFO - Tâche 'Redimensionnement des images' terminée en 53.42 secondes.
|
||||||
|
INFO - --- Résumé des temps ---
|
||||||
|
INFO - Temps de redimensionnement : 53.42 secondes
|
||||||
|
INFO - --------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
Avec le mode d'exécution parallèle (avec multiprocessing). C'est beaucoup plus rapide :
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO - --- Mode d'exécution sélectionné : PARALLEL ---
|
||||||
|
INFO - Trouvé 6 fichier(s) dans 'input_images'.
|
||||||
|
INFO - -> 6 fichier(s) à traiter.
|
||||||
|
INFO - Démarrage de 'Redimensionnement des images' (132 tâches) avec 19 processus...
|
||||||
|
INFO - Tâche 'Redimensionnement des images' terminée en 6.88 secondes.
|
||||||
|
INFO - --- Résumé des temps ---
|
||||||
|
INFO - Temps de redimensionnement : 6.88 secondes
|
||||||
|
INFO - --------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Version avec hachage (plus optimisé)
|
||||||
|
|
||||||
|
Lancement du script, avec le mode d'exécution séquentiel (sans multiprocessing) :
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO - --- Mode d'exécution sélectionné : SEQUENTIAL ---
|
||||||
|
INFO - Trouvé 6 fichier(s) dans 'input_images'.
|
||||||
|
INFO - Démarrage de 'Calcul des hashes' (6 tâches) en mode séquentiel...
|
||||||
|
INFO - Tâche 'Calcul des hashes' terminée en 0.08 secondes.
|
||||||
|
INFO - Filtrage des fichiers modifiés...
|
||||||
|
INFO - Fichier de hash 'hashes.csv' mis à jour.
|
||||||
|
INFO - -> 6 fichier(s) à traiter.
|
||||||
|
INFO - Démarrage de 'Redimensionnement des images' (132 tâches) en mode séquentiel...
|
||||||
|
INFO - Tâche 'Redimensionnement des images' terminée en 53.42 secondes.
|
||||||
|
INFO - --- Résumé des temps ---
|
||||||
|
INFO - Temps de hachage : 0.08 secondes
|
||||||
|
INFO - Temps de redimensionnement : 53.42 secondes
|
||||||
|
INFO - --------------------------
|
||||||
|
INFO - Traitement complet terminé en 53.51 secondes.
|
||||||
|
```
|
||||||
|
|
||||||
|
Avec le mode d'exécution parallèle (avec multiprocessing). C'est toujours beaucoup plus rapide :
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO - --- Mode d'exécution sélectionné : PARALLEL ---
|
||||||
|
INFO - Trouvé 6 fichier(s) dans 'input_images'.
|
||||||
|
INFO - Démarrage de 'Calcul des hashes' (6 tâches) avec 19 processus...
|
||||||
|
INFO - Tâche 'Calcul des hashes' terminée en 0.52 secondes.
|
||||||
|
INFO - Filtrage des fichiers modifiés...
|
||||||
|
INFO - Fichier de hash 'hashes.csv' mis à jour.
|
||||||
|
INFO - -> 6 fichier(s) à traiter.
|
||||||
|
INFO - Démarrage de 'Redimensionnement des images' (132 tâches) avec 19 processus...
|
||||||
|
INFO - Tâche 'Redimensionnement des images' terminée en 6.88 secondes.
|
||||||
|
INFO - --- Résumé des temps ---
|
||||||
|
INFO - Temps de hachage : 0.52 secondes
|
||||||
|
INFO - Temps de redimensionnement : 6.88 secondes
|
||||||
|
INFO - --------------------------
|
||||||
|
INFO - Traitement complet terminé en 7.40 secondes.
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans le cas où le hachage des images n'a pas changé, le script ira très vite (on ne redimensionne rien, on ne fait que calculer les hashes) :
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO - --- Mode d'exécution sélectionné : PARALLEL ---
|
||||||
|
INFO - Trouvé 6 fichier(s) dans 'input_images'.
|
||||||
|
INFO - Démarrage de 'Calcul des hashes' (6 tâches) avec 19 processus...
|
||||||
|
INFO - Tâche 'Calcul des hashes' terminée en 0.52 secondes.
|
||||||
|
INFO - Filtrage des fichiers modifiés...
|
||||||
|
INFO - Fichier de hash 'hashes.csv' mis à jour.
|
||||||
|
INFO - -> 0 fichier(s) à traiter.
|
||||||
|
INFO - Aucun fichier à redimensionner.
|
||||||
|
INFO - --- Résumé des temps ---
|
||||||
|
INFO - Temps de hachage : 0.52 secondes
|
||||||
|
INFO - Temps de redimensionnement : 0.00 secondes
|
||||||
|
INFO - --------------------------
|
||||||
|
INFO - Traitement complet terminé en 0.52 secondes.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Vision d'ensemble du script (sans la partie hachage)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Démarrage du script] --> B["Lister les fichiers source .jpg"]
|
||||||
|
B --> G[Générer les tâches de redimensionnement pour tous les fichiers]
|
||||||
|
G --> H{Aucune tâche de redimensionnement?}
|
||||||
|
H -- Non --> I["Exécuter le redimensionnement (parallèle ou séquentiel)"]
|
||||||
|
I --> J[Sauvegarder les nouvelles images]
|
||||||
|
J --> L[Fin]
|
||||||
|
H -- Oui --> L[Fin]
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Partie 1 : le redimensionnement d'images séquentiel
|
||||||
|
|
||||||
|
Le squelette du script `process.py` vous est fourni.
|
||||||
|
Votre première tâche est de compléter les sections marquées par des `TODO` pour implémenter la logique de base du traitement séquentiel.
|
||||||
|
|
||||||
|
1. **Prise en main de la structure :**
|
||||||
|
* Ouvrez le fichier `process.py` et prenez connaissance de sa structure : les imports, les constantes globales (`SOURCE_DIRECTORY`, `TARGET_DIRECTORY`, etc.) et les fonctions déjà définies.
|
||||||
|
* Remarquez que la fonction `calculate_new_height` est déjà implémentée.
|
||||||
|
|
||||||
|
2. **Création de la logique de redimensionnement :**
|
||||||
|
* Votre premier objectif est de compléter la fonction `resize_single_image`. Suivez les instructions laissées dans le commentaire `TODO` pour ouvrir, redimensionner et sauvegarder une image.
|
||||||
|
* Ensuite, dans la fonction `main`, complétez la section `TODO` qui liste les fichiers sources (`all_source_files`).
|
||||||
|
|
||||||
|
### Partie 2 : flexibilité et traitement par lots
|
||||||
|
|
||||||
|
Améliorez le script pour qu'il ne soit plus limité à une seule taille et un seul format.
|
||||||
|
|
||||||
|
1. **Configuration avancée :**
|
||||||
|
* Repérez en haut du script les deux listes de configuration déjà définies :
|
||||||
|
* `RESIZE_WIDTHS`: une liste d'entiers contenant toutes les largeurs de sortie souhaitées (ex: `[4000, 2500, 1928, 992, 768, 576, 480, 260, 150, 100, 50]` inspirée des breakpoints Bootstrap).
|
||||||
|
* `FORMATS`: une liste de chaînes de caractères pour les formats de sortie (ex: `["png", "webp"]`).
|
||||||
|
|
||||||
|
2. **Mise à jour de la logique :**
|
||||||
|
* Dans la fonction `main`, modifiez la section `TODO` pour construire la liste `resize_tasks`. Vous devez maintenant construire cette liste pour que **chaque image source** soit traitée pour **chaque largeur** de `RESIZE_WIDTHS` et sauvegardée dans **chaque format** de `FORMATS`.
|
||||||
|
* Adaptez le nommage des fichiers de sortie pour qu'il inclue la taille, par exemple : `{nom_original}_{largeur}x{hauteur}.{format}`.
|
||||||
|
|
||||||
|
Le schéma ci-dessous illustre comment une seule image source génère une multitude de tâches de redimensionnement :
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph "Entrées"
|
||||||
|
A["Fichier img.jpg"]
|
||||||
|
subgraph "Configuration"
|
||||||
|
B1[Largeur 4000]
|
||||||
|
B2[Largeur 2500]
|
||||||
|
B3[...]
|
||||||
|
C1["Format png"]
|
||||||
|
C2["Format webp"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Tâches générées"
|
||||||
|
D1["Tâche: img, 4000, png"]
|
||||||
|
D2["Tâche: img, 4000, webp"]
|
||||||
|
D3["Tâche: img, 2500, png"]
|
||||||
|
D4["Tâche: img, 2500, webp"]
|
||||||
|
D5[...]
|
||||||
|
end
|
||||||
|
|
||||||
|
A --> D1 & D2 & D3 & D4 & D5
|
||||||
|
B1 & C1 --> D1
|
||||||
|
B1 & C2 --> D2
|
||||||
|
B2 & C1 --> D3
|
||||||
|
B2 & C2 --> D4
|
||||||
|
```
|
||||||
|
|
||||||
|
> **ASTUCE POUR TESTER** : À ce stade, la logique de hachage n'est pas encore faite. Pour que votre script traite les images, vous pouvez temporairement forcer le traitement de tous les fichiers en ajoutant cette ligne dans `main()`, juste après avoir défini `all_source_files` :
|
||||||
|
> `files_to_process = all_source_files`
|
||||||
|
> Vous pourrez retirer cette ligne lorsque vous implémenterez la Partie 4.
|
||||||
|
|
||||||
|
À ce stade, votre script est fonctionnel, mais probablement lent si vous avez beaucoup d'images et de tailles cibles.
|
||||||
|
|
||||||
|
### Partie 3 : Parallélisation des tâches (Multiprocessing)
|
||||||
|
|
||||||
|
C'est le cœur du TP. Vous allez drastiquement accélérer le script en utilisant la programmation parallèle pour exploiter tous les cœurs de votre CPU.
|
||||||
|
|
||||||
|
La différence entre l'exécution séquentielle et parallèle est la suivante :
|
||||||
|
|
||||||
|
**Mode Séquentiel :** Les tâches sont exécutées les unes après les autres.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Démarrage] --> B[Tâche 1]
|
||||||
|
B --> C[Tâche 2]
|
||||||
|
C --> D[Tâche 3]
|
||||||
|
D --> E[...]
|
||||||
|
E --> F[Fin]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mode Parallèle :** Les tâches sont distribuées à un pool de processus (workers) et s'exécutent en même temps.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Démarrage] --> B[Pool de processus]
|
||||||
|
B --> T1[Tâche 1]
|
||||||
|
B --> T2[Tâche 2]
|
||||||
|
B --> T3[Tâche 3]
|
||||||
|
B --> T4[Tâche 4]
|
||||||
|
B --> T...[...]
|
||||||
|
|
||||||
|
subgraph "Exécution concurrente"
|
||||||
|
T1
|
||||||
|
T2
|
||||||
|
T3
|
||||||
|
T4
|
||||||
|
T...
|
||||||
|
end
|
||||||
|
|
||||||
|
T1 & T2 & T3 & T4 & T... --> E[Attente de la fin de toutes les tâches]
|
||||||
|
E --> F[Fin]
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Refactorisation du code :**
|
||||||
|
* L'architecture du code est déjà prête pour la parallélisation. Remarquez que la fonction "worker" `resize_single_image` est déjà conçue pour accepter un seul argument de type `Tuple`, ce qui est une condition requise pour `ProcessPoolExecutor.map`.
|
||||||
|
|
||||||
|
2. **Implémentation des exécuteurs :**
|
||||||
|
* Votre mission est de compléter les deux fonctions "runner" : `run_sequential` et `run_parallel`.
|
||||||
|
* Suivez les `TODO` dans `run_sequential` pour implémenter une simple boucle `for` qui exécute les tâches les unes après les autres.
|
||||||
|
* Suivez les `TODO` dans `run_parallel` pour utiliser `concurrent.futures.ProcessPoolExecutor` afin de distribuer les tâches sur plusieurs processus.
|
||||||
|
* Les deux fonctions doivent mesurer et retourner le temps d'exécution.
|
||||||
|
|
||||||
|
3. **Intégration et comparaison :**
|
||||||
|
* Dans `main`, la logique pour sélectionner le bon "runner" en fonction de la constante `EXECUTION_MODE` est déjà en place. Une fois vos runners implémentés, vous pourrez basculer entre `"parallel"` et `"sequential"` pour comparer les performances.
|
||||||
|
* Assurez-vous que l'appel au runner pour le redimensionnement est correctement effectué. Vous devriez constater un gain de performance spectaculaire en mode parallèle !
|
||||||
|
|
||||||
|
### Partie 4 (bonus) : optimisation avec un cache de hachage
|
||||||
|
|
||||||
|
Votre script est rapide, mais il retraite toutes les images à chaque lancement. Vous allez maintenant implémenter un système de cache pour ne traiter que les fichiers nouveaux ou modifiés.
|
||||||
|
|
||||||
|
Voici un schéma représentant la logique complète du script que vous allez construire :
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Démarrage du script] --> B["Lister les fichiers source .jpg"]
|
||||||
|
B --> C[Calculer les nouveaux hashes SHA3 des fichiers]
|
||||||
|
C --> D["Lire les anciens hashes depuis hashes.csv"]
|
||||||
|
D --> E{Fichiers modifiés ou nouveaux?}
|
||||||
|
E -- Oui --> F[Filtrer la liste des fichiers à traiter]
|
||||||
|
F --> G[Générer les tâches de redimensionnement]
|
||||||
|
G --> H{Aucune tâche de redimensionnement?}
|
||||||
|
H -- Non --> I["Exécuter le redimensionnement (parallèle ou séquentiel)"]
|
||||||
|
I --> J[Sauvegarder les nouvelles images]
|
||||||
|
J --> K["Mettre à jour hashes.csv avec les nouveaux hashes"]
|
||||||
|
K --> L[Fin]
|
||||||
|
E -- Non --> H
|
||||||
|
H -- Oui --> K
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Hachage de fichiers :**
|
||||||
|
* La fonction `compute_sha3_512` qui calcule l'empreinte numérique d'un fichier vous est déjà fournie. Notez qu'elle est optimisée pour lire les fichiers par blocs et qu'elle est, comme `resize_single_image`, prête pour la parallélisation.
|
||||||
|
|
||||||
|
2. **Mise en place du cache :**
|
||||||
|
* Complétez la fonction `get_hashes_from_csv` en suivant le `TODO`. Elle doit lire un fichier `hashes.csv` et retourner un dictionnaire des hashes existants. L'utilisation de la bibliothèque `pandas` est recommandée à cet effet.
|
||||||
|
* Dans la fonction `main`, suivez les `TODO` pour :
|
||||||
|
1. Préparer les `hash_tasks` pour tous les fichiers sources.
|
||||||
|
2. Appeler le "runner" pour exécuter ces tâches. Vous pourrez immédiatement appliquer le `run_parallel` que vous venez de créer pour accélérer également cette étape de hachage !
|
||||||
|
3. Implémenter la logique de filtrage pour ne garder que les fichiers modifiés, c'est à dire en comparant les nouveaux hashes aux anciens et ne gardant que les fichiers modifiés dans la liste `files_to_process`.
|
||||||
|
4. Sauvegarder les nouveaux hashes dans le fichier CSV.
|
||||||
|
|
||||||
|
Exemple de fichier `hashes.csv` généré par votre programme :
|
||||||
|
```
|
||||||
|
filename,hash
|
||||||
|
GSFC_20171208.jpg,c84a997754ef10a2b8a66d793372983180bdb961753d16ffa9037c32ef09db483ed67ff774863c6591b9120f05e31ddd9893d7e5ac59c1049a993c396af6baa4
|
||||||
|
ISS070E034016.jpg,82ffdf4f9c0b13bd42626f6026f268b4db078b3e26324038b925c5e563149b0d5e40124251378d0c6f085a4d877fb38a91465934d0f9c51aa34e82740168f7ee
|
||||||
|
ISS070E052303.jpg,4f8d0614830652a19e53571b26fa44d6f08cee386e19c69ab1347d2afc591e67cdbb48640e050fdf03ce44b1ce8ad8d43f26a2e407cd854205fb23de70ca52fc
|
||||||
|
PIA04921.jpg,775e80e7782f98cded5e4be2ee5933e7df73ef02470dbd017e1782bba7e01ad30b6c01e78ba825e218db316c8839233b5e9a0a7e8642a5c69e9949aa1f23a00b
|
||||||
|
PIA18033.jpg,2b6e0212d9185f58da5d7c2d37a732edb055a4032c1679857f00f2cd26bba16d91b5b54944333686bd72d79f0a33421c6edffad2f3c75ea30317a83d5b71b44f
|
||||||
|
PIA25163.jpg,c3f5d3268b2bc4ec810e084d2156492aee02f6d10d2950d52a5d7471a76de96cb9f5505e565b2b064b4d81cd14814416579e8ebb430d3e9a72346a3a175bf619
|
||||||
|
```
|
||||||
|
|
||||||
|
### Partie 5 : Tests unitaires
|
||||||
|
|
||||||
|
Pour garantir la fiabilité de votre code, nous allons examiner et utiliser des tests unitaires.
|
||||||
|
|
||||||
|
1. **Examen d'un test existant :**
|
||||||
|
* Le test `test_calculate_new_height_various_scenarios` vous est intégralement fourni.
|
||||||
|
* Analysez sa structure, notamment l'utilisation de `@pytest.mark.parametrize` et `pytest.raises`.
|
||||||
|
* Exécutez-le pour vous assurer que tout fonctionne.
|
||||||
|
|
||||||
|
2. **Ajout d'un test unitaire :**
|
||||||
|
* En s'inspirant du test existant, ajoutez un autre test `test_calculate_new_height_with_invalid_width_raises_error`, qui vérifie qu'une exception `ValueError` est bien levée pour des largeurs (`orig_width`) incorrectes : `0` et `-100`.
|
||||||
537
poetry.lock
generated
Normal file
537
poetry.lock
generated
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
groups = ["dev"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.10.0"
|
||||||
|
description = "Code coverage measurement for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbd823f7ea5286c26406ad9e54268544d82f3d1cadb6d4f3b85e9877f0cab1ef"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab3f7a5dbaab937df0b9e9e8ec6eab235ba9a6f29d71fd3b24335affaed886cc"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8c63aaf850523d8cbe3f5f1a5c78f689b223797bef902635f2493ab43498f36c"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c3133ce3fa84023f7c6921c4dca711be0b658784c5a51a797168229eae26172"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3747d1d0af85b17d3a156cd30e4bbacf893815e846dc6c07050e9769da2b138e"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:241923b350437f6a7cb343d9df72998305ef940c3c40009f06e05029a047677c"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13e82e499309307104d58ac66f9eed237f7aaceab4325416645be34064d9a2be"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf73cdde4f6c9cd4457b00bf1696236796ac3a241f859a55e0f84a4c58326a7f"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-win32.whl", hash = "sha256:2396e13275b37870a3345f58bce8b15a7e0a985771d13a4b16ce9129954e07d6"},
|
||||||
|
{file = "coverage-7.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d45c7c71fb3d2da92ab893602e3f28f2d1560cec765a27e1824a6e0f7e92cfd"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4abc01843581a6f9dd72d4d15761861190973a2305416639435ef509288f7a04"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2093297773111d7d748fe4a99b68747e57994531fb5c57bbe439af17c11c169"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58240e27815bf105bd975c2fd42e700839f93d5aad034ef976411193ca32dbfd"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d019eac999b40ad48521ea057958b07a9f549c0c6d257a20e5c7c4ba91af8d1c"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e0a1f5454bc80faf4ceab10d1d48f025f92046c9c0f3bec2e1a9dda55137f8"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a93dd7759c416dd1cc754123b926d065055cb9a33b6699e64a1e5bdfae1ff459"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7b3d737266048368a6ffd68f1ecd662c54de56535c82eb8f98a55ac216a72cbd"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:93227c2707cb0effd9163cd0d8f0d9ab628982f7a3e915d6d64c7107867b9a07"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-win32.whl", hash = "sha256:69270af3014ab3058ad6108c6d0e218166f568b5a7a070dc3d62c0a63aca1c4d"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c16bbb661a7b4dafac0ab69e44d6dbcc6a64c4d93aefd89edc6f8911b6ab4a"},
|
||||||
|
{file = "coverage-7.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:14e7c23fcb74ed808efb4eb48fcd25a759f0e20f685f83266d1df174860e4733"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2adcfdaf3b4d69b0c64ad024fe9dd6996782b52790fb6033d90f36f39e287df"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d7b27c2c0840e8eeff3f1963782bd9d3bc767488d2e67a31de18d724327f9f6"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ed50429786e935517570b08576a661fd79032e6060985ab492b9d39ba8e66ee"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7171c139ab6571d70460ecf788b1dcaf376bfc75a42e1946b8c031d062bbbad4"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a726aac7e6e406e403cdee4c443a13aed3ea3d67d856414c5beacac2e70c04e"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2886257481a14e953e96861a00c0fe7151117a523f0470a51e392f00640bba03"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:536578b79521e59c385a2e0a14a5dc2a8edd58761a966d79368413e339fc9535"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77fae95558f7804a9ceefabf3c38ad41af1da92b39781b87197c6440dcaaa967"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-win32.whl", hash = "sha256:97803e14736493eb029558e1502fe507bd6a08af277a5c8eeccf05c3e970cb84"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c73ab554e54ffd38d114d6bc4a7115fb0c840cf6d8622211bee3da26e4bd25d"},
|
||||||
|
{file = "coverage-7.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:3ae95d5a9aedab853641026b71b2ddd01983a0a7e9bf870a20ef3c8f5d904699"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d883fee92b9245c0120fa25b5d36de71ccd4cfc29735906a448271e935d8d86d"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c87e59e88268d30e33d3665ede4fbb77b513981a2df0059e7c106ca3de537586"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f669d969f669a11d6ceee0b733e491d9a50573eb92a71ffab13b15f3aa2665d4"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9582bd6c6771300a847d328c1c4204e751dbc339a9e249eecdc48cada41f72e6"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f97e9637dc7977842776fdb7ad142075d6fa40bc1b91cb73685265e0d31d32"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae4fa92b6601a62367c6c9967ad32ad4e28a89af54b6bb37d740946b0e0534dd"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3a5cc8b97473e7b3623dd17a42d2194a2b49de8afecf8d7d03c8987237a9552c"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc1cbb7f623250e047c32bd7aa1bb62ebc62608d5004d74df095e1059141ac88"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-win32.whl", hash = "sha256:1380cc5666d778e77f1587cd88cc317158111f44d54c0dd3975f0936993284e0"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf03cf176af098ee578b754a03add4690b82bdfe070adfb5d192d0b1cd15cf82"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:8041c78cd145088116db2329b2fb6e89dc338116c962fbe654b7e9f5d72ab957"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37cc2c06052771f48651160c080a86431884db9cd62ba622cab71049b90a95b3"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:91f37270b16178b05fa107d85713d29bf21606e37b652d38646eef5f2dfbd458"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f9b0b0168864d09bcb9a3837548f75121645c4cfd0efce0eb994c221955c5b10"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0be435d3b616e7d3ee3f9ebbc0d784a213986fe5dff9c6f1042ee7cfd30157"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35e9aba1c4434b837b1d567a533feba5ce205e8e91179c97974b28a14c23d3a0"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a0b0c481e74dfad631bdc2c883e57d8b058e5c90ba8ef087600995daf7bbec18"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8aec1b7c8922808a433c13cd44ace6fceac0609f4587773f6c8217a06102674b"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04ec59ceb3a594af0927f2e0d810e1221212abd9a2e6b5b917769ff48760b460"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-win32.whl", hash = "sha256:b6871e62d29646eb9b3f5f92def59e7575daea1587db21f99e2b19561187abda"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff99cff2be44f78920b76803f782e91ffb46ccc7fa89eccccc0da3ca94285b64"},
|
||||||
|
{file = "coverage-7.10.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3246b63501348fe47299d12c47a27cfc221cfbffa1c2d857bcc8151323a4ae4f"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:1f628d91f941a375b4503cb486148dbeeffb48e17bc080e0f0adfee729361574"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a0e101d5af952d233557e445f42ebace20b06b4ceb615581595ced5386caa78"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec4c1abbcc53f9f650acb14ea71725d88246a9e14ed42f8dd1b4e1b694e9d842"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9c95f3a7f041b4cc68a8e3fecfa6366170c13ac773841049f1cd19c8650094e0"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a2cd597b69c16d24e310611f2ed6fcfb8f09429316038c03a57e7b4f5345244"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5e18591906a40c2b3609196c9879136aa4a47c5405052ca6b065ab10cb0b71d0"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:485c55744252ed3f300cc1a0f5f365e684a0f2651a7aed301f7a67125906b80e"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4dabea1516e5b0e9577282b149c8015e4dceeb606da66fb8d9d75932d5799bf5"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-win32.whl", hash = "sha256:ac455f0537af22333fdc23b824cff81110dff2d47300bb2490f947b7c9a16017"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:b3c94b532f52f95f36fbfde3e178510a4d04eea640b484b2fe8f1491338dc653"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:2f807f2c3a9da99c80dfa73f09ef5fc3bd21e70c73ba1c538f23396a3a772252"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0a889ef25215990f65073c32cadf37483363a6a22914186dedc15a6b1a597d50"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c638ecf3123805bacbf71aff8091e93af490c676fca10ab4e442375076e483"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f2f2c0df0cbcf7dffa14f88a99c530cdef3f4fcfe935fa4f95d28be2e7ebc570"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:048d19a5d641a2296745ab59f34a27b89a08c48d6d432685f22aac0ec1ea447f"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1209b65d302d7a762004be37ab9396cbd8c99525ed572bdf455477e3a9449e06"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e44aa79a36a7a0aec6ea109905a4a7c28552d90f34e5941b36217ae9556657d5"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96124be864b89395770c9a14652afcddbcdafb99466f53a9281c51d1466fb741"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aad222e841f94b42bd1d6be71737fade66943853f0807cf87887c88f70883a2a"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-win32.whl", hash = "sha256:0eed5354d28caa5c8ad60e07e938f253e4b2810ea7dd56784339b6ce98b6f104"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3da35f9980058acb960b2644527cc3911f1e00f94d309d704b309fa984029109"},
|
||||||
|
{file = "coverage-7.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cb9e138dfa8a4b5c52c92a537651e2ca4f2ca48d8cb1bc01a2cbe7a5773c2426"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cf283ec9c6878826291b17442eb5c32d3d252dc77d25e082b460b2d2ea67ba3c"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a83488c9fc6fff487f2ab551f9b64c70672357b8949f0951b0cd778b3ed8165"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b86df3a7494d12338c11e59f210a0498d6109bbc3a4037f44de517ebb30a9c6b"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6de9b460809e5e4787b742e786a36ae2346a53982e2be317cdcb7a33c56412fb"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de5ef8a5954d63fa26a6aaa4600e48f885ce70fe495e8fce2c43aa9241fc9434"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f178fe5e96f1e057527d5d0b20ab76b8616e0410169c33716cc226118eaf2c4f"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4a38c42f0182a012fa9ec25bc6057e51114c1ba125be304f3f776d6d283cb303"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bf09beb5c1785cb36aad042455c0afab561399b74bb8cdaf6e82b7d77322df99"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-win32.whl", hash = "sha256:cb8dfbb5d3016cb8d1940444c0c69b40cdc6c8bde724b07716ee5ea47b5273c6"},
|
||||||
|
{file = "coverage-7.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:58ff22653cd93d563110d1ff2aef958f5f21be9e917762f8124d0e36f80f172a"},
|
||||||
|
{file = "coverage-7.10.0-py3-none-any.whl", hash = "sha256:310a786330bb0463775c21d68e26e79973839b66d29e065c5787122b8dd4489f"},
|
||||||
|
{file = "coverage-7.10.0.tar.gz", hash = "sha256:2768885aef484b5dcde56262cbdfba559b770bfc46994fe9485dc3614c7a5867"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
|
||||||
|
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numpy"
|
||||||
|
version = "2.3.1"
|
||||||
|
description = "Fundamental package for array computing in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.11"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"},
|
||||||
|
{file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"},
|
||||||
|
{file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"},
|
||||||
|
{file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"},
|
||||||
|
{file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"},
|
||||||
|
{file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
|
||||||
|
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pandas"
|
||||||
|
version = "2.3.1"
|
||||||
|
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97"},
|
||||||
|
{file = "pandas-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299"},
|
||||||
|
{file = "pandas-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679"},
|
||||||
|
{file = "pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96"},
|
||||||
|
{file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88"},
|
||||||
|
{file = "pandas-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d"},
|
||||||
|
{file = "pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""}
|
||||||
|
python-dateutil = ">=2.8.2"
|
||||||
|
pytz = ">=2020.1"
|
||||||
|
tzdata = ">=2022.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
|
||||||
|
aws = ["s3fs (>=2022.11.0)"]
|
||||||
|
clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
|
||||||
|
compression = ["zstandard (>=0.19.0)"]
|
||||||
|
computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
|
||||||
|
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||||
|
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
|
||||||
|
feather = ["pyarrow (>=10.0.1)"]
|
||||||
|
fss = ["fsspec (>=2022.11.0)"]
|
||||||
|
gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
|
||||||
|
hdf5 = ["tables (>=3.8.0)"]
|
||||||
|
html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
|
||||||
|
mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
|
||||||
|
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
|
||||||
|
parquet = ["pyarrow (>=10.0.1)"]
|
||||||
|
performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
|
||||||
|
plot = ["matplotlib (>=3.6.3)"]
|
||||||
|
postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
|
||||||
|
pyarrow = ["pyarrow (>=10.0.1)"]
|
||||||
|
spss = ["pyreadstat (>=1.2.0)"]
|
||||||
|
sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
|
||||||
|
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
|
||||||
|
xml = ["lxml (>=4.9.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "11.3.0"
|
||||||
|
description = "Python Imaging Library (Fork)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"},
|
||||||
|
{file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"},
|
||||||
|
{file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"},
|
||||||
|
{file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"},
|
||||||
|
{file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"},
|
||||||
|
{file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"},
|
||||||
|
{file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"},
|
||||||
|
{file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"},
|
||||||
|
{file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"},
|
||||||
|
{file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||||
|
fpx = ["olefile"]
|
||||||
|
mic = ["olefile"]
|
||||||
|
test-arrow = ["pyarrow"]
|
||||||
|
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
|
||||||
|
typing = ["typing-extensions ; python_version < \"3.10\""]
|
||||||
|
xmp = ["defusedxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
||||||
|
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["coverage", "pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
|
||||||
|
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.4.1"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
|
||||||
|
{file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
|
||||||
|
iniconfig = ">=1"
|
||||||
|
packaging = ">=20"
|
||||||
|
pluggy = ">=1.5,<2"
|
||||||
|
pygments = ">=2.7.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "6.2.1"
|
||||||
|
description = "Pytest plugin for measuring coverage."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"},
|
||||||
|
{file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
coverage = {version = ">=7.5", extras = ["toml"]}
|
||||||
|
pluggy = ">=1.2"
|
||||||
|
pytest = ">=6.2.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dateutil"
|
||||||
|
version = "2.9.0.post0"
|
||||||
|
description = "Extensions to the standard Python datetime module"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||||
|
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytz"
|
||||||
|
version = "2025.2"
|
||||||
|
description = "World timezone definitions, modern and historical"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"},
|
||||||
|
{file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.17.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||||
|
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.2"
|
||||||
|
description = "Provider of IANA time zone data"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
|
||||||
|
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = "^3.13"
|
||||||
|
content-hash = "7d05498c899519f4f05c0a6105d4ef33c146dd410b14138f08cfb020e7406590"
|
||||||
46
pyproject.toml
Normal file
46
pyproject.toml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
[project]
|
||||||
|
name = "tp-multiprocessing"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "TP Multiprocessing"
|
||||||
|
authors = [
|
||||||
|
{name = "Your Name",email = "you@example.com"}
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
]
|
||||||
|
keywords = ["multiprocessing", "concurrency"]
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
{ path = "tests", format = "wheel" }
|
||||||
|
]
|
||||||
|
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.13"
|
||||||
|
pandas = "^2.3.1"
|
||||||
|
pillow = "^11.3.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
pytest = "^8.4.1"
|
||||||
|
pytest-cov = "^6.2.1"
|
||||||
|
coverage = { version="*", extras=["toml"]}
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.pycln]
|
||||||
|
all = true
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
line_length = 120
|
||||||
|
multi_line_output = 3
|
||||||
|
include_trailing_comma = true
|
||||||
|
force_grid_wrap = 0
|
||||||
|
use_parentheses = true
|
||||||
|
ensure_newline_before_comments = true
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
BIN
resources/input_images.zip
Normal file
BIN
resources/input_images.zip
Normal file
Binary file not shown.
0
src/tp_multiprocessing/__init__.py
Normal file
0
src/tp_multiprocessing/__init__.py
Normal file
263
src/tp_multiprocessing/process.py
Normal file
263
src/tp_multiprocessing/process.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
"""
|
||||||
|
Script pour redimensionner des images en parallèle ou séquentiellement.
|
||||||
|
|
||||||
|
Ce script inclut :
|
||||||
|
|
||||||
|
- Calcul du hash SHA3-512 des fichiers pour détecter les modifications (la fonction de hachage est volontairement consommatrice en temps pour simuler une charge de travail).
|
||||||
|
- Redimensionnement des images à plusieurs largeurs cibles tout en préservant le ratio hauteur/largeur.
|
||||||
|
- Sauvegarde des images redimensionnées dans différents formats (PNG, WebP).
|
||||||
|
- Gestion des erreurs de lecture de fichiers et de redimensionnement.
|
||||||
|
- Utilisation de multiprocessing pour paralléliser les tâches de redimensionnement, et de hashing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import multiprocessing
|
||||||
|
import sys
|
||||||
|
import pandas as pd
|
||||||
|
from pathlib import Path
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
from PIL import Image
|
||||||
|
from typing import List, Tuple, Dict, Callable, Any, Optional
|
||||||
|
|
||||||
|
# --- CONFIGURATION ---
|
||||||
|
EXECUTION_MODE: str = "sequential" # Options : "parallel" ou "sequential"
|
||||||
|
SOURCE_DIRECTORY: Path = Path("../../resources/input_images/")
|
||||||
|
TARGET_DIRECTORY: Path = Path("../../resources/output_images/")
|
||||||
|
HASHES_CSV_PATH: Path = Path("../../resources/hash/hashes.csv")
|
||||||
|
RESIZE_WIDTHS: List[int] = [4000, 2500, 1928, 992, 768, 576, 480, 260, 150, 100, 50] # inspiré des breakpoints de Bootstrap
|
||||||
|
FORMATS: List[str] = ["png", "webp"]
|
||||||
|
FILENAME_COL: str = 'filename'
|
||||||
|
HASH_COL: str = 'hash'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging() -> None:
|
||||||
|
"""
|
||||||
|
Configure le système de logging pour le script.
|
||||||
|
"""
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
handlers=[
|
||||||
|
logging.StreamHandler(sys.stdout),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def calculate_new_height(orig_width: int, orig_height: int, new_width: int) -> int:
|
||||||
|
"""
|
||||||
|
Calcule la hauteur d'une image pour une nouvelle largeur donnée,
|
||||||
|
tout en préservant le ratio hauteur/largeur original.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
orig_width (int): la largeur originale de l'image.
|
||||||
|
orig_height (int): la hauteur originale de l'image.
|
||||||
|
new_width (int): la nouvelle largeur souhaitée pour l'image.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
int: la nouvelle hauteur calculée qui maintient le ratio de l'image.
|
||||||
|
"""
|
||||||
|
if orig_width <= 0:
|
||||||
|
raise ValueError("La largeur d'origine doit être positive.")
|
||||||
|
scale_ratio = new_width / orig_width
|
||||||
|
return int(orig_height * scale_ratio)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_sha3_512(args: Tuple[Path]) -> Tuple[Optional[str], Path]:
|
||||||
|
"""
|
||||||
|
Calcule l'empreinte numérique (hash) SHA3-512 d'un fichier.
|
||||||
|
La fonction lit le fichier par blocs pour gérer efficacement les fichiers volumineux.
|
||||||
|
L'argument est un tuple pour assurer la compatibilité avec `ProcessPoolExecutor.map`.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
args (Tuple[Path]): un tuple contenant le chemin du fichier à traiter.
|
||||||
|
Ex: (filepath,)
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
tuple (Tuple[Optional[str], Path]): un tuple contenant le hash hexadécimal (str) et le chemin du fichier (Path).
|
||||||
|
En cas d'erreur de lecture, retourne (None, filepath).
|
||||||
|
"""
|
||||||
|
filepath, = args
|
||||||
|
hasher = hashlib.sha3_512()
|
||||||
|
try:
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
for block in iter(lambda: f.read(65536), b''):
|
||||||
|
hasher.update(block)
|
||||||
|
return hasher.hexdigest(), filepath
|
||||||
|
except IOError as e:
|
||||||
|
logging.error("Erreur de lecture du fichier %s: %s", filepath.name, e)
|
||||||
|
return None, filepath
|
||||||
|
|
||||||
|
|
||||||
|
def resize_single_image(args: Tuple[Path, int, str]) -> str:
|
||||||
|
"""
|
||||||
|
Redimensionne une image à une largeur cible et la sauvegarde dans un format spécifié.
|
||||||
|
Le redimensionnement n'est effectué que si la largeur cible est inférieure à la largeur originale.
|
||||||
|
L'algorithme de rééchantillonnage LANCZOS est utilisé pour une haute qualité de réduction.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
args (Tuple[Path, int, str]): un tuple contenant les informations nécessaires :
|
||||||
|
- source_file (Path): Chemin de l'image d'origine.
|
||||||
|
- output_width (int): Largeur de l'image de sortie.
|
||||||
|
- fmt (str): Format de sortie souhaité (ex: "png", "webp").
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
str: un message de statut indiquant le résultat de l'opération (succès, échec, ignoré).
|
||||||
|
"""
|
||||||
|
source_file, output_width, fmt = args
|
||||||
|
# TODO : Partie 1 - implémenter la logique de redimensionnement.
|
||||||
|
# 1. Ouvrir l'image source avec Pillow (`Image.open`). Utiliser un bloc `with`.
|
||||||
|
# 2. Récupérer la largeur et la hauteur originales de l'image.
|
||||||
|
# 3. Vérifier si `output_width` est >= à la largeur originale. Si c'est le cas, retourner un message d'information.
|
||||||
|
# 4. Calculer la nouvelle hauteur en utilisant la fonction `calculate_new_height`.
|
||||||
|
# 5. Redimensionner l'image avec `img.resize`, en utilisant `Image.Resampling.LANCZOS`.
|
||||||
|
# 6. Construire le chemin de sortie du fichier (ex: f"{source_file.stem}_{output_width}x{new_height}.{fmt}").
|
||||||
|
# 7. Sauvegarder l'image redimensionnée.
|
||||||
|
# 8. Retourner un message de succès.
|
||||||
|
# 9. Encadrer la logique dans un bloc `try...except Exception` pour capturer les erreurs et retourner un message d'erreur.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def run_sequential(worker_function: Callable[[Any], Any], tasks: List[Any], description: str = "") -> Tuple[List[Any], float]:
|
||||||
|
"""
|
||||||
|
Exécute une série de tâches de manière séquentielle, l'une après l'autre.
|
||||||
|
Cette fonction est utile pour le débogage ou sur des systèmes mono-cœur.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
worker_function (Callable[[Any], Any]): la fonction à appliquer à chaque élément de la liste de tâches.
|
||||||
|
tasks (List[Any]): une liste d'arguments, où chaque argument est destiné à un appel de `worker_function`.
|
||||||
|
description (str, optional): une description de la tâche globale pour l'affichage.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
float: durée totale d'exécution en secondes.
|
||||||
|
"""
|
||||||
|
# TODO : Partie 3 - implémenter l'exécuteur séquentiel.
|
||||||
|
# 1. Logguer le message de démarrage.
|
||||||
|
# 2. Enregistrer le temps de début (`time.time()`).
|
||||||
|
# 3. Exécuter les tâches avec une list comprehension (ou boucle for): `[worker_function(task) for task in tasks]`.
|
||||||
|
# 4. Enregistrer le temps de fin.
|
||||||
|
# 5. Calculer la durée et logguer le message de fin.
|
||||||
|
# 6. Retourner la liste des résultats et la durée.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def run_parallel(worker_function: Callable[[Any], Any], tasks: List[Any], description: str = "") -> Tuple[List[Any], float]:
|
||||||
|
"""
|
||||||
|
Exécute une série de tâches en parallèle en utilisant un pool de processus.
|
||||||
|
Le nombre de processus est basé sur le nombre de cœurs CPU disponibles pour optimiser les performances.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
worker_function (Callable[[Any], Any]): la fonction à exécuter pour chaque tâche.
|
||||||
|
tasks (List[Any]): une liste d'arguments à passer à `worker_function`.
|
||||||
|
description (str, optional): une description de la tâche globale pour l'affichage.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
float: durée totale d'exécution en secondes.
|
||||||
|
"""
|
||||||
|
# TODO : Partie 3 - implémenter l'exécuteur parallèle.
|
||||||
|
# 1. Déterminer le nombre de workers (`multiprocessing.cpu_count()`).
|
||||||
|
# 2. Logguer le message de démarrage.
|
||||||
|
# 3. Enregistrer le temps de début.
|
||||||
|
# 4. Utiliser un `ProcessPoolExecutor` dans un bloc `with`, en passant `initializer=setup_logging`.
|
||||||
|
# 5. Appeler `executor.map(worker_function, tasks)` et convertir le résultat en liste.
|
||||||
|
# 6. Enregistrer le temps de fin.
|
||||||
|
# 7. Calculer la durée et logguer le message de fin.
|
||||||
|
# 8. Retourner la liste des résultats et la durée.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_hashes_from_csv() -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
Charge les hashes de fichiers précédemment calculés depuis un fichier CSV.
|
||||||
|
Permet de comparer les hashes actuels aux anciens pour détecter les fichiers modifiés.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
dict (Dict[str, str]) : un dictionnaire où les clés sont les noms de fichiers (str) et les valeurs
|
||||||
|
sont leurs hashes SHA3-512 (str). Retourne un dictionnaire vide si le
|
||||||
|
fichier CSV n'existe pas.
|
||||||
|
"""
|
||||||
|
# TODO : Partie 4 - implémenter la lecture du CSV de hashes.
|
||||||
|
# 1. Vérifier si `HASHES_CSV_PATH` existe. Si non, retourner un dictionnaire vide.
|
||||||
|
# 2. Utiliser un bloc `try...except` pour lire le CSV avec `pd.read_csv`.
|
||||||
|
# 3. Convertir le DataFrame en dictionnaire (ex: `pd.Series(df[HASH_COL].values, index=df[FILENAME_COL]).to_dict()`).
|
||||||
|
# 4. Retourner le dictionnaire. En cas d'erreur (fichier vide...), retourner un dictionnaire vide.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""
|
||||||
|
Point d'entrée principal du script.
|
||||||
|
Orchestre le processus de traitement d'images.
|
||||||
|
"""
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
# --- Initialisation ---
|
||||||
|
TARGET_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
||||||
|
HASHES_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
total_start_time = time.time()
|
||||||
|
|
||||||
|
# --- Sélection du mode d'exécution ---
|
||||||
|
if EXECUTION_MODE == "parallel":
|
||||||
|
runner = run_parallel
|
||||||
|
elif EXECUTION_MODE == "sequential":
|
||||||
|
runner = run_sequential
|
||||||
|
else:
|
||||||
|
logging.critical("Mode d'exécution inconnu : '%s'.", EXECUTION_MODE)
|
||||||
|
raise ValueError(f"Mode d'exécution inconnu : '{EXECUTION_MODE}'.")
|
||||||
|
logging.info("--- Mode d'exécution sélectionné : %s ---", EXECUTION_MODE.upper())
|
||||||
|
|
||||||
|
# TODO : Partie 1 - lister les fichiers sources dans SOURCE_DIRECTORY.
|
||||||
|
# Utiliser `SOURCE_DIRECTORY.iterdir()` avec une list comprehension (ou boucle for) pour ne garder que les fichiers (exclusion des dossiers) dans `all_source_files`.
|
||||||
|
all_source_files = []
|
||||||
|
logging.info("Trouvé %d fichier(s) dans '%s'.", len(all_source_files), SOURCE_DIRECTORY.name)
|
||||||
|
|
||||||
|
# La logique de Hachage et Filtrage sera implémentée en Partie 4.
|
||||||
|
# --- Hachage ---
|
||||||
|
# hash_tasks = [(f,) for f in all_source_files]
|
||||||
|
# hash_results, hash_duration = runner(compute_sha3_512, hash_tasks, "Calcul des hashes")
|
||||||
|
|
||||||
|
# --- Filtrage ---
|
||||||
|
logging.info("Filtrage des fichiers modifiés...")
|
||||||
|
# Tant qu'on n'a pas implémenté le hachage, on traite tous les fichiers.
|
||||||
|
files_to_process = all_source_files
|
||||||
|
# TODO : Partie 4 - implémenter la logique de filtrage.
|
||||||
|
# 1. Charger les anciens hashes depuis le fichier CSV en appelant `get_hashes_from_csv`.
|
||||||
|
# old_hashes = get_hashes_from_csv()
|
||||||
|
# files_to_process: List[Path] = []
|
||||||
|
# new_hash_records: List[Dict[str, str]] = []
|
||||||
|
#
|
||||||
|
# 2. Itérer sur `hash_results` pour comparer les hashes et remplir `files_to_process`.
|
||||||
|
# for result in hash_results:
|
||||||
|
# file_hash: Optional[str] = result[0]
|
||||||
|
# filepath: Path = result[1]
|
||||||
|
# ...
|
||||||
|
# 3. Préparer `new_hash_records` pour la sauvegarde.
|
||||||
|
#if new_hash_records:
|
||||||
|
# 4. Sauvegarder les nouveaux hashes dans le CSV avec pandas.
|
||||||
|
# logging.info("-> %d fichier(s) à traiter.", len(files_to_process))
|
||||||
|
|
||||||
|
# --- Redimensionnement ---
|
||||||
|
resize_duration = 0.0
|
||||||
|
if files_to_process:
|
||||||
|
# TODO : Partie 2 - préparer les tâches de redimensionnement (triple boucle : chaque fichier d'entrée, chaque largeur, chaque format).
|
||||||
|
resize_tasks = []
|
||||||
|
resize_duration = runner(resize_single_image, resize_tasks, "Redimensionnement des images")
|
||||||
|
else:
|
||||||
|
logging.info("Aucun fichier à redimensionner.")
|
||||||
|
|
||||||
|
# --- Résumé Final ---
|
||||||
|
total_duration = time.time() - total_start_time
|
||||||
|
logging.info("--- Résumé des temps ---")
|
||||||
|
#logging.info(f"Temps de hachage : {hash_duration:.2f} secondes")
|
||||||
|
logging.info(f"Temps de redimensionnement : {resize_duration:.2f} secondes")
|
||||||
|
logging.info("--------------------------")
|
||||||
|
logging.info(f"Traitement complet terminé en {total_duration:.2f} secondes.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
multiprocessing.freeze_support()
|
||||||
|
main()
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
41
tests/test_calculate_new_height.py
Normal file
41
tests/test_calculate_new_height.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from tp_multiprocessing.process import calculate_new_height
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# ## Tests des cas de succès et des cas limites ##
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("orig_width, orig_height, new_width, expected_height", [
|
||||||
|
# Cas 1: Réduction standard (ratio 16:9)
|
||||||
|
(1920, 1080, 960, 540),
|
||||||
|
|
||||||
|
# Cas 2: Format portrait
|
||||||
|
(800, 1200, 400, 600),
|
||||||
|
|
||||||
|
# Cas 3: Pas de redimensionnement (la nouvelle largeur est identique)
|
||||||
|
(1024, 768, 1024, 768),
|
||||||
|
|
||||||
|
# Cas 4: Le calcul du ratio produit un flottant (test de la conversion en int)
|
||||||
|
(300, 200, 100, 66), # 200 * (100/300) = 66.66... -> 66
|
||||||
|
|
||||||
|
# Cas 5: Agrandissement (upscaling)
|
||||||
|
(100, 100, 250, 250),
|
||||||
|
|
||||||
|
# Cas 6: Hauteur d'origine nulle
|
||||||
|
(1000, 0, 500, 0)
|
||||||
|
])
|
||||||
|
def test_calculate_new_height_various_scenarios(orig_width, orig_height, new_width, expected_height):
|
||||||
|
"""
|
||||||
|
Vérifie le calcul de la nouvelle hauteur pour plusieurs scénarios valides.
|
||||||
|
"""
|
||||||
|
assert calculate_new_height(orig_width, orig_height, new_width) == expected_height
|
||||||
|
|
||||||
|
# ## Tests des cas d'erreur ##
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("invalid_width", [0, -1, -100])
|
||||||
|
def test_calculate_new_height_with_invalid_width_raises_error(invalid_width):
|
||||||
|
"""
|
||||||
|
Vérifie qu'une ValueError est levée si la largeur d'origine est nulle ou négative.
|
||||||
|
"""
|
||||||
|
# Le contexte `pytest.raises` vérifie qu'une exception est bien levée.
|
||||||
|
# L'argument `match` vérifie que le message d'erreur contient le texte attendu.
|
||||||
|
with pytest.raises(ValueError, match="La largeur d'origine doit être positive"):
|
||||||
|
calculate_new_height(invalid_width, 1080, 500)
|
||||||
Reference in New Issue
Block a user