sibi-dst 0.3.39__py3-none-any.whl → 0.3.42__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.
- sibi_dst/df_helper/__init__.py +2 -0
- sibi_dst/df_helper/_artifact_updater_multi_wrapper.py +257 -0
- sibi_dst/utils/__init__.py +3 -0
- sibi_dst/utils/data_utils.py +66 -25
- sibi_dst/utils/data_wrapper.py +586 -286
- sibi_dst/utils/date_utils.py +118 -113
- sibi_dst/utils/log_utils.py +57 -18
- sibi_dst/utils/phone_formatter.py +127 -0
- {sibi_dst-0.3.39.dist-info → sibi_dst-0.3.42.dist-info}/METADATA +1 -1
- {sibi_dst-0.3.39.dist-info → sibi_dst-0.3.42.dist-info}/RECORD +11 -9
- {sibi_dst-0.3.39.dist-info → sibi_dst-0.3.42.dist-info}/WHEEL +0 -0
sibi_dst/utils/date_utils.py
CHANGED
@@ -140,21 +140,32 @@ class DateUtils:
|
|
140
140
|
'last_month': lambda: cls.get_month_range(n=-1),
|
141
141
|
'current_year': lambda: cls.get_year_timerange(today().year),
|
142
142
|
'current_quarter': lambda: (
|
143
|
-
|
143
|
+
cls.get_first_day_of_the_quarter(today()), cls.get_last_day_of_the_quarter(today())),
|
144
144
|
'ytd': lambda: (datetime.date(today().year, 1, 1), today()),
|
145
145
|
}
|
146
146
|
|
147
|
-
|
148
|
-
|
147
|
+
|
148
|
+
class FileAgeChecker:
|
149
|
+
def __init__(self, logger=None):
|
150
|
+
self.logger = logger or Logger.default_logger(logger_name=self.__class__.__name__)
|
151
|
+
|
152
|
+
def is_file_older_than(
|
153
|
+
self,
|
154
|
+
file_path: str,
|
155
|
+
max_age_minutes: int,
|
156
|
+
fs: Optional[fsspec.AbstractFileSystem] = None,
|
157
|
+
ignore_missing: bool = False,
|
158
|
+
verbose: bool = False,
|
159
|
+
) -> bool:
|
149
160
|
"""
|
150
|
-
Check if a file or
|
161
|
+
Check if a file or directory is older than the specified max_age_minutes.
|
151
162
|
|
152
|
-
:param file_path: Path to the file or
|
163
|
+
:param file_path: Path to the file or directory.
|
153
164
|
:param max_age_minutes: Maximum allowed age in minutes.
|
154
|
-
:param fs: Filesystem object
|
155
|
-
:param ignore_missing:
|
156
|
-
:param verbose:
|
157
|
-
:return: True if
|
165
|
+
:param fs: Filesystem object. Defaults to local filesystem.
|
166
|
+
:param ignore_missing: Treat missing paths as not old if True.
|
167
|
+
:param verbose: Enable detailed logging.
|
168
|
+
:return: True if older than max_age_minutes, False otherwise.
|
158
169
|
"""
|
159
170
|
fs = fs or fsspec.filesystem("file")
|
160
171
|
self.logger.info(f"Checking age for {file_path}...")
|
@@ -165,136 +176,129 @@ class DateUtils:
|
|
165
176
|
return not ignore_missing
|
166
177
|
|
167
178
|
if fs.isdir(file_path):
|
168
|
-
self.logger.info(f"Found
|
169
|
-
|
170
|
-
|
179
|
+
self.logger.info(f"Found directory: {file_path}")
|
180
|
+
age = self._get_directory_age_minutes(file_path, fs, verbose)
|
171
181
|
elif fs.isfile(file_path):
|
172
|
-
|
173
|
-
|
182
|
+
age = self._get_file_age_minutes(file_path, fs, verbose)
|
174
183
|
else:
|
175
|
-
self.logger.warning(f"Path {file_path} is neither
|
184
|
+
self.logger.warning(f"Path {file_path} is neither file nor directory.")
|
176
185
|
return True
|
177
186
|
|
187
|
+
return age > max_age_minutes
|
188
|
+
|
178
189
|
except Exception as e:
|
179
|
-
self.logger.warning(f"Error checking
|
190
|
+
self.logger.warning(f"Error checking {file_path}: {str(e)}")
|
180
191
|
return True
|
181
192
|
|
182
|
-
def
|
183
|
-
|
193
|
+
def get_file_or_dir_age_minutes(
|
194
|
+
self,
|
195
|
+
file_path: str,
|
196
|
+
fs: Optional[fsspec.AbstractFileSystem] = None,
|
197
|
+
) -> float:
|
184
198
|
"""
|
185
|
-
|
199
|
+
Get age of file/directory in minutes. Returns infinity for errors/missing paths.
|
186
200
|
|
187
|
-
:param
|
188
|
-
:param
|
189
|
-
:
|
190
|
-
:param verbose: If True, log detailed information.
|
191
|
-
:return: True if the oldest file is older than max_age_minutes, False otherwise.
|
201
|
+
:param file_path: Path to check.
|
202
|
+
:param fs: Filesystem object. Defaults to local filesystem.
|
203
|
+
:return: Age in minutes or infinity if unavailable.
|
192
204
|
"""
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
modification_times = [
|
199
|
-
self._get_modification_time(fs.info(file), file)
|
200
|
-
for file in all_files
|
201
|
-
if self._is_valid_file(file, fs)
|
202
|
-
]
|
205
|
+
fs = fs or fsspec.filesystem("file")
|
206
|
+
try:
|
207
|
+
if not fs.exists(file_path):
|
208
|
+
self.logger.info(f"Path not found: {file_path}")
|
209
|
+
return float("inf")
|
203
210
|
|
204
|
-
|
205
|
-
|
206
|
-
|
211
|
+
if fs.isdir(file_path):
|
212
|
+
return self._get_directory_age_minutes(file_path, fs, verbose=False)
|
213
|
+
if fs.isfile(file_path):
|
214
|
+
return self._get_file_age_minutes(file_path, fs, verbose=False)
|
207
215
|
|
208
|
-
|
209
|
-
|
210
|
-
datetime.timezone.utc) - oldest_modification_time).total_seconds() / 60
|
216
|
+
self.logger.warning(f"Invalid path type: {file_path}")
|
217
|
+
return float("inf")
|
211
218
|
|
212
|
-
|
213
|
-
self.logger.
|
214
|
-
|
215
|
-
|
216
|
-
|
219
|
+
except Exception as e:
|
220
|
+
self.logger.warning(f"Error getting age for {file_path}: {str(e)}")
|
221
|
+
return float("inf")
|
222
|
+
|
223
|
+
def _get_directory_age_minutes(
|
224
|
+
self,
|
225
|
+
dir_path: str,
|
226
|
+
fs: fsspec.AbstractFileSystem,
|
227
|
+
verbose: bool,
|
228
|
+
) -> float:
|
229
|
+
"""Calculate age of oldest file in directory."""
|
230
|
+
try:
|
231
|
+
all_files = fs.ls(dir_path)
|
232
|
+
except Exception as e:
|
233
|
+
self.logger.warning(f"Error listing {dir_path}: {str(e)}")
|
234
|
+
return float("inf")
|
217
235
|
|
218
|
-
|
236
|
+
if not all_files:
|
237
|
+
self.logger.info(f"Empty directory: {dir_path}")
|
238
|
+
return float("inf")
|
219
239
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
240
|
+
modification_times = []
|
241
|
+
for file in all_files:
|
242
|
+
try:
|
243
|
+
info = fs.info(file)
|
244
|
+
mod_time = self._get_modification_time(info, file)
|
245
|
+
modification_times.append(mod_time)
|
246
|
+
except Exception as e:
|
247
|
+
self.logger.warning(f"Skipping {file}: {str(e)}")
|
224
248
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
info
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
)
|
249
|
+
if not modification_times:
|
250
|
+
self.logger.warning(f"No valid files in {dir_path}")
|
251
|
+
return float("inf")
|
252
|
+
|
253
|
+
oldest = min(modification_times)
|
254
|
+
age = (datetime.datetime.now(datetime.timezone.utc) - oldest).total_seconds() / 60
|
255
|
+
self.logger.info(f"Oldest in {dir_path}: {age:.2f} minutes")
|
256
|
+
|
257
|
+
return age
|
258
|
+
|
259
|
+
def _get_file_age_minutes(
|
260
|
+
self,
|
261
|
+
file_path: str,
|
262
|
+
fs: fsspec.AbstractFileSystem,
|
263
|
+
verbose: bool,
|
264
|
+
) -> float:
|
265
|
+
"""Calculate file age in minutes."""
|
266
|
+
try:
|
267
|
+
info = fs.info(file_path)
|
268
|
+
mod_time = self._get_modification_time(info, file_path)
|
269
|
+
age = (datetime.datetime.now(datetime.timezone.utc) - mod_time).total_seconds() / 60
|
244
270
|
|
245
|
-
|
271
|
+
if verbose:
|
272
|
+
self.logger.debug(f"{file_path} info: {info}")
|
273
|
+
self.logger.debug(f"File age: {age:.2f} minutes")
|
246
274
|
|
247
|
-
|
248
|
-
"""
|
249
|
-
Check if a file is valid (exists and has a valid modification time).
|
275
|
+
return age
|
250
276
|
|
251
|
-
:param file_path: Path to the file.
|
252
|
-
:param fs: Filesystem object.
|
253
|
-
:return: True if the file is valid, False otherwise.
|
254
|
-
"""
|
255
|
-
try:
|
256
|
-
fs.info(file_path)
|
257
|
-
return True
|
258
277
|
except Exception as e:
|
259
|
-
self.logger.warning(f"Error
|
260
|
-
return
|
278
|
+
self.logger.warning(f"Error processing {file_path}: {str(e)}")
|
279
|
+
return float("inf")
|
261
280
|
|
262
281
|
def _get_modification_time(self, info: Dict, file_path: str) -> datetime.datetime:
|
263
|
-
"""
|
264
|
-
|
282
|
+
"""Extract modification time from filesystem info with timezone awareness."""
|
283
|
+
try:
|
284
|
+
if "LastModified" in info: # S3-like
|
285
|
+
lm = info["LastModified"]
|
286
|
+
return lm if isinstance(lm, datetime.datetime) else datetime.datetime.fromisoformat(
|
287
|
+
lm[:-1]).astimezone()
|
265
288
|
|
266
|
-
|
267
|
-
|
268
|
-
:return: Modification time as a timezone-aware datetime object.
|
269
|
-
"""
|
270
|
-
if "LastModified" in info: # S3-compatible filesystem
|
271
|
-
last_modified = info["LastModified"]
|
272
|
-
if isinstance(last_modified, datetime.datetime):
|
273
|
-
return last_modified
|
274
|
-
else:
|
275
|
-
return datetime.datetime.strptime(last_modified, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
|
276
|
-
tzinfo=datetime.timezone.utc)
|
289
|
+
if "mtime" in info: # Local filesystem
|
290
|
+
return datetime.datetime.fromtimestamp(info["mtime"], tz=datetime.timezone.utc)
|
277
291
|
|
278
|
-
|
279
|
-
|
292
|
+
if "modified" in info: # FTP/SSH
|
293
|
+
return datetime.datetime.strptime(
|
294
|
+
info["modified"], "%Y-%m-%d %H:%M:%S"
|
295
|
+
).replace(tzinfo=datetime.timezone.utc)
|
280
296
|
|
281
|
-
|
282
|
-
modified_str = info["modified"]
|
283
|
-
try:
|
284
|
-
return datetime.datetime.strptime(modified_str, "%Y-%m-%d %H:%M:%S").replace(
|
285
|
-
tzinfo=datetime.timezone.utc)
|
286
|
-
except ValueError:
|
287
|
-
try:
|
288
|
-
return datetime.datetime.strptime(modified_str, "%b %d %H:%M").replace(
|
289
|
-
year=datetime.datetime.now().year, tzinfo=datetime.timezone.utc
|
290
|
-
)
|
291
|
-
except ValueError:
|
292
|
-
self.logger.warning(f"Unsupported modification time format for {file_path}: {modified_str}")
|
293
|
-
raise ValueError("Unsupported modification time format")
|
297
|
+
raise KeyError("No valid modification time key found")
|
294
298
|
|
295
|
-
|
296
|
-
self.logger.warning(f"
|
297
|
-
raise ValueError("
|
299
|
+
except (KeyError, ValueError) as e:
|
300
|
+
self.logger.warning(f"Invalid mod time for {file_path}: {str(e)}")
|
301
|
+
raise ValueError(f"Unsupported modification time format for {file_path}") from e
|
298
302
|
|
299
303
|
|
300
304
|
class BusinessDays:
|
@@ -315,6 +319,7 @@ class BusinessDays:
|
|
315
319
|
:ivar week_mask: Boolean array indicating working days within a week.
|
316
320
|
:type week_mask: numpy.ndarray
|
317
321
|
"""
|
322
|
+
|
318
323
|
def __init__(self, holiday_list, logger):
|
319
324
|
"""
|
320
325
|
Initialize a BusinessDays object with a given holiday list.
|
sibi_dst/utils/log_utils.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
import sys
|
4
|
+
import time
|
5
|
+
from typing import Optional
|
4
6
|
|
5
7
|
|
6
8
|
class Logger:
|
@@ -23,15 +25,26 @@ class Logger:
|
|
23
25
|
:ivar logger: The initialized logger instance used for logging messages.
|
24
26
|
:type logger: logging.Logger
|
25
27
|
"""
|
26
|
-
|
28
|
+
|
29
|
+
def __init__(self, log_dir: str, logger_name: str, log_file: str, log_level: int = logging.DEBUG):
|
30
|
+
"""
|
31
|
+
Initialize the Logger instance.
|
32
|
+
|
33
|
+
:param log_dir: Directory where logs are stored.
|
34
|
+
:param logger_name: Name of the logger instance.
|
35
|
+
:param log_file: Base name of the log file.
|
36
|
+
:param log_level: Logging level (defaults to DEBUG).
|
37
|
+
"""
|
27
38
|
self.log_dir = log_dir
|
28
39
|
self.logger_name = logger_name
|
29
40
|
self.log_file = log_file
|
41
|
+
self.log_level = log_level
|
30
42
|
self.logger = None
|
31
43
|
|
32
44
|
self._setup()
|
33
45
|
|
34
46
|
def _setup(self):
|
47
|
+
"""Set up the logger with file and console handlers."""
|
35
48
|
# Ensure the log directory exists
|
36
49
|
os.makedirs(self.log_dir, exist_ok=True)
|
37
50
|
|
@@ -47,45 +60,71 @@ class Logger:
|
|
47
60
|
|
48
61
|
# Create a logger
|
49
62
|
self.logger = logging.getLogger(self.logger_name)
|
50
|
-
self.logger.setLevel(
|
63
|
+
self.logger.setLevel(self.log_level)
|
51
64
|
|
52
|
-
# Create a
|
53
|
-
|
65
|
+
# Create a formatter
|
66
|
+
formatter = logging.Formatter(
|
67
|
+
'[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s',
|
68
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
69
|
+
)
|
54
70
|
|
55
|
-
|
56
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
57
|
-
handler.setFormatter(formatter)
|
71
|
+
formatter.converter = time.localtime # << Set local time explicitly
|
58
72
|
|
59
|
-
#
|
60
|
-
|
73
|
+
# Create a file handler
|
74
|
+
file_handler = logging.FileHandler(log_file_path)
|
75
|
+
file_handler.setFormatter(formatter)
|
76
|
+
self.logger.addHandler(file_handler)
|
77
|
+
|
78
|
+
# Create a console handler (optional)
|
79
|
+
console_handler = logging.StreamHandler()
|
80
|
+
console_handler.setFormatter(formatter)
|
81
|
+
self.logger.addHandler(console_handler)
|
61
82
|
|
62
83
|
@classmethod
|
63
|
-
def default_logger(
|
84
|
+
def default_logger(
|
85
|
+
cls,
|
86
|
+
log_dir: str = './logs/',
|
87
|
+
logger_name: Optional[str] = None,
|
88
|
+
log_file: Optional[str] = None,
|
89
|
+
log_level: int = logging.INFO
|
90
|
+
) -> 'Logger':
|
64
91
|
"""
|
65
92
|
Class-level method to create a default logger with generic parameters.
|
66
|
-
|
93
|
+
|
94
|
+
:param log_dir: Directory where logs are stored (defaults to './logs/').
|
67
95
|
:param logger_name: Name of the logger (defaults to __name__).
|
68
96
|
:param log_file: Name of the log file (defaults to logger_name).
|
97
|
+
:param log_level: Logging level (defaults to INFO).
|
69
98
|
:return: Instance of Logger.
|
70
99
|
"""
|
71
100
|
logger_name = logger_name or __name__
|
72
101
|
log_file = log_file or logger_name
|
73
|
-
return cls(log_dir=log_dir, logger_name=logger_name, log_file=log_file)
|
102
|
+
return cls(log_dir=log_dir, logger_name=logger_name, log_file=log_file, log_level=log_level)
|
74
103
|
|
75
|
-
def set_level(self, level):
|
104
|
+
def set_level(self, level: int):
|
105
|
+
"""
|
106
|
+
Set the logging level for the logger.
|
107
|
+
|
108
|
+
:param level: Logging level (e.g., logging.DEBUG, logging.INFO).
|
109
|
+
"""
|
76
110
|
self.logger.setLevel(level)
|
77
111
|
|
78
|
-
def debug(self, msg):
|
112
|
+
def debug(self, msg: str):
|
113
|
+
"""Log a debug message."""
|
79
114
|
self.logger.debug(msg)
|
80
115
|
|
81
|
-
def info(self, msg):
|
116
|
+
def info(self, msg: str):
|
117
|
+
"""Log an info message."""
|
82
118
|
self.logger.info(msg)
|
83
119
|
|
84
|
-
def warning(self, msg):
|
120
|
+
def warning(self, msg: str):
|
121
|
+
"""Log a warning message."""
|
85
122
|
self.logger.warning(msg)
|
86
123
|
|
87
|
-
def error(self, msg):
|
124
|
+
def error(self, msg: str):
|
125
|
+
"""Log an error message."""
|
88
126
|
self.logger.error(msg)
|
89
127
|
|
90
|
-
def critical(self, msg):
|
128
|
+
def critical(self, msg: str):
|
129
|
+
"""Log a critical message."""
|
91
130
|
self.logger.critical(msg)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import re
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Optional, Union, Callable
|
4
|
+
|
5
|
+
class CountryCode(Enum):
|
6
|
+
"""Enum for supported country codes, including phone number length and formatting rules."""
|
7
|
+
|
8
|
+
USA = ("1", 10, lambda number: f"({number[:3]}) {number[3:6]}-{number[6:]}")
|
9
|
+
UK = ("44", 10, lambda number: f"{number[:2]} {number[2:6]} {number[6:]}")
|
10
|
+
FRANCE = ("33", 9, lambda number: f"{number[:1]} {number[1:3]} {number[3:5]} {number[5:]}")
|
11
|
+
SPAIN = ("34", 9, lambda number: f"{number[:2]} {number[2:5]} {number[5:]}")
|
12
|
+
DEFAULT = ("506", 8, lambda number: f"{number[:4]}-{number[4:]}")
|
13
|
+
|
14
|
+
def __init__(self, code: str, length: int, formatter: Callable[[str], str]):
|
15
|
+
"""
|
16
|
+
Initialize a CountryCode enum member.
|
17
|
+
|
18
|
+
:param code: The country code.
|
19
|
+
:type code: str
|
20
|
+
:param length: The expected length of the phone number (excluding the country code).
|
21
|
+
:type length: int
|
22
|
+
:param formatter: A function to format the phone number.
|
23
|
+
:type formatter: Callable[[str], str]
|
24
|
+
"""
|
25
|
+
self.code = code
|
26
|
+
self.length = length
|
27
|
+
self.formatter = formatter
|
28
|
+
|
29
|
+
@property
|
30
|
+
def value(self) -> str:
|
31
|
+
"""
|
32
|
+
Get the country code value.
|
33
|
+
|
34
|
+
:return: The country code.
|
35
|
+
:rtype: str
|
36
|
+
"""
|
37
|
+
return self.code
|
38
|
+
|
39
|
+
def validate_length(self, number: str) -> bool:
|
40
|
+
"""
|
41
|
+
Validate the length of the phone number for this country.
|
42
|
+
|
43
|
+
:param number: The phone number part to validate.
|
44
|
+
:type number: str
|
45
|
+
:return: True if the number length is valid, False otherwise.
|
46
|
+
:rtype: bool
|
47
|
+
"""
|
48
|
+
return len(number) == self.length
|
49
|
+
|
50
|
+
def format_number(self, number: str) -> str:
|
51
|
+
"""
|
52
|
+
Format the phone number according to this country's rules.
|
53
|
+
|
54
|
+
:param number: The phone number part to format.
|
55
|
+
:type number: str
|
56
|
+
:return: The formatted number.
|
57
|
+
:rtype: str
|
58
|
+
"""
|
59
|
+
return self.formatter(number)
|
60
|
+
|
61
|
+
class PhoneNumberFormatter:
|
62
|
+
"""
|
63
|
+
A utility class for validating and formatting phone numbers based on country-specific rules.
|
64
|
+
|
65
|
+
The class supports phone numbers for the UK, USA, France, and Spain. It detects the country code
|
66
|
+
from the input or uses a default country code if missing. Phone numbers are formatted according
|
67
|
+
to country-specific rules.
|
68
|
+
"""
|
69
|
+
|
70
|
+
def __init__(self, default_country_code: CountryCode = CountryCode.DEFAULT):
|
71
|
+
"""
|
72
|
+
Initialize the PhoneNumberFormatter with a default country code.
|
73
|
+
|
74
|
+
:param default_country_code: The default country code to use if missing.
|
75
|
+
:type default_country_code: CountryCode
|
76
|
+
"""
|
77
|
+
self.default_country_code = default_country_code
|
78
|
+
|
79
|
+
def format_phone_number(self, phone_number: Union[str, int, float]) -> Optional[str]:
|
80
|
+
"""
|
81
|
+
Validate and format a phone number according to country-specific rules.
|
82
|
+
|
83
|
+
If the input is numeric (e.g., an integer or float), it will be converted to a string.
|
84
|
+
If the country code is missing, the default country code will be used. The phone number
|
85
|
+
will be formatted according to the detected country's rules.
|
86
|
+
|
87
|
+
:param phone_number: The phone number to validate and format. Can be a string, integer, or float.
|
88
|
+
:type phone_number: Union[str, int, float]
|
89
|
+
:return: The formatted phone number, or None if the input is invalid.
|
90
|
+
:rtype: Optional[str]
|
91
|
+
"""
|
92
|
+
# Convert numeric input to string
|
93
|
+
if isinstance(phone_number, (int, float)):
|
94
|
+
phone_number = str(int(phone_number)) # Convert to integer first to remove decimal points
|
95
|
+
|
96
|
+
# Remove all non-digit characters
|
97
|
+
digits = re.sub(r"\D", "", phone_number)
|
98
|
+
|
99
|
+
# Validate the length of the phone number
|
100
|
+
if not digits or len(digits) < 7: # Minimum length for a valid phone number
|
101
|
+
return None
|
102
|
+
|
103
|
+
# Detect the country code
|
104
|
+
country_code, number = self._detect_country_code(digits)
|
105
|
+
|
106
|
+
# Validate the number length for the detected country
|
107
|
+
if not country_code.validate_length(number):
|
108
|
+
return None
|
109
|
+
|
110
|
+
# Format the phone number based on the country code
|
111
|
+
formatted_number = country_code.format_number(number)
|
112
|
+
|
113
|
+
return f"+{country_code.value} {formatted_number}"
|
114
|
+
|
115
|
+
def _detect_country_code(self, digits: str) -> tuple[CountryCode, str]:
|
116
|
+
"""
|
117
|
+
Detect the country code from the input digits.
|
118
|
+
|
119
|
+
:param digits: The phone number digits (without non-digit characters).
|
120
|
+
:type digits: str
|
121
|
+
:return: A tuple containing the detected country code and the remaining number.
|
122
|
+
:rtype: tuple[CountryCode, str]
|
123
|
+
"""
|
124
|
+
for country_code in CountryCode:
|
125
|
+
if digits.startswith(country_code.value):
|
126
|
+
return country_code, digits[len(country_code.value):]
|
127
|
+
return self.default_country_code, digits
|
@@ -1,5 +1,6 @@
|
|
1
1
|
sibi_dst/__init__.py,sha256=CLHfzrFNqklNx5uMKAPtbZfkbBbVYR5qsiMro0RTfmA,252
|
2
|
-
sibi_dst/df_helper/__init__.py,sha256=
|
2
|
+
sibi_dst/df_helper/__init__.py,sha256=A-f5cCBy949HHxgiPt0T4MG3qdLAnDpGOpRvP-2dXWc,400
|
3
|
+
sibi_dst/df_helper/_artifact_updater_multi_wrapper.py,sha256=wstGiqayex4yOa-WzoOnWHY6vsLoh1XJyfS3LppKD3Q,11502
|
3
4
|
sibi_dst/df_helper/_df_helper.py,sha256=NRiLdHHO45SPwhif5JIQpfj56iC8HcffaRAyT7-TC2w,29585
|
4
5
|
sibi_dst/df_helper/_parquet_artifact.py,sha256=K9FnKjXDmkqCzYqv5weS9scLHsPGyj0UUUoVzOtWv30,8858
|
5
6
|
sibi_dst/df_helper/_parquet_reader.py,sha256=HhzhKtV_7qABHJvmpU2CssjNLgQHUB07eF0CqqzmkOs,3654
|
@@ -37,19 +38,20 @@ sibi_dst/osmnx_helper/basemaps/router_plotter.py,sha256=QznnBGsUwhl8ZITcVNBrQDm-
|
|
37
38
|
sibi_dst/osmnx_helper/utils.py,sha256=BzuY8CtYnBAAO8UAr_M7EOk6CP1zcifNLs8pkdFZEFg,20577
|
38
39
|
sibi_dst/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
40
|
sibi_dst/tests/test_data_wrapper_class.py,sha256=Nkup5OFH5Cos2fxPaU7g9IEyINJM0uJ5-rOZ-eNtd20,3275
|
40
|
-
sibi_dst/utils/__init__.py,sha256=
|
41
|
+
sibi_dst/utils/__init__.py,sha256=2uX4IQN02NeQzR1RRK3d7AV51XC54wg4ieOM7yTSfDs,875
|
41
42
|
sibi_dst/utils/airflow_manager.py,sha256=-d44EKUZNYJyp4wuNwRvilRQktunArPOB5fZuWdQv10,7526
|
42
43
|
sibi_dst/utils/clickhouse_writer.py,sha256=syXGN9NG1FS8soHuMj6QNRqTRWi-thuYUF-_BWDc_KI,9883
|
43
44
|
sibi_dst/utils/credentials.py,sha256=cHJPPsmVyijqbUQIq7WWPe-lIallA-mI5RAy3YUuRME,1724
|
44
|
-
sibi_dst/utils/data_utils.py,sha256=
|
45
|
-
sibi_dst/utils/data_wrapper.py,sha256=
|
46
|
-
sibi_dst/utils/date_utils.py,sha256=
|
45
|
+
sibi_dst/utils/data_utils.py,sha256=w07GStEBK6iz6tENTnhngiu1jpRNdo6dTgqP4iAUsTw,10304
|
46
|
+
sibi_dst/utils/data_wrapper.py,sha256=xA0f4KpHGlAmkR2cZGs2yFuPK4sWttcC9OAU0WUfDo8,29007
|
47
|
+
sibi_dst/utils/date_utils.py,sha256=QSsL_O9ZqIdNjhppyiVOjCFLmhJEKH6F2TeiGBZe9m4,18363
|
47
48
|
sibi_dst/utils/df_utils.py,sha256=OFEtcwVKIilvf9qVf-IfIOHp4jcFAHX5l2IDGudhPZg,10989
|
48
49
|
sibi_dst/utils/file_utils.py,sha256=JpsybYj3XvVJisSBeVU6YSaZnYRm4_6YWTI3TLnnY4Y,1257
|
49
50
|
sibi_dst/utils/filepath_generator.py,sha256=volVm0SSlBrtZp1RpTHxyui5rj5asNcVsWEBRY5FOUQ,6673
|
50
|
-
sibi_dst/utils/log_utils.py,sha256=
|
51
|
+
sibi_dst/utils/log_utils.py,sha256=DtUAcY1aJzlIi9ItWDRz8Yyrz68A908JTm3XEd1HKBo,4484
|
51
52
|
sibi_dst/utils/parquet_saver.py,sha256=_QkXL3IiC2b4m7sxHpCSeqPwBWxXeiP5sH_WheSMEm4,8042
|
53
|
+
sibi_dst/utils/phone_formatter.py,sha256=tsVTDamuthFYgy4-5UwmQkPQ-FGTGH7MjZyH8utAkIY,4945
|
52
54
|
sibi_dst/utils/storage_manager.py,sha256=-zlMrRo_6o6mCd_OHknKqNQl7m0I9VW89grDAUO1V5c,4229
|
53
|
-
sibi_dst-0.3.
|
54
|
-
sibi_dst-0.3.
|
55
|
-
sibi_dst-0.3.
|
55
|
+
sibi_dst-0.3.42.dist-info/METADATA,sha256=3Zk5DLEHp-R0zAIhHHdeNY-Gbq497P6h8Uah46d_NpU,2564
|
56
|
+
sibi_dst-0.3.42.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
57
|
+
sibi_dst-0.3.42.dist-info/RECORD,,
|
File without changes
|