From b103bcefeb93a2f7ca0684c84cab562dc9668ba7 Mon Sep 17 00:00:00 2001 From: Tom Hall Date: Wed, 31 Dec 2025 23:07:14 +0000 Subject: [PATCH 1/3] Organise data files and cache json reading functions --- .../classifications/classification_utils.py | 13 +++++++++---- .../classifications/{ => data}/AGB_ages.json | 0 .../classifications/{ => data}/AGB_bowstyles.json | 0 .../classifications/{ => data}/AGB_classes_in.json | 0 .../classifications/{ => data}/AGB_classes_out.json | 0 .../classifications/{ => data}/AGB_genders.json | 0 pyproject.toml | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) rename archeryutils/classifications/{ => data}/AGB_ages.json (100%) rename archeryutils/classifications/{ => data}/AGB_bowstyles.json (100%) rename archeryutils/classifications/{ => data}/AGB_classes_in.json (100%) rename archeryutils/classifications/{ => data}/AGB_classes_out.json (100%) rename archeryutils/classifications/{ => data}/AGB_genders.json (100%) diff --git a/archeryutils/classifications/classification_utils.py b/archeryutils/classifications/classification_utils.py index 7c611901..7d5931d2 100644 --- a/archeryutils/classifications/classification_utils.py +++ b/archeryutils/classifications/classification_utils.py @@ -14,6 +14,7 @@ """ import json +from functools import cache from pathlib import Path from typing import Literal, TypedDict @@ -40,8 +41,9 @@ class AGBAgeData(TypedDict): step: int +@cache def read_ages_json( - age_file: Path = Path(__file__).parent / "AGB_ages.json", + age_file: Path = Path(__file__).parent / "data" / "AGB_ages.json", ) -> dict[str, AGBAgeData]: """ Read AGB age categories in from neighbouring json file to list of dicts. @@ -94,8 +96,9 @@ class AGBBowstyleData(TypedDict): ageStep_field: float +@cache def read_bowstyles_json( - bowstyles_file: Path = Path(__file__).parent / "AGB_bowstyles.json", + bowstyles_file: Path = Path(__file__).parent / "data" / "AGB_bowstyles.json", ) -> dict[str, AGBBowstyleData]: """ Read AGB bowstyles in from neighbouring json file to list of dicts. @@ -130,8 +133,9 @@ def read_bowstyles_json( raise TypeError(msg) +@cache def read_genders_json( - genders_file: Path = Path(__file__).parent / "AGB_genders.json", + genders_file: Path = Path(__file__).parent / "data" / "AGB_genders.json", ) -> list[Literal["Male", "Female"]]: """ Read AGB genders in from neighbouring json file to list of dict. @@ -175,6 +179,7 @@ class AGBClassificationData(TypedDict): classes_long: list[str] +@cache def read_classes_json( class_system: str, ) -> AGBClassificationData: @@ -217,7 +222,7 @@ def read_classes_json( ) raise ValueError(msg) - classes_file = Path(__file__).parent / filename + classes_file = Path(__file__).parent / "data" / filename # Read in classification names as dict with open(classes_file, encoding="utf-8") as json_file: diff --git a/archeryutils/classifications/AGB_ages.json b/archeryutils/classifications/data/AGB_ages.json similarity index 100% rename from archeryutils/classifications/AGB_ages.json rename to archeryutils/classifications/data/AGB_ages.json diff --git a/archeryutils/classifications/AGB_bowstyles.json b/archeryutils/classifications/data/AGB_bowstyles.json similarity index 100% rename from archeryutils/classifications/AGB_bowstyles.json rename to archeryutils/classifications/data/AGB_bowstyles.json diff --git a/archeryutils/classifications/AGB_classes_in.json b/archeryutils/classifications/data/AGB_classes_in.json similarity index 100% rename from archeryutils/classifications/AGB_classes_in.json rename to archeryutils/classifications/data/AGB_classes_in.json diff --git a/archeryutils/classifications/AGB_classes_out.json b/archeryutils/classifications/data/AGB_classes_out.json similarity index 100% rename from archeryutils/classifications/AGB_classes_out.json rename to archeryutils/classifications/data/AGB_classes_out.json diff --git a/archeryutils/classifications/AGB_genders.json b/archeryutils/classifications/data/AGB_genders.json similarity index 100% rename from archeryutils/classifications/AGB_genders.json rename to archeryutils/classifications/data/AGB_genders.json diff --git a/pyproject.toml b/pyproject.toml index 9e73ef0a..6b6b9107 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ include = ["archeryutils", "archeryutils.*"] # package names should match these namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.setuptools.package-data] -archeryutils = ["*.json", "round_data_files/*.json", "classifications/*.json"] +archeryutils = ["*.json", "round_data_files/*.json", "classifications/data/*.json"] [tool.mypy] warn_unused_configs = true From b7f34d964d8a19737180503117d6a169a6f8c9d0 Mon Sep 17 00:00:00 2001 From: Tom Hall Date: Thu, 1 Jan 2026 12:21:04 +0000 Subject: [PATCH 2/3] Simplify repeated if-else logic with match-case statement for class data file reading Removes duplicated definition of outdoor classes filename --- .../classifications/classification_utils.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/archeryutils/classifications/classification_utils.py b/archeryutils/classifications/classification_utils.py index 7d5931d2..ded0df15 100644 --- a/archeryutils/classifications/classification_utils.py +++ b/archeryutils/classifications/classification_utils.py @@ -41,7 +41,7 @@ class AGBAgeData(TypedDict): step: int -@cache + def read_ages_json( age_file: Path = Path(__file__).parent / "data" / "AGB_ages.json", ) -> dict[str, AGBAgeData]: @@ -208,21 +208,20 @@ def read_classes_json( ---------- Archery GB Rules of Shooting """ - if class_system == "agb_indoor": - filename = "AGB_classes_in.json" - elif class_system == "agb_outdoor": - filename = "AGB_classes_out.json" - elif class_system == "agb_field": + match class_system: + case "agb_indoor": + filename = "AGB_classes_in" # Field classifications are same as outdoor - filename = "AGB_classes_out.json" - else: - msg = ( - "Unexpected classification system specified. " - "Expected one of 'agb_indoor', 'agb_outdoor', 'agb_field'." - ) - raise ValueError(msg) - - classes_file = Path(__file__).parent / "data" / filename + case "agb_outdoor" | "agb_field": + filename = "AGB_classes_out" + case _: + msg = ( + "Unexpected classification system specified. " + "Expected one of 'agb_indoor', 'agb_outdoor', 'agb_field'." + ) + raise ValueError(msg) + + classes_file = Path(__file__).parent.joinpath("data", filename).with_suffix(".json") # Read in classification names as dict with open(classes_file, encoding="utf-8") as json_file: From 678fdfc906da1526ec7cfb49b208bbadc482e16e Mon Sep 17 00:00:00 2001 From: Tom Hall Date: Thu, 1 Jan 2026 16:56:54 +0000 Subject: [PATCH 3/3] Remove unused file path parameters from read_x_json functions Allows use of joinpath and clearer sequence of logic to early exit if type check fails on data rather than early return and defaulting to raising error. --- .../classifications/classification_utils.py | 89 ++++++++----------- 1 file changed, 37 insertions(+), 52 deletions(-) diff --git a/archeryutils/classifications/classification_utils.py b/archeryutils/classifications/classification_utils.py index ded0df15..c33a249f 100644 --- a/archeryutils/classifications/classification_utils.py +++ b/archeryutils/classifications/classification_utils.py @@ -41,18 +41,11 @@ class AGBAgeData(TypedDict): step: int - -def read_ages_json( - age_file: Path = Path(__file__).parent / "data" / "AGB_ages.json", -) -> dict[str, AGBAgeData]: +@cache +def read_ages_json() -> dict[str, AGBAgeData]: """ Read AGB age categories in from neighbouring json file to list of dicts. - Parameters - ---------- - age_file : Path, default="./AGB_ages.json" - path to json file with age information - Returns ------- ages : list of dict @@ -67,15 +60,17 @@ def read_ages_json( ---------- Archery GB Rules of Shooting """ - with open(age_file, encoding="utf-8") as json_file: - ages = json.load(json_file) - if isinstance(ages, dict): - return ages - msg = ( - f"Unexpected ages input when reading from json file. " - f"Expected dict() but got {type(ages)}. Check {age_file}." - ) - raise TypeError(msg) + age_file = Path(__file__).parent.joinpath("data", "AGB_ages.json") + ages = json.loads(age_file.read_text(encoding="utf-8")) + + if not isinstance(ages, dict): + msg = ( + f"Unexpected ages input when reading from json file. " + f"Expected dict() but got {type(ages)}. Check {age_file}." + ) + raise TypeError(msg) + + return ages class AGBBowstyleData(TypedDict): @@ -97,17 +92,10 @@ class AGBBowstyleData(TypedDict): @cache -def read_bowstyles_json( - bowstyles_file: Path = Path(__file__).parent / "data" / "AGB_bowstyles.json", -) -> dict[str, AGBBowstyleData]: +def read_bowstyles_json() -> dict[str, AGBBowstyleData]: """ Read AGB bowstyles in from neighbouring json file to list of dicts. - Parameters - ---------- - bowstyles_file : Path, default="./AGB_bowstyles.json" - path to json file - Returns ------- bowstyles : list of dict @@ -122,29 +110,24 @@ def read_bowstyles_json( ---------- Archery GB Rules of Shooting """ - with open(bowstyles_file, encoding="utf-8") as json_file: - bowstyles = json.load(json_file) - if isinstance(bowstyles, dict): - return bowstyles - msg = ( - f"Unexpected bowstyles input when reading from json file. " - f"Expected dict() but got {type(bowstyles)}. Check {bowstyles_file}." - ) - raise TypeError(msg) + bowstyles_file = Path(__file__).parent.joinpath("data", "AGB_bowstyles.json") + bowstyles = json.loads(bowstyles_file.read_text(encoding="utf-8")) + + if not isinstance(bowstyles, dict): + msg = ( + f"Unexpected bowstyles input when reading from json file. " + f"Expected dict() but got {type(bowstyles)}. Check {bowstyles_file}." + ) + raise TypeError(msg) + + return bowstyles @cache -def read_genders_json( - genders_file: Path = Path(__file__).parent / "data" / "AGB_genders.json", -) -> list[Literal["Male", "Female"]]: +def read_genders_json() -> list[Literal["Male", "Female"]]: """ Read AGB genders in from neighbouring json file to list of dict. - Parameters - ---------- - genders_file : Path, default="./AGB_genders.json" - path to json file - Returns ------- genders : list of str @@ -160,15 +143,17 @@ def read_genders_json( Archery GB Rules of Shooting """ # Read in gender info as list - with open(genders_file, encoding="utf-8") as json_file: - genders = json.load(json_file)["genders"] - if isinstance(genders, list): - return genders - msg = ( - f"Unexpected genders input when reading from json file. " - f"Expected list() but got {type(genders)}. Check {genders_file}." - ) - raise TypeError(msg) + genders_file = Path(__file__).parent.joinpath("data", "AGB_genders.json") + genders = json.loads(genders_file.read_text(encoding="utf-8"))["genders"] + + if not isinstance(genders, list): + msg = ( + f"Unexpected genders input when reading from json file. " + f"Expected list() but got {type(genders)}. Check {genders_file}." + ) + raise TypeError(msg) + + return genders class AGBClassificationData(TypedDict):