encommon 0.7.6__py3-none-any.whl → 0.8.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.
Files changed (53) hide show
  1. encommon/config/__init__.py +4 -0
  2. encommon/config/common.py +18 -14
  3. encommon/config/config.py +8 -5
  4. encommon/config/files.py +13 -7
  5. encommon/config/logger.py +94 -88
  6. encommon/config/params.py +1 -1
  7. encommon/config/paths.py +16 -8
  8. encommon/config/test/test_common.py +27 -4
  9. encommon/config/test/test_config.py +48 -82
  10. encommon/config/test/test_files.py +58 -43
  11. encommon/config/test/test_logger.py +129 -82
  12. encommon/config/test/test_paths.py +70 -30
  13. encommon/conftest.py +52 -12
  14. encommon/crypts/__init__.py +2 -0
  15. encommon/crypts/crypts.py +3 -1
  16. encommon/crypts/hashes.py +2 -2
  17. encommon/crypts/test/test_crypts.py +50 -28
  18. encommon/crypts/test/test_hashes.py +20 -18
  19. encommon/times/__init__.py +2 -0
  20. encommon/times/common.py +99 -15
  21. encommon/times/duration.py +50 -36
  22. encommon/times/parse.py +13 -25
  23. encommon/times/test/test_common.py +47 -16
  24. encommon/times/test/test_duration.py +104 -79
  25. encommon/times/test/test_parse.py +53 -63
  26. encommon/times/test/test_timers.py +90 -36
  27. encommon/times/test/test_times.py +21 -30
  28. encommon/times/test/test_window.py +73 -21
  29. encommon/times/timers.py +91 -58
  30. encommon/times/times.py +36 -34
  31. encommon/times/window.py +4 -4
  32. encommon/types/dicts.py +10 -4
  33. encommon/types/empty.py +7 -2
  34. encommon/types/strings.py +10 -0
  35. encommon/types/test/test_dicts.py +5 -5
  36. encommon/types/test/test_empty.py +4 -1
  37. encommon/types/test/test_strings.py +1 -1
  38. encommon/utils/__init__.py +4 -0
  39. encommon/utils/common.py +51 -6
  40. encommon/utils/match.py +1 -0
  41. encommon/utils/paths.py +42 -23
  42. encommon/utils/sample.py +31 -27
  43. encommon/utils/stdout.py +28 -17
  44. encommon/utils/test/test_common.py +35 -0
  45. encommon/utils/test/test_paths.py +3 -2
  46. encommon/utils/test/test_sample.py +28 -12
  47. encommon/version.txt +1 -1
  48. {encommon-0.7.6.dist-info → encommon-0.8.0.dist-info}/METADATA +1 -1
  49. encommon-0.8.0.dist-info/RECORD +63 -0
  50. encommon-0.7.6.dist-info/RECORD +0 -62
  51. {encommon-0.7.6.dist-info → encommon-0.8.0.dist-info}/LICENSE +0 -0
  52. {encommon-0.7.6.dist-info → encommon-0.8.0.dist-info}/WHEEL +0 -0
  53. {encommon-0.7.6.dist-info → encommon-0.8.0.dist-info}/top_level.txt +0 -0
@@ -9,12 +9,32 @@ is permitted, for more information consult the project license file.
9
9
 
10
10
  from pathlib import Path
11
11
 
12
+ from pytest import fixture
13
+
12
14
  from . import SAMPLES
13
15
  from ..paths import ConfigPath
14
16
  from ..paths import ConfigPaths
15
17
  from ... import ENPYRWS
16
- from ...utils.sample import load_sample
17
- from ...utils.sample import prep_sample
18
+ from ... import PROJECT
19
+ from ...utils import load_sample
20
+ from ...utils import prep_sample
21
+
22
+
23
+
24
+ @fixture
25
+ def paths(
26
+ config_path: Path,
27
+ ) -> ConfigPaths:
28
+ """
29
+ Construct the instance for use in the downstream tests.
30
+
31
+ :param config_path: Custom fixture for populating paths.
32
+ :returns: Newly constructed instance of related class.
33
+ """
34
+
35
+ return ConfigPaths([
36
+ f'{SAMPLES}/stark',
37
+ f'{SAMPLES}/wayne'])
18
38
 
19
39
 
20
40
 
@@ -29,33 +49,38 @@ def test_ConfigPath(
29
49
 
30
50
  path = ConfigPath(config_path)
31
51
 
52
+
32
53
  attrs = list(path.__dict__)
33
54
 
34
- assert attrs == ['path', 'config']
55
+ assert attrs == [
56
+ 'path',
57
+ 'config']
58
+
59
+
60
+ assert repr(path)[:22] == (
61
+ '<encommon.config.paths')
62
+
63
+ assert hash(path) > 0
35
64
 
65
+ assert str(path)[:22] == (
66
+ '<encommon.config.paths')
36
67
 
37
- assert repr(path).startswith(
38
- '<encommon.config.paths.ConfigPath')
39
- assert isinstance(hash(path), int)
40
- assert str(path).startswith(
41
- '<encommon.config.paths.ConfigPath')
42
68
 
69
+ assert 'test' in path.path.name
43
70
 
44
- assert path.path == config_path
45
- assert len(path.config) == 2
71
+ assert len(path.config) == 1
46
72
 
47
73
 
48
74
 
49
75
  def test_ConfigPaths(
50
- config_path: Path,
76
+ paths: ConfigPaths,
51
77
  ) -> None:
52
78
  """
53
79
  Perform various tests associated with relevant routines.
54
80
 
55
- :param config_path: Custom fixture for populating paths.
81
+ :param paths: Custom fixture for the configuration paths.
56
82
  """
57
83
 
58
- paths = ConfigPaths(config_path)
59
84
 
60
85
  attrs = list(paths.__dict__)
61
86
 
@@ -64,35 +89,50 @@ def test_ConfigPaths(
64
89
  '_ConfigPaths__merged']
65
90
 
66
91
 
67
- assert repr(paths).startswith(
68
- '<encommon.config.paths.ConfigPaths')
69
- assert isinstance(hash(paths), int)
70
- assert str(paths).startswith(
71
- '<encommon.config.paths.ConfigPaths')
92
+ assert repr(paths)[:22] == (
93
+ '<encommon.config.paths')
94
+
95
+ assert hash(paths) > 0
96
+
97
+ assert str(paths)[:22] == (
98
+ '<encommon.config.paths')
72
99
 
73
100
 
74
- assert len(paths.paths) == 1
75
- assert len(paths.config) == 1
101
+ assert len(paths.paths) == 2
76
102
 
103
+ assert len(paths.config) == 2
77
104
 
78
- _merged1 = paths.merged
79
- _merged2 = paths.merged
80
105
 
81
- assert _merged1 is not _merged2
106
+ replaces = {
107
+ 'PROJECT': PROJECT}
82
108
 
83
- sample_path = Path(
109
+ sample_path = (
84
110
  f'{SAMPLES}/paths.json')
85
111
 
86
112
  sample = load_sample(
87
113
  path=sample_path,
88
114
  update=ENPYRWS,
89
- content=_merged1,
90
- replace={
91
- 'config_path': str(config_path)})
115
+ content=paths.merged,
116
+ replace=replaces)
92
117
 
93
118
  expect = prep_sample(
94
- content=_merged2,
95
- replace={
96
- 'config_path': str(config_path)})
119
+ content=paths.merged,
120
+ replace=replaces)
97
121
 
98
122
  assert sample == expect
123
+
124
+
125
+
126
+ def test_ConfigPaths_cover(
127
+ paths: ConfigPaths,
128
+ ) -> None:
129
+ """
130
+ Perform various tests associated with relevant routines.
131
+
132
+ :param paths: Custom fixture for the configuration paths.
133
+ """
134
+
135
+ merged1 = paths.merged
136
+ merged2 = paths.merged
137
+
138
+ assert merged1 is not merged2
encommon/conftest.py CHANGED
@@ -11,6 +11,46 @@ from pathlib import Path
11
11
 
12
12
  from pytest import fixture
13
13
 
14
+ from .config import Config
15
+ from .config.test import SAMPLES
16
+ from .utils import save_text
17
+
18
+
19
+
20
+ def config_factory(
21
+ tmp_path: Path,
22
+ ) -> Config:
23
+ """
24
+ Construct the instance for use in the downstream tests.
25
+
26
+ :param tmp_path: pytest object for temporal filesystem.
27
+ :returns: Newly constructed instance of related class.
28
+ """
29
+
30
+ save_text(
31
+ f'{tmp_path}/config.yml',
32
+ content=(
33
+ 'enconfig:\n'
34
+ ' paths:\n'
35
+ f" - '{SAMPLES}/stark'\n"
36
+ f" - '{SAMPLES}/wayne'\n"
37
+ 'enlogger:\n'
38
+ ' stdo_level: info\n'
39
+ 'encrypts:\n'
40
+ ' phrases:\n'
41
+ ' default: phrase\n'))
42
+
43
+ config_log = f'{tmp_path}/config.log'
44
+
45
+ cargs = {
46
+ 'enlogger': {
47
+ 'file_path': config_log,
48
+ 'file_level': 'info'}}
49
+
50
+ return Config(
51
+ files=f'{tmp_path}/config.yml',
52
+ cargs=cargs)
53
+
14
54
 
15
55
 
16
56
  @fixture
@@ -24,21 +64,21 @@ def config_path(
24
64
  :returns: New resolved filesystem path object instance.
25
65
  """
26
66
 
67
+ config_factory(tmp_path)
27
68
 
28
- Path.mkdir(
29
- Path(f'{tmp_path}/wayne'))
30
-
31
- (Path(f'{tmp_path}/wayne')
32
- .joinpath('bwayne.yml')
33
- .write_text('name: Bruce Wayne'))
69
+ return tmp_path.resolve()
34
70
 
35
71
 
36
- Path.mkdir(
37
- Path(f'{tmp_path}/stark'))
38
72
 
39
- (Path(f'{tmp_path}/stark')
40
- .joinpath('tstark.yml')
41
- .write_text('name: Tony Stark'))
73
+ @fixture
74
+ def config(
75
+ tmp_path: Path,
76
+ ) -> Config:
77
+ """
78
+ Construct the instance for use in the downstream tests.
42
79
 
80
+ :param tmp_path: pytest object for temporal filesystem.
81
+ :returns: Newly constructed instance of related class.
82
+ """
43
83
 
44
- return tmp_path.resolve()
84
+ return config_factory(tmp_path)
@@ -9,9 +9,11 @@ is permitted, for more information consult the project license file.
9
9
 
10
10
  from .crypts import Crypts
11
11
  from .hashes import Hashes
12
+ from .params import CryptsParams
12
13
 
13
14
 
14
15
 
15
16
  __all__ = [
16
17
  'Crypts',
18
+ 'CryptsParams',
17
19
  'Hashes']
encommon/crypts/crypts.py CHANGED
@@ -15,6 +15,8 @@ from typing import TYPE_CHECKING
15
15
 
16
16
  from cryptography.fernet import Fernet
17
17
 
18
+ from ..types.strings import SEMPTY
19
+
18
20
  if TYPE_CHECKING:
19
21
  from .params import CryptsParams
20
22
 
@@ -157,4 +159,4 @@ def crypt_clean(
157
159
  :param value: String value that will returned decrypted.
158
160
  """
159
161
 
160
- return re_sub(r'[\n\s]', '', value)
162
+ return re_sub(r'[\n\s]', SEMPTY, value)
encommon/crypts/hashes.py CHANGED
@@ -90,7 +90,7 @@ class Hashes:
90
90
 
91
91
  string = self.__string.encode()
92
92
 
93
- return sha1(string).hexdigest() # noqa: S324
93
+ return sha1(string).hexdigest()
94
94
 
95
95
 
96
96
  @property
@@ -151,6 +151,6 @@ class Hashes:
151
151
 
152
152
  string = self.__string.encode()
153
153
 
154
- digest = sha1(string).digest() # noqa: S324
154
+ digest = sha1(string).digest()
155
155
 
156
156
  return b64encode(digest).decode()
@@ -12,33 +12,38 @@ from pytest import mark
12
12
  from pytest import raises
13
13
 
14
14
  from ..crypts import Crypts
15
+ from ..params import CryptsParams
15
16
 
16
17
 
17
18
 
18
19
  @fixture
19
- def phrases() -> dict[str, str]:
20
+ def crypts() -> Crypts:
20
21
  """
21
- Construct randomly generated Fernet keys for passphrases.
22
+ Construct the instance for use in the downstream tests.
22
23
 
23
- :returns: Randomly generated Fernet keys for passphrases.
24
+ :returns: Newly constructed instance of related class.
24
25
  """
25
26
 
26
- return {
27
+ phrases = {
27
28
  'default': Crypts.keygen(),
28
29
  'secrets': Crypts.keygen()}
29
30
 
31
+ params = CryptsParams(
32
+ phrases=phrases)
33
+
34
+ return Crypts(params=params)
35
+
30
36
 
31
37
 
32
38
  def test_Crypts(
33
- phrases: dict[str, str],
39
+ crypts: Crypts,
34
40
  ) -> None:
35
41
  """
36
42
  Perform various tests associated with relevant routines.
37
43
 
38
- :param phrases: Dictionary of randomly generated phrases.
44
+ :param crypts: Primary class instance for the encryption.
39
45
  """
40
46
 
41
- crypts = Crypts(phrases)
42
47
 
43
48
  attrs = list(crypts.__dict__)
44
49
 
@@ -46,14 +51,18 @@ def test_Crypts(
46
51
  '_Crypts__phrases']
47
52
 
48
53
 
49
- assert repr(crypts).startswith(
50
- '<encommon.crypts.crypts.Crypts')
51
- assert isinstance(hash(crypts), int)
52
- assert str(crypts).startswith(
53
- '<encommon.crypts.crypts.Crypts')
54
+ assert repr(crypts)[:23] == (
55
+ '<encommon.crypts.crypts')
56
+
57
+ assert hash(crypts) > 0
54
58
 
59
+ assert str(crypts)[:23] == (
60
+ '<encommon.crypts.crypts')
55
61
 
56
- assert crypts.phrases == phrases
62
+
63
+ assert len(crypts.phrases) == 2
64
+
65
+ assert len(crypts.keygen()) == 44
57
66
 
58
67
 
59
68
 
@@ -62,21 +71,20 @@ def test_Crypts(
62
71
  [('foo', 'default'),
63
72
  ('foo', 'secrets')])
64
73
  def test_Crypts_iterate(
74
+ crypts: Crypts,
65
75
  value: str,
66
76
  unique: str,
67
- phrases: dict[str, str],
68
77
  ) -> None:
69
78
  """
70
79
  Perform various tests associated with relevant routines.
71
80
 
81
+ :param crypts: Primary class instance for the encryption.
72
82
  :param value: String value that will returned encrypted.
73
83
  :param unique: Unique identifier of mapping passphrase.
74
- :param phrases: Dictionary of randomly generated phrases.
75
84
  """
76
85
 
77
- crypts = Crypts(phrases)
78
-
79
- encrypt = crypts.encrypt(value, unique)
86
+ encrypt = (
87
+ crypts.encrypt(value, unique))
80
88
 
81
89
  split = encrypt.split(';')
82
90
 
@@ -90,31 +98,45 @@ def test_Crypts_iterate(
90
98
 
91
99
 
92
100
  def test_Crypts_raises(
93
- phrases: dict[str, str],
101
+ crypts: Crypts,
94
102
  ) -> None:
95
103
  """
96
104
  Perform various tests associated with relevant routines.
97
105
 
98
- :param phrases: Dictionary of randomly generated phrases.
106
+ :param crypts: Primary class instance for the encryption.
99
107
  """
100
108
 
101
109
 
102
- with raises(ValueError) as reason:
110
+ _raises = raises(ValueError)
111
+
112
+ with _raises as reason:
103
113
  Crypts({'foo': 'bar'})
104
114
 
105
- assert str(reason.value) == 'default'
115
+ _reason = str(reason.value)
116
+
117
+ assert _reason == 'default'
118
+
106
119
 
120
+ crypts = Crypts(
121
+ crypts.phrases)
107
122
 
108
- crypts = Crypts(phrases)
109
123
 
124
+ _raises = raises(ValueError)
110
125
 
111
- with raises(ValueError) as reason:
126
+ with _raises as reason:
112
127
  crypts.decrypt('foo')
113
128
 
114
- assert str(reason.value) == 'string'
129
+ _reason = str(reason.value)
130
+
131
+ assert _reason == 'string'
132
+
133
+
134
+ _raises = raises(ValueError)
115
135
 
136
+ with _raises as reason:
137
+ string = '$ENCRYPT;1.1;f;oo'
138
+ crypts.decrypt(string)
116
139
 
117
- with raises(ValueError) as reason:
118
- crypts.decrypt('$ENCRYPT;1.1;f;oo')
140
+ _reason = str(reason.value)
119
141
 
120
- assert str(reason.value) == 'version'
142
+ assert _reason == 'version'
@@ -18,36 +18,38 @@ def test_Hashes() -> None:
18
18
 
19
19
  hashes = Hashes('string')
20
20
 
21
+
21
22
  attrs = list(hashes.__dict__)
22
23
 
23
24
  assert attrs == [
24
25
  '_Hashes__string']
25
26
 
26
27
 
27
- assert repr(hashes).startswith(
28
- '<encommon.crypts.hashes.Hashes')
29
- assert isinstance(hash(hashes), int)
30
- assert str(hashes).startswith(
31
- '<encommon.crypts.hashes.Hashes')
28
+ assert repr(hashes)[:23] == (
29
+ '<encommon.crypts.hashes')
32
30
 
31
+ assert hash(hashes) > 0
33
32
 
34
- assert hashes.string == 'string'
33
+ assert str(hashes)[:23] == (
34
+ '<encommon.crypts.hashes')
35
35
 
36
36
 
37
- assert hashes.md5.startswith('b4')
38
- assert hashes.md5.endswith('0f21')
37
+ assert hashes.string == 'string'
38
+
39
+ assert hashes.md5[:3] == 'b45'
40
+ assert hashes.md5[-2:] == '21'
39
41
 
40
- assert hashes.sha1.startswith('ec')
41
- assert hashes.sha1.endswith('904d')
42
+ assert hashes.sha1[:3] == 'ecb'
43
+ assert hashes.sha1[-2:] == '4d'
42
44
 
43
- assert hashes.sha256.startswith('47')
44
- assert hashes.sha256.endswith('2fa8')
45
+ assert hashes.sha256[:3] == '473'
46
+ assert hashes.sha256[-2:] == 'a8'
45
47
 
46
- assert hashes.sha512.startswith('27')
47
- assert hashes.sha512.endswith('6a87')
48
+ assert hashes.sha512[:3] == '275'
49
+ assert hashes.sha512[-2:] == '87'
48
50
 
49
- assert hashes.uuid.startswith('38')
50
- assert hashes.uuid.endswith('eee4')
51
+ assert hashes.uuid[:3] == '38f'
52
+ assert hashes.uuid[-2:] == 'e4'
51
53
 
52
- assert hashes.apache.startswith('7L')
53
- assert hashes.apache.endswith('kE0=')
54
+ assert hashes.apache[:3] == '7LJ'
55
+ assert hashes.apache[-2:] == '0='
@@ -7,6 +7,7 @@ is permitted, for more information consult the project license file.
7
7
 
8
8
 
9
9
 
10
+ from .common import findtz
10
11
  from .duration import Duration
11
12
  from .parse import parse_time
12
13
  from .parse import shift_time
@@ -20,6 +21,7 @@ from .window import Window
20
21
 
21
22
  __all__ = [
22
23
  'Duration',
24
+ 'findtz',
23
25
  'Timers',
24
26
  'Times',
25
27
  'Window',
encommon/times/common.py CHANGED
@@ -10,30 +10,64 @@ is permitted, for more information consult the project license file.
10
10
  from contextlib import suppress
11
11
  from datetime import datetime
12
12
  from datetime import timezone
13
+ from datetime import tzinfo
13
14
  from re import compile
14
15
  from typing import Any
16
+ from typing import Optional
15
17
  from typing import TYPE_CHECKING
16
18
  from typing import Union
19
+
20
+ from dateutil.tz import gettz
21
+
17
22
  if TYPE_CHECKING:
18
23
  from .times import Times
19
24
 
20
25
 
21
26
 
22
- NUMERISH = compile(r'^\-?\d+(\.\d+)?$')
23
- SNAPABLE = compile(r'^(\-|\+)[\d\@a-z\-\+]+$')
24
- STRINGNOW = {'None', 'null', 'now'}
27
+ NUMERISH = compile(
28
+ r'^\-?\d+(\.\d+)?$')
29
+
30
+ SNAPABLE = compile(
31
+ r'^(\-|\+)[\d\@a-z\-\+]+$')
32
+
33
+ STRINGNOW = {
34
+ 'None', 'null', 'now'}
35
+
36
+
25
37
 
26
38
  NUMERIC = Union[int, float]
27
- PARSABLE = Union[str, NUMERIC, datetime, 'Times']
28
- SCHEDULE = Union[str, dict[str, int]]
29
39
 
30
- UNIXEPOCH = '1970-01-01T00:00:00+0000'
31
- UNIXMPOCH = '1970-01-01T00:00:00.000000+0000'
32
- UNIXHPOCH = '01/01/1970 12:00AM UTC'
40
+ PARSABLE = Union[
41
+ str, NUMERIC,
42
+ datetime, 'Times']
43
+
44
+ SCHEDULE = Union[
45
+ str, dict[str, int]]
46
+
47
+
48
+
49
+ UNIXEPOCH = (
50
+ '1970-01-01T00:00:00+0000')
51
+
52
+ UNIXMPOCH = (
53
+ '1970-01-01T00:00:00.000000+0000')
33
54
 
34
- STAMP_SIMPLE = '%Y-%m-%dT%H:%M:%S%z'
35
- STAMP_SUBSEC = '%Y-%m-%dT%H:%M:%S.%f%z'
36
- STAMP_HUMAN = '%m/%d/%Y %I:%M%p %Z'
55
+ UNIXSPOCH = (
56
+ '1970-01-01T00:00:00Z')
57
+
58
+ UNIXHPOCH = (
59
+ '01/01/1970 12:00AM UTC')
60
+
61
+
62
+
63
+ STAMP_SIMPLE = (
64
+ '%Y-%m-%dT%H:%M:%S%z')
65
+
66
+ STAMP_SUBSEC = (
67
+ '%Y-%m-%dT%H:%M:%S.%f%z')
68
+
69
+ STAMP_HUMAN = (
70
+ '%m/%d/%Y %I:%M%p %Z')
37
71
 
38
72
 
39
73
 
@@ -57,14 +91,16 @@ def utcdatetime(
57
91
  :returns: Instance of datetime within the UTC timezone.
58
92
  """
59
93
 
94
+ tzinfo = timezone.utc
95
+
60
96
  if not args and not kwargs:
61
- return datetime.now(tz=timezone.utc)
97
+ return datetime.now(tz=tzinfo)
62
98
 
63
99
  if 'tzinfo' not in kwargs:
64
- kwargs['tzinfo'] = timezone.utc
100
+ kwargs['tzinfo'] = tzinfo
65
101
 
66
102
  return (
67
- datetime(*args, **kwargs) # noqa: DTZ001
103
+ datetime(*args, **kwargs)
68
104
  .astimezone(timezone.utc))
69
105
 
70
106
 
@@ -86,6 +122,8 @@ def strptime(
86
122
  :returns: Python datetime object containing related time.
87
123
  """
88
124
 
125
+ tzinfo = timezone.utc
126
+
89
127
  if isinstance(formats, str):
90
128
  formats = [formats]
91
129
 
@@ -93,14 +131,60 @@ def strptime(
93
131
  def _strptime(
94
132
  format: str,
95
133
  ) -> datetime:
134
+
96
135
  return (
97
136
  datetime
98
137
  .strptime(source, format)
99
- .astimezone(timezone.utc))
138
+ .astimezone(tzinfo))
100
139
 
101
140
 
102
141
  for format in formats:
142
+
103
143
  with suppress(ValueError):
104
144
  return _strptime(format)
105
145
 
146
+
106
147
  raise ValueError('invalid')
148
+
149
+
150
+
151
+ def strftime(
152
+ source: datetime,
153
+ format: str,
154
+ ) -> str:
155
+ """
156
+ Return the timestamp string for datetime object provided.
157
+
158
+ .. note::
159
+ This function is extremely pedantic and cosmetic.
160
+
161
+ :param source: Python datetime instance containing source.
162
+ :param format: Format for the timestamp string returned.
163
+ :returns: Timestamp string for datetime object provided.
164
+ """
165
+
166
+ return (
167
+ datetime
168
+ .strftime(source, format))
169
+
170
+
171
+
172
+ def findtz(
173
+ tzname: Optional[str] = None,
174
+ ) -> tzinfo:
175
+ """
176
+ Return the located timezone object for the provided name.
177
+
178
+ :param tzname: Name of the timezone associated to source.
179
+ :returns: Located timezone object for the provided name.
180
+ """
181
+
182
+ if tzname is None:
183
+ return timezone.utc
184
+
185
+ tzinfo = gettz(tzname)
186
+
187
+ if tzinfo is None:
188
+ raise ValueError('tzname')
189
+
190
+ return tzinfo