1from functools import total_ordering
2
3from .. import i18n
4from ..utils import str_coercible
5
6
7@total_ordering
8@str_coercible
9class Country:
10 """
11 Country class wraps a 2 to 3 letter country code. It provides various
12 convenience properties and methods.
13
14 ::
15
16 from babel import Locale
17 from sqlalchemy_utils import Country, i18n
18
19
20 # First lets add a locale getter for testing purposes
21 i18n.get_locale = lambda: Locale('en')
22
23
24 Country('FI').name # Finland
25 Country('FI').code # FI
26
27 Country(Country('FI')).code # 'FI'
28
29 Country always validates the given code if you use at least the optional
30 dependency list 'babel', otherwise no validation are performed.
31
32 ::
33
34 Country(None) # raises TypeError
35
36 Country('UnknownCode') # raises ValueError
37
38
39 Country supports equality operators.
40
41 ::
42
43 Country('FI') == Country('FI')
44 Country('FI') != Country('US')
45
46
47 Country objects are hashable.
48
49
50 ::
51
52 assert hash(Country('FI')) == hash('FI')
53
54 """
55
56 def __init__(self, code_or_country):
57 if isinstance(code_or_country, Country):
58 self.code = code_or_country.code
59 elif isinstance(code_or_country, str):
60 self.validate(code_or_country)
61 self.code = code_or_country
62 else:
63 raise TypeError(
64 "Country() argument must be a string or a country, not '{}'".format(
65 type(code_or_country).__name__
66 )
67 )
68
69 @property
70 def name(self):
71 return i18n.get_locale().territories[self.code]
72
73 @classmethod
74 def validate(self, code):
75 try:
76 i18n.babel.Locale('en').territories[code]
77 except KeyError:
78 raise ValueError(f'Could not convert string to country code: {code}')
79 except AttributeError:
80 # As babel is optional, we may raise an AttributeError accessing it
81 pass
82
83 def __eq__(self, other):
84 if isinstance(other, Country):
85 return self.code == other.code
86 elif isinstance(other, str):
87 return self.code == other
88 else:
89 return NotImplemented
90
91 def __hash__(self):
92 return hash(self.code)
93
94 def __ne__(self, other):
95 return not (self == other)
96
97 def __lt__(self, other):
98 if isinstance(other, Country):
99 return self.code < other.code
100 elif isinstance(other, str):
101 return self.code < other
102 return NotImplemented
103
104 def __repr__(self):
105 return f'{self.__class__.__name__}({self.code!r})'
106
107 def __unicode__(self):
108 return self.name