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.
- naeural_client/__init__.py +13 -0
- naeural_client/_ver.py +13 -0
- naeural_client/base/__init__.py +6 -0
- naeural_client/base/distributed_custom_code_presets.py +44 -0
- naeural_client/base/generic_session.py +1763 -0
- naeural_client/base/instance.py +616 -0
- naeural_client/base/payload/__init__.py +1 -0
- naeural_client/base/payload/payload.py +66 -0
- naeural_client/base/pipeline.py +1499 -0
- naeural_client/base/plugin_template.py +5209 -0
- naeural_client/base/responses.py +209 -0
- naeural_client/base/transaction.py +157 -0
- naeural_client/base_decentra_object.py +143 -0
- naeural_client/bc/__init__.py +3 -0
- naeural_client/bc/base.py +1046 -0
- naeural_client/bc/chain.py +0 -0
- naeural_client/bc/ec.py +324 -0
- naeural_client/certs/__init__.py +0 -0
- naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt +22 -0
- naeural_client/code_cheker/__init__.py +1 -0
- naeural_client/code_cheker/base.py +520 -0
- naeural_client/code_cheker/checker.py +294 -0
- naeural_client/comm/__init__.py +2 -0
- naeural_client/comm/amqp_wrapper.py +338 -0
- naeural_client/comm/mqtt_wrapper.py +539 -0
- naeural_client/const/README.md +3 -0
- naeural_client/const/__init__.py +9 -0
- naeural_client/const/base.py +101 -0
- naeural_client/const/comms.py +80 -0
- naeural_client/const/environment.py +26 -0
- naeural_client/const/formatter.py +7 -0
- naeural_client/const/heartbeat.py +111 -0
- naeural_client/const/misc.py +20 -0
- naeural_client/const/payload.py +190 -0
- naeural_client/default/__init__.py +1 -0
- naeural_client/default/instance/__init__.py +4 -0
- naeural_client/default/instance/chain_dist_custom_job_01_plugin.py +54 -0
- naeural_client/default/instance/custom_web_app_01_plugin.py +118 -0
- naeural_client/default/instance/net_mon_01_plugin.py +45 -0
- naeural_client/default/instance/view_scene_01_plugin.py +28 -0
- naeural_client/default/session/mqtt_session.py +72 -0
- naeural_client/io_formatter/__init__.py +2 -0
- naeural_client/io_formatter/base/__init__.py +1 -0
- naeural_client/io_formatter/base/base_formatter.py +80 -0
- naeural_client/io_formatter/default/__init__.py +3 -0
- naeural_client/io_formatter/default/a_dummy.py +51 -0
- naeural_client/io_formatter/default/aixp1.py +113 -0
- naeural_client/io_formatter/default/default.py +22 -0
- naeural_client/io_formatter/io_formatter_manager.py +96 -0
- naeural_client/logging/__init__.py +1 -0
- naeural_client/logging/base_logger.py +2056 -0
- naeural_client/logging/logger_mixins/__init__.py +12 -0
- naeural_client/logging/logger_mixins/class_instance_mixin.py +92 -0
- naeural_client/logging/logger_mixins/computer_vision_mixin.py +443 -0
- naeural_client/logging/logger_mixins/datetime_mixin.py +344 -0
- naeural_client/logging/logger_mixins/download_mixin.py +421 -0
- naeural_client/logging/logger_mixins/general_serialization_mixin.py +242 -0
- naeural_client/logging/logger_mixins/json_serialization_mixin.py +481 -0
- naeural_client/logging/logger_mixins/pickle_serialization_mixin.py +301 -0
- naeural_client/logging/logger_mixins/process_mixin.py +63 -0
- naeural_client/logging/logger_mixins/resource_size_mixin.py +81 -0
- naeural_client/logging/logger_mixins/timers_mixin.py +501 -0
- naeural_client/logging/logger_mixins/upload_mixin.py +260 -0
- naeural_client/logging/logger_mixins/utils_mixin.py +675 -0
- naeural_client/logging/small_logger.py +93 -0
- naeural_client/logging/tzlocal/__init__.py +20 -0
- naeural_client/logging/tzlocal/unix.py +231 -0
- naeural_client/logging/tzlocal/utils.py +113 -0
- naeural_client/logging/tzlocal/win32.py +151 -0
- naeural_client/logging/tzlocal/windows_tz.py +718 -0
- naeural_client/plugins_manager_mixin.py +273 -0
- naeural_client/utils/__init__.py +2 -0
- naeural_client/utils/comm_utils.py +44 -0
- naeural_client/utils/dotenv.py +75 -0
- naeural_client-2.0.0.dist-info/METADATA +365 -0
- naeural_client-2.0.0.dist-info/RECORD +78 -0
- naeural_client-2.0.0.dist-info/WHEEL +4 -0
- 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()
|