commodutil 5.5.1__tar.gz → 5.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {commodutil-5.5.1 → commodutil-5.5.3}/CHANGELOG.md +8 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/PKG-INFO +1 -1
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/geography.py +13 -3
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil.egg-info/PKG-INFO +1 -1
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_geography.py +27 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/uv.lock +3 -3
- {commodutil-5.5.1 → commodutil-5.5.3}/.coveragerc +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.github/workflows/1_tests.yml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.github/workflows/2_coverage.yml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.github/workflows/3_linting.yml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.github/workflows/4_release.yml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.gitignore +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/.pypirc +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/README.md +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/azure-build-pipelines.yml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/__init__.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/arb.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/convfactors.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/dates.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/__init__.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/calendar.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/continuous.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/fly.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/quarterly.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/spreads.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/structure.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forward/util.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/forwards.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/pandasutil.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/priceconv.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/__init__.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/analysis_types.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/commodities.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/commodity_groups.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/currency.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/price_unit.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/trading_hubs.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/unit_registry.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/standards/units.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/stats.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil/transforms.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil.egg-info/SOURCES.txt +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil.egg-info/dependency_links.txt +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil.egg-info/requires.txt +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/commodutil.egg-info/top_level.txt +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/pyproject.toml +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/setup.cfg +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/__init__.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/conftest.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/__init__.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/conftest.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_calendar.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_continuous.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_fly.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_quarterly.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_spreads.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_structure.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/forward/test_util.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/golden/golden_factors.json +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_aliases.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_align_fx_result.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_arb.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_cl.csv +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_conv.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_dates.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_forwards.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_golden_factors.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_pandasutils.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_price_conv.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_price_unit.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_priceconv_reexports.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_rate_pint.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_regex_literals.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_analysis_types.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_commodities.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_commodity_groups.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_currency.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_trading_hubs.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_standards_units.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_stats.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_transforms.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_unit_registry.py +0 -0
- {commodutil-5.5.1 → commodutil-5.5.3}/tests/test_weekly.csv +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.5.3 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
- Fixed country-name resolution for standalone `St`/`St.` abbreviations and the vendor spellings `Taiwan, China` and `Virgin Islands (US)`.
|
|
6
|
+
|
|
7
|
+
## 5.5.2 - 2026-07-28
|
|
8
|
+
|
|
9
|
+
- Fixed country-name resolution to normalize ampersands to `and`, matching ISO names such as Antigua and Barbuda.
|
|
10
|
+
|
|
3
11
|
## 5.5.0 - 2026-07-27
|
|
4
12
|
|
|
5
13
|
- Added `commodutil.standards.geography` with pycountry-backed countries, desk aliases, and membership-defined country groups; added `pycountry` as a production dependency.
|
|
@@ -15,6 +15,7 @@ memberships are derived from those definitions at import time.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import re
|
|
18
19
|
from dataclasses import dataclass
|
|
19
20
|
from typing import Dict, Optional, Tuple
|
|
20
21
|
|
|
@@ -69,11 +70,13 @@ COUNTRY_ALIASES: dict[str, str] = {
|
|
|
69
70
|
"Turkey": "TR",
|
|
70
71
|
"Turkiye": "TR",
|
|
71
72
|
"Republic of China": "TW",
|
|
73
|
+
"Taiwan, China": "TW",
|
|
72
74
|
"USA": "US",
|
|
73
75
|
"U.S.": "US",
|
|
74
76
|
"U.S.A.": "US",
|
|
75
77
|
"Holy See": "VA",
|
|
76
78
|
"US Virgin Islands": "VI",
|
|
79
|
+
"Virgin Islands (US)": "VI",
|
|
77
80
|
# From empirical vendor-vocabulary validation (see commodflows research).
|
|
78
81
|
# ISO merges Bonaire and Sint Eustatius into BQ; sub-island detail is
|
|
79
82
|
# lost by design.
|
|
@@ -98,6 +101,13 @@ COUNTRY_ALIASES: dict[str, str] = {
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
|
|
104
|
+
def _normalize_country_name(name: str) -> str:
|
|
105
|
+
"""Return the case-folded lookup key for a country name."""
|
|
106
|
+
normalized = name.replace("&", " and ")
|
|
107
|
+
normalized = re.sub(r"\bSt\.?(?=\s|$)", "Saint", normalized, flags=re.IGNORECASE)
|
|
108
|
+
return " ".join(normalized.split()).casefold()
|
|
109
|
+
|
|
110
|
+
|
|
101
111
|
def _build_country_name_map(aliases: Dict[str, str]) -> Dict[str, str]:
|
|
102
112
|
"""Build the case-folded name map and reject conflicting aliases."""
|
|
103
113
|
result: Dict[str, str] = {}
|
|
@@ -106,7 +116,7 @@ def _build_country_name_map(aliases: Dict[str, str]) -> Dict[str, str]:
|
|
|
106
116
|
name = getattr(country, attribute, None)
|
|
107
117
|
if name is None:
|
|
108
118
|
continue
|
|
109
|
-
key = name
|
|
119
|
+
key = _normalize_country_name(name)
|
|
110
120
|
existing = result.get(key)
|
|
111
121
|
if existing is not None and existing != country.alpha_2:
|
|
112
122
|
raise ValueError(
|
|
@@ -120,7 +130,7 @@ def _build_country_name_map(aliases: Dict[str, str]) -> Dict[str, str]:
|
|
|
120
130
|
raise ValueError(
|
|
121
131
|
f"country alias {alias!r} has unknown country code {code!r}"
|
|
122
132
|
)
|
|
123
|
-
key = alias
|
|
133
|
+
key = _normalize_country_name(alias)
|
|
124
134
|
existing = result.get(key)
|
|
125
135
|
if existing is not None and existing != code:
|
|
126
136
|
raise ValueError(
|
|
@@ -144,7 +154,7 @@ def canonical_country(text: object) -> Optional[str]:
|
|
|
144
154
|
code = cleaned.upper()
|
|
145
155
|
if code in VALID_COUNTRY_CODES:
|
|
146
156
|
return code
|
|
147
|
-
return COUNTRY_NAME_MAP.get(cleaned
|
|
157
|
+
return COUNTRY_NAME_MAP.get(_normalize_country_name(cleaned))
|
|
148
158
|
|
|
149
159
|
|
|
150
160
|
def is_canonical_country(code: object) -> bool:
|
|
@@ -66,6 +66,33 @@ def test_country_name_map_contains_pycountry_names_and_aliases():
|
|
|
66
66
|
assert COUNTRY_NAME_MAP["uk"] == "GB"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
@pytest.mark.parametrize(
|
|
70
|
+
"value,expected",
|
|
71
|
+
[
|
|
72
|
+
("Antigua & Barbuda", "AG"),
|
|
73
|
+
("Trinidad & Tobago", "TT"),
|
|
74
|
+
("Bosnia & Herzegovina", "BA"),
|
|
75
|
+
("&", None),
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
def test_canonical_country_normalizes_ampersands(value, expected):
|
|
79
|
+
assert canonical_country(value) == expected
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize(
|
|
83
|
+
"value,expected",
|
|
84
|
+
[
|
|
85
|
+
("St Kitts & Nevis", "KN"),
|
|
86
|
+
("St. Lucia", "LC"),
|
|
87
|
+
("Taiwan, China", "TW"),
|
|
88
|
+
("Virgin Islands (US)", "VI"),
|
|
89
|
+
("Stockholm", None),
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
def test_canonical_country_normalizes_live_vendor_spellings(value, expected):
|
|
93
|
+
assert canonical_country(value) == expected
|
|
94
|
+
|
|
95
|
+
|
|
69
96
|
@pytest.mark.parametrize(
|
|
70
97
|
"value,expected",
|
|
71
98
|
[
|
|
@@ -1627,7 +1627,7 @@ wheels = [
|
|
|
1627
1627
|
|
|
1628
1628
|
[[package]]
|
|
1629
1629
|
name = "twine"
|
|
1630
|
-
version = "
|
|
1630
|
+
version = "7.0.0"
|
|
1631
1631
|
source = { registry = "https://pypi.org/simple" }
|
|
1632
1632
|
dependencies = [
|
|
1633
1633
|
{ name = "id" },
|
|
@@ -1640,9 +1640,9 @@ dependencies = [
|
|
|
1640
1640
|
{ name = "rich" },
|
|
1641
1641
|
{ name = "urllib3" },
|
|
1642
1642
|
]
|
|
1643
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1643
|
+
sdist = { url = "https://files.pythonhosted.org/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" }
|
|
1644
1644
|
wheels = [
|
|
1645
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1645
|
+
{ url = "https://files.pythonhosted.org/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl", hash = "sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size = 43204, upload-time = "2026-07-27T15:58:59.26Z" },
|
|
1646
1646
|
]
|
|
1647
1647
|
|
|
1648
1648
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|