naeural-client 2.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.
Files changed (78) hide show
  1. naeural_client/__init__.py +13 -0
  2. naeural_client/_ver.py +13 -0
  3. naeural_client/base/__init__.py +6 -0
  4. naeural_client/base/distributed_custom_code_presets.py +44 -0
  5. naeural_client/base/generic_session.py +1763 -0
  6. naeural_client/base/instance.py +616 -0
  7. naeural_client/base/payload/__init__.py +1 -0
  8. naeural_client/base/payload/payload.py +66 -0
  9. naeural_client/base/pipeline.py +1499 -0
  10. naeural_client/base/plugin_template.py +5209 -0
  11. naeural_client/base/responses.py +209 -0
  12. naeural_client/base/transaction.py +157 -0
  13. naeural_client/base_decentra_object.py +143 -0
  14. naeural_client/bc/__init__.py +3 -0
  15. naeural_client/bc/base.py +1046 -0
  16. naeural_client/bc/chain.py +0 -0
  17. naeural_client/bc/ec.py +324 -0
  18. naeural_client/certs/__init__.py +0 -0
  19. naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt +22 -0
  20. naeural_client/code_cheker/__init__.py +1 -0
  21. naeural_client/code_cheker/base.py +520 -0
  22. naeural_client/code_cheker/checker.py +294 -0
  23. naeural_client/comm/__init__.py +2 -0
  24. naeural_client/comm/amqp_wrapper.py +338 -0
  25. naeural_client/comm/mqtt_wrapper.py +539 -0
  26. naeural_client/const/README.md +3 -0
  27. naeural_client/const/__init__.py +9 -0
  28. naeural_client/const/base.py +101 -0
  29. naeural_client/const/comms.py +80 -0
  30. naeural_client/const/environment.py +26 -0
  31. naeural_client/const/formatter.py +7 -0
  32. naeural_client/const/heartbeat.py +111 -0
  33. naeural_client/const/misc.py +20 -0
  34. naeural_client/const/payload.py +190 -0
  35. naeural_client/default/__init__.py +1 -0
  36. naeural_client/default/instance/__init__.py +4 -0
  37. naeural_client/default/instance/chain_dist_custom_job_01_plugin.py +54 -0
  38. naeural_client/default/instance/custom_web_app_01_plugin.py +118 -0
  39. naeural_client/default/instance/net_mon_01_plugin.py +45 -0
  40. naeural_client/default/instance/view_scene_01_plugin.py +28 -0
  41. naeural_client/default/session/mqtt_session.py +72 -0
  42. naeural_client/io_formatter/__init__.py +2 -0
  43. naeural_client/io_formatter/base/__init__.py +1 -0
  44. naeural_client/io_formatter/base/base_formatter.py +80 -0
  45. naeural_client/io_formatter/default/__init__.py +3 -0
  46. naeural_client/io_formatter/default/a_dummy.py +51 -0
  47. naeural_client/io_formatter/default/aixp1.py +113 -0
  48. naeural_client/io_formatter/default/default.py +22 -0
  49. naeural_client/io_formatter/io_formatter_manager.py +96 -0
  50. naeural_client/logging/__init__.py +1 -0
  51. naeural_client/logging/base_logger.py +2056 -0
  52. naeural_client/logging/logger_mixins/__init__.py +12 -0
  53. naeural_client/logging/logger_mixins/class_instance_mixin.py +92 -0
  54. naeural_client/logging/logger_mixins/computer_vision_mixin.py +443 -0
  55. naeural_client/logging/logger_mixins/datetime_mixin.py +344 -0
  56. naeural_client/logging/logger_mixins/download_mixin.py +421 -0
  57. naeural_client/logging/logger_mixins/general_serialization_mixin.py +242 -0
  58. naeural_client/logging/logger_mixins/json_serialization_mixin.py +481 -0
  59. naeural_client/logging/logger_mixins/pickle_serialization_mixin.py +301 -0
  60. naeural_client/logging/logger_mixins/process_mixin.py +63 -0
  61. naeural_client/logging/logger_mixins/resource_size_mixin.py +81 -0
  62. naeural_client/logging/logger_mixins/timers_mixin.py +501 -0
  63. naeural_client/logging/logger_mixins/upload_mixin.py +260 -0
  64. naeural_client/logging/logger_mixins/utils_mixin.py +675 -0
  65. naeural_client/logging/small_logger.py +93 -0
  66. naeural_client/logging/tzlocal/__init__.py +20 -0
  67. naeural_client/logging/tzlocal/unix.py +231 -0
  68. naeural_client/logging/tzlocal/utils.py +113 -0
  69. naeural_client/logging/tzlocal/win32.py +151 -0
  70. naeural_client/logging/tzlocal/windows_tz.py +718 -0
  71. naeural_client/plugins_manager_mixin.py +273 -0
  72. naeural_client/utils/__init__.py +2 -0
  73. naeural_client/utils/comm_utils.py +44 -0
  74. naeural_client/utils/dotenv.py +75 -0
  75. naeural_client-2.0.0.dist-info/METADATA +365 -0
  76. naeural_client-2.0.0.dist-info/RECORD +78 -0
  77. naeural_client-2.0.0.dist-info/WHEEL +4 -0
  78. naeural_client-2.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,93 @@
1
+ from .base_logger import BaseLogger
2
+ from .logger_mixins import (_ClassInstanceMixin,
3
+ _ComputerVisionMixin,
4
+ _DateTimeMixin,
5
+ _DownloadMixin,
6
+ _GeneralSerializationMixin,
7
+ _JSONSerializationMixin,
8
+ _PickleSerializationMixin,
9
+ _ProcessMixin,
10
+ _ResourceSizeMixin,
11
+ _TimersMixin,
12
+ _UploadMixin,
13
+ _UtilsMixin
14
+ )
15
+
16
+
17
+ class Logger(
18
+ BaseLogger,
19
+ _ClassInstanceMixin,
20
+ _ComputerVisionMixin,
21
+ _DateTimeMixin,
22
+ _DownloadMixin,
23
+ _GeneralSerializationMixin,
24
+ _JSONSerializationMixin,
25
+ _PickleSerializationMixin,
26
+ _ProcessMixin,
27
+ _ResourceSizeMixin,
28
+ _TimersMixin,
29
+ _UploadMixin,
30
+ _UtilsMixin):
31
+
32
+ def __init__(self, lib_name="",
33
+ lib_ver="",
34
+ config_file="",
35
+ config_data={},
36
+ base_folder=None,
37
+ app_folder=None,
38
+ show_time=True,
39
+ config_file_encoding=None,
40
+ no_folders_no_save=False,
41
+ max_lines=None,
42
+ HTML=False,
43
+ DEBUG=True,
44
+ data_config_subfolder=None,
45
+ check_additional_configs=False,
46
+ default_color='n',
47
+ ):
48
+
49
+ super(Logger, self).__init__(
50
+ lib_name=lib_name, lib_ver=lib_ver,
51
+ config_file=config_file,
52
+ base_folder=base_folder,
53
+ app_folder=app_folder,
54
+ show_time=show_time,
55
+ config_data=config_data,
56
+ config_file_encoding=config_file_encoding,
57
+ no_folders_no_save=no_folders_no_save,
58
+ max_lines=max_lines,
59
+ HTML=HTML,
60
+ DEBUG=DEBUG,
61
+ data_config_subfolder=data_config_subfolder,
62
+ check_additional_configs=check_additional_configs,
63
+ default_color=default_color,
64
+ )
65
+ self.cleanup_logs(archive_older_than_days=2)
66
+
67
+ return
68
+
69
+ def iP(self, str_msg, results=False, show_time=False, noprefix=False, color=None):
70
+ if self.runs_from_ipython():
71
+ return self._logger(
72
+ str_msg,
73
+ show=True, results=results, show_time=show_time,
74
+ noprefix=noprefix, color=color
75
+ )
76
+ return
77
+
78
+ def __repr__(self):
79
+ # Get the name of the class
80
+ class_name = self.__class__.__name__
81
+
82
+ # Get public properties (those not starting with "_")
83
+ public_properties = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
84
+
85
+ # Convert properties to a string representation
86
+ properties_str = ", ".join(f"{k}={v!r}" for k, v in public_properties.items())
87
+
88
+ return f"{class_name}({properties_str})"
89
+
90
+
91
+ if __name__ == '__main__':
92
+ l = Logger('TEST', base_folder='Dropbox', app_folder='_libraries_testdata')
93
+ l.P("All check", color='green')
@@ -0,0 +1,20 @@
1
+ import sys
2
+
3
+ if sys.platform == "win32":
4
+ from .win32 import (
5
+ get_localzone,
6
+ get_localzone_name,
7
+ reload_localzone,
8
+ ) # pragma: no cover
9
+ else:
10
+ from .unix import get_localzone, get_localzone_name, reload_localzone
11
+
12
+ from .utils import assert_tz_offset
13
+
14
+
15
+ __all__ = [
16
+ "get_localzone",
17
+ "get_localzone_name",
18
+ "reload_localzone",
19
+ "assert_tz_offset",
20
+ ]
@@ -0,0 +1,231 @@
1
+ import logging
2
+ import os
3
+ import re
4
+ import sys
5
+ import warnings
6
+ from datetime import timezone
7
+
8
+ from . import utils
9
+
10
+ if sys.version_info >= (3, 9):
11
+ import zoneinfo # pragma: no cover
12
+ else:
13
+ from backports import zoneinfo # pragma: no cover
14
+
15
+ _cache_tz = None
16
+ _cache_tz_name = None
17
+
18
+ log = logging.getLogger("tzlocal")
19
+
20
+
21
+ def _get_localzone_name(_root="/"):
22
+ """Tries to find the local timezone configuration.
23
+
24
+ This method finds the timezone name, if it can, or it returns None.
25
+
26
+ The parameter _root makes the function look for files like /etc/localtime
27
+ beneath the _root directory. This is primarily used by the tests.
28
+ In normal usage you call the function without parameters."""
29
+
30
+ # First try the ENV setting.
31
+ tzenv = utils._tz_name_from_env()
32
+ if tzenv:
33
+ return tzenv
34
+
35
+ # Are we under Termux on Android?
36
+ if os.path.exists(os.path.join(_root, "system/bin/getprop")):
37
+ log.debug("This looks like Termux")
38
+
39
+ import subprocess
40
+
41
+ try:
42
+ androidtz = (
43
+ subprocess.check_output(["getprop", "persist.sys.timezone"])
44
+ .strip()
45
+ .decode()
46
+ )
47
+ return androidtz
48
+ except (OSError, subprocess.CalledProcessError):
49
+ # proot environment or failed to getprop
50
+ log.debug("It's not termux?")
51
+ pass
52
+
53
+ # Now look for distribution specific configuration files
54
+ # that contain the timezone name.
55
+
56
+ # Stick all of them in a dict, to compare later.
57
+ found_configs = {}
58
+
59
+ for configfile in ("etc/timezone", "var/db/zoneinfo"):
60
+ tzpath = os.path.join(_root, configfile)
61
+ try:
62
+ with open(tzpath) as tzfile:
63
+ data = tzfile.read()
64
+ log.debug(f"{tzpath} found, contents:\n {data}")
65
+
66
+ etctz = data.strip("/ \t\r\n")
67
+ if not etctz:
68
+ # Empty file, skip
69
+ continue
70
+ for etctz in etctz.splitlines():
71
+ # Get rid of host definitions and comments:
72
+ if " " in etctz:
73
+ etctz, dummy = etctz.split(" ", 1)
74
+ if "#" in etctz:
75
+ etctz, dummy = etctz.split("#", 1)
76
+ if not etctz:
77
+ continue
78
+
79
+ found_configs[tzpath] = etctz.replace(" ", "_")
80
+
81
+ except (OSError, UnicodeDecodeError):
82
+ # File doesn't exist or is a directory, or it's a binary file.
83
+ continue
84
+
85
+ # CentOS has a ZONE setting in /etc/sysconfig/clock,
86
+ # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
87
+ # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
88
+ # We look through these files for a timezone:
89
+
90
+ zone_re = re.compile(r"\s*ZONE\s*=\s*\"")
91
+ timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"")
92
+ end_re = re.compile('"')
93
+
94
+ for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
95
+ tzpath = os.path.join(_root, filename)
96
+ try:
97
+ with open(tzpath, "rt") as tzfile:
98
+ data = tzfile.readlines()
99
+ log.debug(f"{tzpath} found, contents:\n {data}")
100
+
101
+ for line in data:
102
+ # Look for the ZONE= setting.
103
+ match = zone_re.match(line)
104
+ if match is None:
105
+ # No ZONE= setting. Look for the TIMEZONE= setting.
106
+ match = timezone_re.match(line)
107
+ if match is not None:
108
+ # Some setting existed
109
+ line = line[match.end():]
110
+ etctz = line[: end_re.search(line).start()]
111
+
112
+ # We found a timezone
113
+ found_configs[tzpath] = etctz.replace(" ", "_")
114
+
115
+ except (OSError, UnicodeDecodeError):
116
+ # UnicodeDecode handles when clock is symlink to /etc/localtime
117
+ continue
118
+
119
+ # systemd distributions use symlinks that include the zone name,
120
+ # see manpage of localtime(5) and timedatectl(1)
121
+ tzpath = os.path.join(_root, "etc/localtime")
122
+ if os.path.exists(tzpath) and os.path.islink(tzpath):
123
+ log.debug(f"{tzpath} found")
124
+ etctz = os.path.realpath(tzpath)
125
+ start = etctz.find("/") + 1
126
+ while start != 0:
127
+ etctz = etctz[start:]
128
+ try:
129
+ zoneinfo.ZoneInfo(etctz)
130
+ tzinfo = f"{tzpath} is a symlink to"
131
+ found_configs[tzinfo] = etctz.replace(" ", "_")
132
+ # Only need first valid relative path in simlink.
133
+ break
134
+ except zoneinfo.ZoneInfoNotFoundError:
135
+ pass
136
+ start = etctz.find("/") + 1
137
+
138
+ if len(found_configs) > 0:
139
+ log.debug(f"{len(found_configs)} found:\n {found_configs}")
140
+ # We found some explicit config of some sort!
141
+ if len(found_configs) > 1:
142
+ # Uh-oh, multiple configs. See if they match:
143
+ unique_tzs = set()
144
+ zoneinfopath = os.path.join(_root, "usr", "share", "zoneinfo")
145
+ directory_depth = len(zoneinfopath.split(os.path.sep))
146
+
147
+ for tzname in found_configs.values():
148
+ # Look them up in /usr/share/zoneinfo, and find what they
149
+ # really point to:
150
+ path = os.path.realpath(os.path.join(zoneinfopath, *tzname.split("/")))
151
+ real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
152
+ unique_tzs.add(real_zone_name)
153
+
154
+ if len(unique_tzs) != 1:
155
+ message = "Multiple conflicting time zone configurations found:\n"
156
+ for key, value in found_configs.items():
157
+ message += f"{key}: {value}\n"
158
+ message += "Fix the configuration, or set the time zone in a TZ environment variable.\n"
159
+ raise zoneinfo.ZoneInfoNotFoundError(message)
160
+
161
+ # We found exactly one config! Use it.
162
+ return list(found_configs.values())[0]
163
+
164
+
165
+ def _get_localzone(_root="/"):
166
+ """Creates a timezone object from the timezone name.
167
+
168
+ If there is no timezone config, it will try to create a file from the
169
+ localtime timezone, and if there isn't one, it will default to UTC.
170
+
171
+ The parameter _root makes the function look for files like /etc/localtime
172
+ beneath the _root directory. This is primarily used by the tests.
173
+ In normal usage you call the function without parameters."""
174
+
175
+ # First try the ENV setting.
176
+ tzenv = utils._tz_from_env()
177
+ if tzenv:
178
+ return tzenv
179
+
180
+ tzname = _get_localzone_name(_root)
181
+ if tzname is None:
182
+ # No explicit setting existed. Use localtime
183
+ log.debug("No explicit setting existed. Use localtime")
184
+ for filename in ("etc/localtime", "usr/local/etc/localtime"):
185
+ tzpath = os.path.join(_root, filename)
186
+
187
+ if not os.path.exists(tzpath):
188
+ continue
189
+ with open(tzpath, "rb") as tzfile:
190
+ tz = zoneinfo.ZoneInfo.from_file(tzfile, key="local")
191
+ break
192
+ else:
193
+ warnings.warn("Can not find any timezone configuration, defaulting to UTC.")
194
+ tz = timezone.utc
195
+ else:
196
+ tz = zoneinfo.ZoneInfo(tzname)
197
+
198
+ if _root == "/":
199
+ # We are using a file in etc to name the timezone.
200
+ # Verify that the timezone specified there is actually used:
201
+ utils.assert_tz_offset(tz, error=False)
202
+ return tz
203
+
204
+
205
+ def get_localzone_name():
206
+ """Get the computers configured local timezone name, if any."""
207
+ global _cache_tz_name
208
+ if _cache_tz_name is None:
209
+ _cache_tz_name = _get_localzone_name()
210
+
211
+ return _cache_tz_name
212
+
213
+
214
+ def get_localzone():
215
+ """Get the computers configured local timezone, if any."""
216
+
217
+ global _cache_tz
218
+ if _cache_tz is None:
219
+ _cache_tz = _get_localzone()
220
+
221
+ return _cache_tz
222
+
223
+
224
+ def reload_localzone():
225
+ """Reload the cached localzone. You need to call this if the timezone has changed."""
226
+ global _cache_tz_name
227
+ global _cache_tz
228
+ _cache_tz_name = _get_localzone_name()
229
+ _cache_tz = _get_localzone()
230
+
231
+ return _cache_tz
@@ -0,0 +1,113 @@
1
+ import logging
2
+ import os
3
+ import time
4
+ import datetime
5
+ import calendar
6
+ import warnings
7
+
8
+
9
+ try:
10
+ import zoneinfo # pragma: no cover
11
+ except ImportError:
12
+ from backports import zoneinfo # pragma: no cover
13
+
14
+ from . import windows_tz
15
+
16
+ log = logging.getLogger("tzlocal")
17
+
18
+
19
+ def get_tz_offset(tz):
20
+ """Get timezone's offset using built-in function datetime.utcoffset()."""
21
+ return int(datetime.datetime.now(tz).utcoffset().total_seconds())
22
+
23
+
24
+ def assert_tz_offset(tz, error=True):
25
+ """Assert that system's timezone offset equals to the timezone offset found.
26
+
27
+ If they don't match, we probably have a misconfiguration, for example, an
28
+ incorrect timezone set in /etc/timezone file in systemd distributions.
29
+
30
+ If error is True, this method will raise a ValueError, otherwise it will
31
+ emit a warning.
32
+ """
33
+
34
+ tz_offset = get_tz_offset(tz)
35
+ system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime())
36
+ # No one has timezone offsets less than a minute, so this should be close enough:
37
+ if abs(tz_offset - system_offset) > 60:
38
+ msg = (
39
+ "Timezone offset does not match system offset: {} != {}. "
40
+ "Please, check your config files."
41
+ ).format(tz_offset, system_offset)
42
+ if error:
43
+ raise ValueError(msg)
44
+ warnings.warn(msg)
45
+
46
+
47
+ def _tz_name_from_env(tzenv=None):
48
+ if tzenv is None:
49
+ tzenv = os.environ.get("TZ")
50
+
51
+ if not tzenv:
52
+ return None
53
+
54
+ log.debug(f"Found a TZ environment: {tzenv}")
55
+
56
+ if tzenv[0] == ":":
57
+ tzenv = tzenv[1:]
58
+
59
+ if tzenv in windows_tz.tz_win:
60
+ # Yup, it's a timezone
61
+ return tzenv
62
+
63
+ if os.path.isabs(tzenv) and os.path.exists(tzenv):
64
+ # It's a file specification, expand it, if possible
65
+ parts = os.path.realpath(tzenv).split(os.sep)
66
+
67
+ # Is it a zone info zone?
68
+ possible_tz = "/".join(parts[-2:])
69
+ if possible_tz in windows_tz.tz_win:
70
+ # Yup, it is
71
+ return possible_tz
72
+
73
+ # Maybe it's a short one, like UTC?
74
+ if parts[-1] in windows_tz.tz_win:
75
+ # Indeed
76
+ return parts[-1]
77
+
78
+ log.debug("TZ does not contain a time zone name")
79
+ return None
80
+
81
+
82
+ def _tz_from_env(tzenv=None):
83
+ if tzenv is None:
84
+ tzenv = os.environ.get("TZ")
85
+
86
+ if not tzenv:
87
+ return None
88
+
89
+ # Some weird format that exists:
90
+ if tzenv[0] == ":":
91
+ tzenv = tzenv[1:]
92
+
93
+ # TZ specifies a file
94
+ if os.path.isabs(tzenv) and os.path.exists(tzenv):
95
+ # Try to see if we can figure out the name
96
+ tzname = _tz_name_from_env(tzenv)
97
+ if not tzname:
98
+ # Nope, not a standard timezone name, just take the filename
99
+ tzname = tzenv.split(os.sep)[-1]
100
+ with open(tzenv, "rb") as tzfile:
101
+ return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
102
+
103
+ # TZ must specify a zoneinfo zone.
104
+ try:
105
+ tz = zoneinfo.ZoneInfo(tzenv)
106
+ # That worked, so we return this:
107
+ return tz
108
+ except zoneinfo.ZoneInfoNotFoundError:
109
+ # Nope, it's something like "PST4DST" etc, we can't handle that.
110
+ raise zoneinfo.ZoneInfoNotFoundError(
111
+ "tzlocal() does not support non-zoneinfo timezones like %s. \n"
112
+ "Please use a timezone in the form of Continent/City" % tzenv
113
+ ) from None
@@ -0,0 +1,151 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ try:
5
+ import _winreg as winreg
6
+ except ImportError:
7
+ import winreg
8
+
9
+ try:
10
+ import zoneinfo # pragma: no cover
11
+ except ImportError:
12
+ from backports import zoneinfo # pragma: no cover
13
+
14
+ from .windows_tz import win_tz
15
+ from . import utils
16
+
17
+ _cache_tz = None
18
+ _cache_tz_name = None
19
+
20
+ log = logging.getLogger("tzlocal")
21
+
22
+
23
+ def valuestodict(key):
24
+ """Convert a registry key's values to a dictionary."""
25
+ result = {}
26
+ size = winreg.QueryInfoKey(key)[1]
27
+ for i in range(size):
28
+ data = winreg.EnumValue(key, i)
29
+ result[data[0]] = data[1]
30
+ return result
31
+
32
+
33
+ def _get_dst_info(tz):
34
+ # Find the offset for when it doesn't have DST:
35
+ dst_offset = std_offset = None
36
+ has_dst = False
37
+ year = datetime.now().year
38
+ for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
39
+ if tz.dst(dt).total_seconds() == 0.0:
40
+ # OK, no DST during winter, get this offset
41
+ std_offset = tz.utcoffset(dt).total_seconds()
42
+ else:
43
+ has_dst = True
44
+
45
+ return has_dst, std_offset, dst_offset
46
+
47
+
48
+ def _get_localzone_name():
49
+ # Windows is special. It has unique time zone names (in several
50
+ # meanings of the word) available, but unfortunately, they can be
51
+ # translated to the language of the operating system, so we need to
52
+ # do a backwards lookup, by going through all time zones and see which
53
+ # one matches.
54
+ tzenv = utils._tz_name_from_env()
55
+ if tzenv:
56
+ return tzenv
57
+
58
+ log.debug("Looking up time zone info from registry")
59
+ handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
60
+
61
+ TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
62
+ localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
63
+ keyvalues = valuestodict(localtz)
64
+ localtz.Close()
65
+
66
+ if "TimeZoneKeyName" in keyvalues:
67
+ # Windows 7 and later
68
+
69
+ # For some reason this returns a string with loads of NUL bytes at
70
+ # least on some systems. I don't know if this is a bug somewhere, I
71
+ # just work around it.
72
+ tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
73
+ else:
74
+ # Don't support XP any longer
75
+ raise LookupError("Can not find Windows timezone configuration")
76
+
77
+ timezone = win_tz.get(tzkeyname)
78
+ if timezone is None:
79
+ # Nope, that didn't work. Try adding "Standard Time",
80
+ # it seems to work a lot of times:
81
+ timezone = win_tz.get(tzkeyname + " Standard Time")
82
+
83
+ # Return what we have.
84
+ if timezone is None:
85
+ raise zoneinfo.ZoneInfoNotFoundError(tzkeyname)
86
+
87
+ if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
88
+ # DST is disabled, so don't return the timezone name,
89
+ # instead return Etc/GMT+offset
90
+
91
+ tz = zoneinfo.ZoneInfo(timezone)
92
+ has_dst, std_offset, dst_offset = _get_dst_info(tz)
93
+ if not has_dst:
94
+ # The DST is turned off in the windows configuration,
95
+ # but this timezone doesn't have DST so it doesn't matter
96
+ return timezone
97
+
98
+ if std_offset is None:
99
+ raise zoneinfo.ZoneInfoNotFoundError(
100
+ f"{tzkeyname} claims to not have a non-DST time!?"
101
+ )
102
+
103
+ if std_offset % 3600:
104
+ # I can't convert this to an hourly offset
105
+ raise zoneinfo.ZoneInfoNotFoundError(
106
+ f"tzlocal can't support disabling DST in the {timezone} zone."
107
+ )
108
+
109
+ # This has whole hours as offset, return it as Etc/GMT
110
+ return f"Etc/GMT{-std_offset//3600:+.0f}"
111
+
112
+ return timezone
113
+
114
+
115
+ def get_localzone_name():
116
+ """Get the zoneinfo timezone name that matches the Windows-configured timezone."""
117
+ global _cache_tz_name
118
+ if _cache_tz_name is None:
119
+ _cache_tz_name = _get_localzone_name()
120
+
121
+ return _cache_tz_name
122
+
123
+
124
+ def get_localzone():
125
+ """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""
126
+
127
+ global _cache_tz
128
+ if _cache_tz is None:
129
+ _cache_tz = zoneinfo.ZoneInfo(get_localzone_name())
130
+
131
+ if not utils._tz_name_from_env():
132
+ # If the timezone does NOT come from a TZ environment variable,
133
+ # verify that it's correct. If it's from the environment,
134
+ # we accept it, this is so you can run tests with different timezones.
135
+ utils.assert_tz_offset(_cache_tz, error=False)
136
+
137
+ return _cache_tz
138
+
139
+
140
+ def reload_localzone():
141
+ """Reload the cached localzone. You need to call this if the timezone has changed."""
142
+ global _cache_tz
143
+ global _cache_tz_name
144
+ _cache_tz_name = _get_localzone_name()
145
+ _cache_tz = zoneinfo.ZoneInfo(_cache_tz_name)
146
+ utils.assert_tz_offset(_cache_tz, error=False)
147
+ return _cache_tz
148
+
149
+
150
+ if __name__ == '__main__':
151
+ res = get_localzone_name()