pyhscodes 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,24 @@
1
+ COPYRIGHT (c) 2008 - 2023, pycountry
2
+
3
+ Pycountry is free software; you can redistribute it and/or modify
4
+ it under the terms of the GNU Lesser General Public License as published by
5
+ the Free Software Foundation; either version 2.1 of the License, or any later version.
6
+
7
+ This project is distributed in the hope that it will be useful,
8
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ GNU Lesser General Public License for more details.
11
+
12
+ Contributors:
13
+ - See Pycountry contributors until 2025
14
+ - David Sigley (2025)
15
+
16
+ Additional Acknowledgements and Licensing Information:
17
+ - Data in the /src/databases/ and /src/locales/ folders is sourced from the Debian iso-codes project, available at: https://salsa.debian.org/iso-codes-team/iso-codes. This data is used under the terms of the GNU Lesser General Public License Version 2.1 (February 1999).
18
+
19
+ The Debian iso-codes project is a collection of code lists for different standards, maintained and made available under the GNU Lesser General Public License Version 2.1. We gratefully acknowledge the Debian iso-codes team and contributors for their work and for making this resource freely available.
20
+
21
+ The full text of the GNU Lesser General Public License Version 2.1 can be found at: https://salsa.debian.org/iso-codes-team/iso-codes/-/blob/main/COPYING.
22
+
23
+ For the full text of the GNU Lesser General Public License,
24
+ see https://github.com/pycountry/pycountry/blob/main/LICENSE.txt.
pyhscodes/__init__.py ADDED
@@ -0,0 +1,128 @@
1
+ """pyhscodes - Harmonized System codes from the World Customs Organization"""
2
+
3
+ import os.path
4
+ import unicodedata
5
+ from importlib import metadata as _importlib_metadata
6
+ from typing import Dict, List, Optional, Type
7
+
8
+ import pyhscodes.db
9
+
10
+ # We prioritise importing the backported `importlib_resources`
11
+ # because the function we use (`importlib.resources.files`) is only
12
+ # available from Python 3.9, but the module itself exists since 3.7.
13
+ # We install `importlib_resources` on Python < 3.9.
14
+ # TODO: Remove usage of importlib_resources once support for Python 3.8 is dropped
15
+ try:
16
+ import importlib_resources # type: ignore
17
+ except ModuleNotFoundError:
18
+ from importlib import resources as importlib_resources # type: ignore
19
+
20
+
21
+ def resource_filename(package_or_requirement: str, resource_name: str) -> str:
22
+ return str(importlib_resources.files(package_or_requirement) / resource_name)
23
+
24
+
25
+ def get_version(distribution_name: str) -> Optional[str]:
26
+ try:
27
+ return _importlib_metadata.version(distribution_name)
28
+ except _importlib_metadata.PackageNotFoundError:
29
+ return "n/a"
30
+
31
+
32
+ # Variable annotations
33
+ DATABASE_DIR: str = resource_filename("pyhscodes", "databases")
34
+ __version__: Optional[str] = get_version("pyhscodes")
35
+
36
+
37
+ def remove_accents(input_str: str) -> str:
38
+ output_str = input_str
39
+ if not input_str.isascii():
40
+ # Borrowed from https://stackoverflow.com/a/517974/1509718
41
+ nfkd_form = unicodedata.normalize("NFKD", input_str)
42
+ output_str = "".join([c for c in nfkd_form if not unicodedata.combining(c)])
43
+ return output_str
44
+
45
+
46
+ class HSCodes(pyhscodes.db.Database):
47
+ """Provides access to Harmonized System codes database."""
48
+
49
+ data_class = pyhscodes.db.HSCode
50
+ root_key = "hscodes"
51
+
52
+ def search_fuzzy(self, query: str) -> List[Type["HSCodes"]]:
53
+ query = remove_accents(query.strip().lower())
54
+
55
+ # A code to points mapping for later sorting codes
56
+ # based on the query's matching incidence.
57
+ results: dict[str, int] = {}
58
+
59
+ def add_result(hscode: "pyhscodes.db.HSCode", points: int) -> None:
60
+ results.setdefault(hscode.hscode, 0)
61
+ results[hscode.hscode] += points
62
+
63
+ # Prio 1: exact matches on HS code numbers
64
+ try:
65
+ add_result(self.lookup(query), 50)
66
+ except LookupError:
67
+ pass
68
+
69
+ # Prio 2: exact matches on descriptions
70
+ try:
71
+ result = self.get(description=query)
72
+ if result is not None:
73
+ add_result(result, 49)
74
+ except (LookupError, TypeError):
75
+ pass
76
+
77
+ # Prio 3: partial matches on descriptions
78
+ for candidate in self:
79
+ description = candidate._fields.get("description")
80
+ if description is not None:
81
+ description = remove_accents(description.lower())
82
+ if query in description:
83
+ # This prefers codes with a match early in their description
84
+ add_result(candidate, max([5, 30 - (2 * description.find(query))]))
85
+
86
+ if not results:
87
+ raise LookupError(query)
88
+
89
+ sorted_results = [
90
+ self.get(hscode=x[0])
91
+ # sort by points first, by hscode second
92
+ for x in sorted(results.items(), key=lambda x: (-x[1], x[0]))
93
+ ]
94
+ return sorted_results
95
+
96
+ def get_by_level(self, level: str) -> List[pyhscodes.db.HSCode]:
97
+ """Get all HS codes at a specific level (2, 4, or 6)."""
98
+ return [obj for obj in self if obj.level == level]
99
+
100
+ def get_children(self, parent_code: str) -> List[pyhscodes.db.HSCode]:
101
+ """Get all child codes for a given parent code."""
102
+ return [obj for obj in self if obj.parent == parent_code]
103
+
104
+ def get_hierarchy(self, hscode: str) -> List[pyhscodes.db.HSCode]:
105
+ """Get the full hierarchy path for a given HS code."""
106
+ hierarchy = []
107
+ current = self.get(hscode=hscode)
108
+
109
+ while current:
110
+ hierarchy.insert(0, current)
111
+ if current.parent and current.parent != "TOTAL":
112
+ current = self.get(hscode=current.parent)
113
+ else:
114
+ break
115
+
116
+ return hierarchy
117
+
118
+
119
+ class Sections(pyhscodes.db.Database):
120
+ """Provides access to HS code sections database."""
121
+
122
+ data_class = pyhscodes.db.Section
123
+ root_key = "sections"
124
+
125
+
126
+ # Create database instances
127
+ hscodes = HSCodes(os.path.join(DATABASE_DIR, "hscodes.json"))
128
+ sections = Sections(os.path.join(DATABASE_DIR, "sections.json"))