Filtre les pièces techniques et documente l’étape 28
This commit is contained in:
parent
812fd4a862
commit
74f8fa57e1
10
README.md
10
README.md
@ -164,14 +164,14 @@ Le script lit `data/intermediate/parts_filtered.csv` et `data/final/stats.csv` (
|
|||||||
2. `python -m scripts.plot_colors_grid`
|
2. `python -m scripts.plot_colors_grid`
|
||||||
|
|
||||||
Le script lit `data/intermediate/parts_filtered.csv` et `data/raw/colors.csv`, puis génère deux visuels : `figures/step12/colors_grid.png` pour l'ensemble des pièces (rechanges incluses) et `figures/step12/colors_grid_minifigs.png` pour la seule palette des minifigs. Les couleurs sont triées perceptuellement et mises en scène sur une grille hexagonale.
|
Le script lit `data/intermediate/parts_filtered.csv` et `data/raw/colors.csv`, puis génère deux visuels : `figures/step12/colors_grid.png` pour l'ensemble des pièces (rechanges incluses) et `figures/step12/colors_grid_minifigs.png` pour la seule palette des minifigs. Les couleurs sont triées perceptuellement et mises en scène sur une grille hexagonale.
|
||||||
Les codes couleurs 0033B2 et 05131D sont ignorés pour ne pas polluer les palettes.
|
Les codes couleurs 0033B2 et 05131D sont ignorés, et les pièces considérées comme techniques/structurelles (catégories Technic, roues, tubes, axes, etc.) sont filtrées afin de ne garder que les éléments « visibles » pour l’esthétique.
|
||||||
|
|
||||||
### Étape 13 : palette de couleurs par set
|
### Étape 13 : palette de couleurs par set
|
||||||
|
|
||||||
1. `source .venv/bin/activate`
|
1. `source .venv/bin/activate`
|
||||||
2. `python -m scripts.build_colors_by_set`
|
2. `python -m scripts.build_colors_by_set`
|
||||||
|
|
||||||
Le script agrège `data/intermediate/parts_filtered.csv` avec les libellés de couleurs `data/raw/colors.csv` et produit `data/intermediate/colors_by_set.csv` contenant, pour chaque set et chaque couleur, les quantités totales, hors rechanges, issues des minifigs et hors minifigs. Ce fichier sert de base aux visualisations et matrices de palette.
|
Le script agrège `data/intermediate/parts_filtered.csv` avec les libellés de couleurs `data/raw/colors.csv` et produit `data/intermediate/colors_by_set.csv` contenant, pour chaque set et chaque couleur, les quantités totales, hors rechanges, issues des minifigs et hors minifigs. Les couleurs ignorées (0033B2, 05131D) et les pièces techniques/structurelles sont exclues. Ce fichier sert de base aux visualisations et matrices de palette.
|
||||||
|
|
||||||
### Étape 14 : évolution annuelle des palettes
|
### Étape 14 : évolution annuelle des palettes
|
||||||
|
|
||||||
@ -187,6 +187,7 @@ Le script lit `data/intermediate/colors_by_set.csv` et produit deux agrégats :
|
|||||||
|
|
||||||
Le script lit les agrégats de l'étape 14 et produit `figures/step15/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes), `figures/step15/colors_heatmap_linear.png` (heatmap année × couleur en quantités brutes) et `figures/step15/colors_heatmap_log.png` (heatmap avec échelle log1p).
|
Le script lit les agrégats de l'étape 14 et produit `figures/step15/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes), `figures/step15/colors_heatmap_linear.png` (heatmap année × couleur en quantités brutes) et `figures/step15/colors_heatmap_log.png` (heatmap avec échelle log1p).
|
||||||
Une troisième variante normalise les quantités par année : `figures/step15/colors_heatmap_share.png`. Dans cette vue, chaque colonne (année) est ramenée à une part relative (0–1) du total de pièces de l'année. Cela met en évidence la structure de palette indépendamment du volume : deux années restent comparables même si leur nombre total de pièces diffère fortement, mais l'information de volume absolu n'apparaît plus (à privilégier pour les comparaisons de proportions, pas pour mesurer la rareté volumique).
|
Une troisième variante normalise les quantités par année : `figures/step15/colors_heatmap_share.png`. Dans cette vue, chaque colonne (année) est ramenée à une part relative (0–1) du total de pièces de l'année. Cela met en évidence la structure de palette indépendamment du volume : deux années restent comparables même si leur nombre total de pièces diffère fortement, mais l'information de volume absolu n'apparaît plus (à privilégier pour les comparaisons de proportions, pas pour mesurer la rareté volumique).
|
||||||
|
Toutes les vues héritent du filtrage des couleurs ignorées et des pièces techniques/structurelles appliqué en amont.
|
||||||
|
|
||||||
### Étape 16 : couleurs de peau des minifigs
|
### Étape 16 : couleurs de peau des minifigs
|
||||||
|
|
||||||
@ -194,6 +195,7 @@ Une troisième variante normalise les quantités par année : `figures/step15/co
|
|||||||
2. `python -m scripts.compute_minifig_heads`
|
2. `python -m scripts.compute_minifig_heads`
|
||||||
|
|
||||||
Le script identifie les têtes de minifigs via la catégorie Rebrickable dédiée (part_cat_id 59 dans `data/raw/parts.csv`), filtre les pièces de rechange, puis agrège leurs couleurs depuis `data/intermediate/parts_filtered.csv`. Les sorties sont `data/intermediate/minifig_heads_by_set.csv` (quantités de têtes par set, couleur et année) et `data/intermediate/minifig_heads_by_year.csv` (agrégées par année). Ces fichiers serviront de base pour analyser l'évolution des teintes de peau (ou assimilées) des minifigs.
|
Le script identifie les têtes de minifigs via la catégorie Rebrickable dédiée (part_cat_id 59 dans `data/raw/parts.csv`), filtre les pièces de rechange, puis agrège leurs couleurs depuis `data/intermediate/parts_filtered.csv`. Les sorties sont `data/intermediate/minifig_heads_by_set.csv` (quantités de têtes par set, couleur et année) et `data/intermediate/minifig_heads_by_year.csv` (agrégées par année). Ces fichiers serviront de base pour analyser l'évolution des teintes de peau (ou assimilées) des minifigs.
|
||||||
|
Les couleurs ignorées (0033B2, 05131D) sont écartées lors de l’agrégation.
|
||||||
|
|
||||||
### Étape 17 : visualiser les couleurs de peau des minifigs
|
### Étape 17 : visualiser les couleurs de peau des minifigs
|
||||||
|
|
||||||
@ -277,3 +279,7 @@ Un second export `data/intermediate/minifigs_per_set_timeline.csv` est généré
|
|||||||
2. `python -m scripts.plot_set_color_swatches`
|
2. `python -m scripts.plot_set_color_swatches`
|
||||||
|
|
||||||
Le script lit `data/intermediate/colors_by_set.csv` (hors rechanges) et `data/intermediate/sets_enriched.csv`, sélectionne pour chaque set les 5 couleurs les plus présentes en excluant les pièces de minifigs (`quantity_non_minifig`), écrit `data/intermediate/set_color_swatches.csv`, puis trace `figures/step27/set_color_swatches.png` affichant chaque set avec ses 5 pastilles de couleurs dominantes.
|
Le script lit `data/intermediate/colors_by_set.csv` (hors rechanges) et `data/intermediate/sets_enriched.csv`, sélectionne pour chaque set les 5 couleurs les plus présentes en excluant les pièces de minifigs (`quantity_non_minifig`), écrit `data/intermediate/set_color_swatches.csv`, puis trace `figures/step27/set_color_swatches.png` affichant chaque set avec ses 5 pastilles de couleurs dominantes.
|
||||||
|
|
||||||
|
### Étape 28 : palettes perceptuelles par set (en préparation)
|
||||||
|
|
||||||
|
Objectif : produire une palette de 5 couleurs « perceptuelles » par set, moins biaisée par le volume de pièces. L’étape s’appuiera sur les mêmes filtres (couleurs 0033B2/05131D exclues, pièces techniques/structurelles ignorées), pondérera les couleurs par parts relatives hors minifigs, appliquera un tri perceptuel et une sélection diversifiée pour refléter l’esthétique plutôt que le poids en pièces. La version volumique (`figures/step27/set_color_swatches.png`) reste disponible en attendant la finalisation de cette étape.
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from matplotlib.lines import Line2D
|
|||||||
|
|
||||||
from lib.filesystem import ensure_parent_dir
|
from lib.filesystem import ensure_parent_dir
|
||||||
from lib.color_sort import lab_sort_key, sort_hex_colors_lab
|
from lib.color_sort import lab_sort_key, sort_hex_colors_lab
|
||||||
from lib.rebrickable.color_ignores import is_ignored_color_rgb
|
from lib.rebrickable.color_ignores import is_ignored_color_rgb, is_ignored_part_category
|
||||||
from lib.rebrickable.parts_inventory import normalize_boolean
|
from lib.rebrickable.parts_inventory import normalize_boolean
|
||||||
from lib.rebrickable.stats import read_rows
|
from lib.rebrickable.stats import read_rows
|
||||||
|
|
||||||
@ -21,18 +21,38 @@ def sort_colors_perceptually(colors: Iterable[dict]) -> List[dict]:
|
|||||||
return sorted(colors, key=lambda color: index_map[color["color_rgb"]])
|
return sorted(colors, key=lambda color: index_map[color["color_rgb"]])
|
||||||
|
|
||||||
|
|
||||||
def load_used_colors(parts_path: Path, colors_path: Path, minifig_only: bool = False) -> List[dict]:
|
def load_part_categories(parts_catalog_path: Path) -> Dict[str, str]:
|
||||||
|
"""Indexe les catégories par part_num."""
|
||||||
|
categories: Dict[str, str] = {}
|
||||||
|
with parts_catalog_path.open() as catalog_file:
|
||||||
|
import csv
|
||||||
|
|
||||||
|
reader = csv.DictReader(catalog_file)
|
||||||
|
for row in reader:
|
||||||
|
categories[row["part_num"]] = row["part_cat_id"]
|
||||||
|
return categories
|
||||||
|
|
||||||
|
|
||||||
|
def load_used_colors(
|
||||||
|
parts_path: Path,
|
||||||
|
colors_path: Path,
|
||||||
|
parts_catalog_path: Path,
|
||||||
|
minifig_only: bool = False,
|
||||||
|
) -> List[dict]:
|
||||||
"""Charge les couleurs utilisées (hors rechanges) et leurs quantités totales.
|
"""Charge les couleurs utilisées (hors rechanges) et leurs quantités totales.
|
||||||
|
|
||||||
Si minifig_only est vrai, ne conserve que les pièces marquées is_minifig_part=true.
|
Si minifig_only est vrai, ne conserve que les pièces marquées is_minifig_part=true.
|
||||||
Sinon, exclut les pièces de minifig.
|
Sinon, exclut les pièces de minifig.
|
||||||
"""
|
"""
|
||||||
rows = read_rows(parts_path)
|
rows = read_rows(parts_path)
|
||||||
|
categories = load_part_categories(parts_catalog_path)
|
||||||
colors_lookup = {(row["rgb"], normalize_boolean(row["is_trans"])): row["name"] for row in read_rows(colors_path)}
|
colors_lookup = {(row["rgb"], normalize_boolean(row["is_trans"])): row["name"] for row in read_rows(colors_path)}
|
||||||
totals: Dict[Tuple[str, str], int] = {}
|
totals: Dict[Tuple[str, str], int] = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if is_ignored_color_rgb(row["color_rgb"]):
|
if is_ignored_color_rgb(row["color_rgb"]):
|
||||||
continue
|
continue
|
||||||
|
if is_ignored_part_category(categories[row["part_num"]]):
|
||||||
|
continue
|
||||||
if minifig_only and row.get("is_minifig_part") != "true":
|
if minifig_only and row.get("is_minifig_part") != "true":
|
||||||
continue
|
continue
|
||||||
if not minifig_only and row.get("is_minifig_part") == "true":
|
if not minifig_only and row.get("is_minifig_part") == "true":
|
||||||
@ -85,11 +105,12 @@ def build_background(width: float, height: float, resolution: int = 600) -> np.n
|
|||||||
def plot_colors_grid(
|
def plot_colors_grid(
|
||||||
parts_path: Path,
|
parts_path: Path,
|
||||||
colors_path: Path,
|
colors_path: Path,
|
||||||
|
parts_catalog_path: Path,
|
||||||
destination_path: Path,
|
destination_path: Path,
|
||||||
minifig_only: bool = False,
|
minifig_only: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Dessine une grille artistique des couleurs utilisées."""
|
"""Dessine une grille artistique des couleurs utilisées."""
|
||||||
colors = load_used_colors(parts_path, colors_path, minifig_only=minifig_only)
|
colors = load_used_colors(parts_path, colors_path, parts_catalog_path, minifig_only=minifig_only)
|
||||||
positions = build_hex_positions(len(colors))
|
positions = build_hex_positions(len(colors))
|
||||||
x_values = [x for x, _ in positions]
|
x_values = [x for x, _ in positions]
|
||||||
y_values = [y for _, y in positions]
|
y_values = [y for _, y in positions]
|
||||||
|
|||||||
@ -1,8 +1,54 @@
|
|||||||
"""Couleurs à exclure des palettes."""
|
"""Couleurs et catégories de pièces à exclure des palettes."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
|
||||||
IGNORED_COLOR_RGB = {"0033b2", "05131d"}
|
IGNORED_COLOR_RGB = {"0033b2", "05131d"}
|
||||||
|
IGNORED_PART_CATEGORY_IDS = {
|
||||||
|
"1", # Baseplates
|
||||||
|
"8", # Technic Bricks
|
||||||
|
"12", # Technic Connectors
|
||||||
|
"17", # Gear Parts
|
||||||
|
"18", # Hinges, Arms and Turntables
|
||||||
|
"22", # Pneumatics
|
||||||
|
"23", # Panels
|
||||||
|
"25", # Technic Steering, Suspension and Engine
|
||||||
|
"26", # Technic Special
|
||||||
|
"29", # Wheels and Tyres
|
||||||
|
"30", # Tubes and Hoses
|
||||||
|
"31", # String, Bands and Reels
|
||||||
|
"34", # Supports, Girders and Cranes
|
||||||
|
"40", # Technic Panels
|
||||||
|
"43", # Znap
|
||||||
|
"44", # Mechanical
|
||||||
|
"45", # Electronics
|
||||||
|
"46", # Technic Axles
|
||||||
|
"51", # Technic Beams
|
||||||
|
"52", # Technic Gears
|
||||||
|
"53", # Technic Pins
|
||||||
|
"54", # Technic Bushes
|
||||||
|
"55", # Technic Beams Special
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_ignored_color_rgb(rgb: str) -> bool:
|
def is_ignored_color_rgb(rgb: str) -> bool:
|
||||||
"""Retourne vrai si le code couleur doit être ignoré."""
|
"""Retourne vrai si le code couleur doit être ignoré."""
|
||||||
return rgb.strip().lower() in IGNORED_COLOR_RGB
|
return rgb.strip().lower() in IGNORED_COLOR_RGB
|
||||||
|
|
||||||
|
|
||||||
|
def is_ignored_part_category(part_cat_id: str) -> bool:
|
||||||
|
"""Retourne vrai si la catégorie de pièce est exclue."""
|
||||||
|
return part_cat_id.strip() in IGNORED_PART_CATEGORY_IDS
|
||||||
|
|
||||||
|
|
||||||
|
def load_ignored_part_numbers(parts_catalog_path: Path) -> Set[str]:
|
||||||
|
"""Charge les références de pièces dont la catégorie est exclue."""
|
||||||
|
ignored: Set[str] = set()
|
||||||
|
with parts_catalog_path.open() as parts_file:
|
||||||
|
reader = csv.DictReader(parts_file)
|
||||||
|
for row in reader:
|
||||||
|
if is_ignored_part_category(row["part_cat_id"]):
|
||||||
|
ignored.add(row["part_num"])
|
||||||
|
return ignored
|
||||||
|
|||||||
@ -25,10 +25,17 @@ def build_colors_lookup(colors_path: Path) -> Dict[Tuple[str, str], str]:
|
|||||||
return colors
|
return colors
|
||||||
|
|
||||||
|
|
||||||
def aggregate_colors_by_set(parts: Iterable[dict], colors_lookup: Dict[Tuple[str, str], str]) -> List[dict]:
|
def aggregate_colors_by_set(
|
||||||
|
parts: Iterable[dict],
|
||||||
|
colors_lookup: Dict[Tuple[str, str], str],
|
||||||
|
ignored_parts: set[str] | None = None,
|
||||||
|
) -> List[dict]:
|
||||||
"""Agrège les quantités par set et par couleur."""
|
"""Agrège les quantités par set et par couleur."""
|
||||||
|
ignored = ignored_parts or set()
|
||||||
totals: Dict[Tuple[str, str, str, str, str], dict] = {}
|
totals: Dict[Tuple[str, str, str, str, str], dict] = {}
|
||||||
for row in parts:
|
for row in parts:
|
||||||
|
if row["part_num"] in ignored:
|
||||||
|
continue
|
||||||
if is_ignored_color_rgb(row["color_rgb"]):
|
if is_ignored_color_rgb(row["color_rgb"]):
|
||||||
continue
|
continue
|
||||||
key = (row["set_num"], row["set_id"], row["year"], row["color_rgb"], row["is_translucent"])
|
key = (row["set_num"], row["set_id"], row["year"], row["color_rgb"], row["is_translucent"])
|
||||||
|
|||||||
@ -8,10 +8,12 @@ from lib.rebrickable.colors_by_set import (
|
|||||||
load_parts,
|
load_parts,
|
||||||
write_colors_by_set,
|
write_colors_by_set,
|
||||||
)
|
)
|
||||||
|
from lib.rebrickable.color_ignores import load_ignored_part_numbers
|
||||||
|
|
||||||
|
|
||||||
PARTS_PATH = Path("data/intermediate/parts_filtered.csv")
|
PARTS_PATH = Path("data/intermediate/parts_filtered.csv")
|
||||||
COLORS_PATH = Path("data/raw/colors.csv")
|
COLORS_PATH = Path("data/raw/colors.csv")
|
||||||
|
PARTS_CATALOG_PATH = Path("data/raw/parts.csv")
|
||||||
DESTINATION_PATH = Path("data/intermediate/colors_by_set.csv")
|
DESTINATION_PATH = Path("data/intermediate/colors_by_set.csv")
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +21,8 @@ def main() -> None:
|
|||||||
"""Génère colors_by_set.csv depuis parts_filtered.csv."""
|
"""Génère colors_by_set.csv depuis parts_filtered.csv."""
|
||||||
parts = load_parts(PARTS_PATH)
|
parts = load_parts(PARTS_PATH)
|
||||||
colors_lookup = build_colors_lookup(COLORS_PATH)
|
colors_lookup = build_colors_lookup(COLORS_PATH)
|
||||||
aggregated = aggregate_colors_by_set(parts, colors_lookup)
|
ignored_parts = load_ignored_part_numbers(PARTS_CATALOG_PATH)
|
||||||
|
aggregated = aggregate_colors_by_set(parts, colors_lookup, ignored_parts=ignored_parts)
|
||||||
write_colors_by_set(DESTINATION_PATH, aggregated)
|
write_colors_by_set(DESTINATION_PATH, aggregated)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,14 +7,15 @@ from lib.plots.colors_grid import plot_colors_grid
|
|||||||
|
|
||||||
PARTS_PATH = Path("data/intermediate/parts_filtered.csv")
|
PARTS_PATH = Path("data/intermediate/parts_filtered.csv")
|
||||||
COLORS_PATH = Path("data/raw/colors.csv")
|
COLORS_PATH = Path("data/raw/colors.csv")
|
||||||
|
PARTS_CATALOG_PATH = Path("data/raw/parts.csv")
|
||||||
DESTINATION_PATH = Path("figures/step12/colors_grid.png")
|
DESTINATION_PATH = Path("figures/step12/colors_grid.png")
|
||||||
MINIFIG_DESTINATION_PATH = Path("figures/step12/colors_grid_minifigs.png")
|
MINIFIG_DESTINATION_PATH = Path("figures/step12/colors_grid_minifigs.png")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Construit les visuels des palettes de couleurs utilisées."""
|
"""Construit les visuels des palettes de couleurs utilisées."""
|
||||||
plot_colors_grid(PARTS_PATH, COLORS_PATH, DESTINATION_PATH, minifig_only=False)
|
plot_colors_grid(PARTS_PATH, COLORS_PATH, PARTS_CATALOG_PATH, DESTINATION_PATH, minifig_only=False)
|
||||||
plot_colors_grid(PARTS_PATH, COLORS_PATH, MINIFIG_DESTINATION_PATH, minifig_only=True)
|
plot_colors_grid(PARTS_PATH, COLORS_PATH, PARTS_CATALOG_PATH, MINIFIG_DESTINATION_PATH, minifig_only=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -33,6 +33,7 @@ def test_plot_colors_grid(tmp_path: Path) -> None:
|
|||||||
"""Produit un fichier image avec les couleurs utilisées."""
|
"""Produit un fichier image avec les couleurs utilisées."""
|
||||||
parts_path = tmp_path / "parts_filtered.csv"
|
parts_path = tmp_path / "parts_filtered.csv"
|
||||||
colors_path = tmp_path / "colors.csv"
|
colors_path = tmp_path / "colors.csv"
|
||||||
|
parts_catalog_path = tmp_path / "parts.csv"
|
||||||
destination_path = tmp_path / "colors_grid.png"
|
destination_path = tmp_path / "colors_grid.png"
|
||||||
|
|
||||||
write_csv(
|
write_csv(
|
||||||
@ -45,6 +46,16 @@ def test_plot_colors_grid(tmp_path: Path) -> None:
|
|||||||
["3004", "0033B2", "false", "1000", "10", "false"],
|
["3004", "0033B2", "false", "1000", "10", "false"],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
write_csv(
|
||||||
|
parts_catalog_path,
|
||||||
|
["part_num", "name", "part_cat_id"],
|
||||||
|
[
|
||||||
|
["3001", "Brick", "11"],
|
||||||
|
["3002", "Pane", "23"],
|
||||||
|
["3003", "Slope", "3"],
|
||||||
|
["3004", "Technic", "8"],
|
||||||
|
],
|
||||||
|
)
|
||||||
write_csv(
|
write_csv(
|
||||||
colors_path,
|
colors_path,
|
||||||
["id", "name", "rgb", "is_trans", "num_parts", "num_sets", "y1", "y2"],
|
["id", "name", "rgb", "is_trans", "num_parts", "num_sets", "y1", "y2"],
|
||||||
@ -52,13 +63,14 @@ def test_plot_colors_grid(tmp_path: Path) -> None:
|
|||||||
["1", "White", "FFFFFF", "False", "0", "0", "0", "0"],
|
["1", "White", "FFFFFF", "False", "0", "0", "0", "0"],
|
||||||
["2", "Black", "000000", "True", "0", "0", "0", "0"],
|
["2", "Black", "000000", "True", "0", "0", "0", "0"],
|
||||||
["3", "Red", "FF0000", "False", "0", "0", "0", "0"],
|
["3", "Red", "FF0000", "False", "0", "0", "0", "0"],
|
||||||
|
["4", "Ignored Blue", "0033B2", "False", "0", "0", "0", "0"],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
colors = load_used_colors(parts_path, colors_path)
|
colors = load_used_colors(parts_path, colors_path, parts_catalog_path)
|
||||||
assert len(colors) == 3
|
assert len(colors) == 2
|
||||||
|
|
||||||
plot_colors_grid(parts_path, colors_path, destination_path)
|
plot_colors_grid(parts_path, colors_path, parts_catalog_path, destination_path)
|
||||||
|
|
||||||
assert destination_path.exists()
|
assert destination_path.exists()
|
||||||
assert destination_path.stat().st_size > 0
|
assert destination_path.stat().st_size > 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user