lionagi 0.0.206__py3-none-any.whl → 0.0.208__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.
lionagi/utils/sys_util.py CHANGED
@@ -10,79 +10,124 @@ import platform
10
10
  import json
11
11
  import logging
12
12
 
13
+ import pandas as pd
13
14
  from typing import Any, List, Dict, Union
14
15
 
15
16
 
17
+ def as_dict(input_: Any) -> Dict[Any, Any]:
18
+ """
19
+ Convert a JSON string or a dictionary into a dictionary.
16
20
 
17
- def get_cpu_architecture():
18
- arch = platform.machine()
19
- if 'arm' in arch or 'aarch64' in arch:
20
- return 'apple_silicon'
21
- else:
22
- return 'other_cpu'
21
+ Args:
22
+ input_ (Any): The input to be converted to a dictionary. This can be a JSON string or a dictionary.
23
23
 
24
+ Returns:
25
+ Dict[Any, Any]: The input converted into a dictionary.
24
26
 
25
- def get_timestamp() -> str:
27
+ Raises:
28
+ ValueError: If the input is a string but cannot be parsed as JSON.
29
+ TypeError: If the input is neither a string nor a dictionary.
30
+ """
31
+ if isinstance(input_, str):
32
+ try:
33
+ return json.loads(input_)
34
+ except Exception as e:
35
+ raise ValueError(f"Could not convert input to dict: {e}") from e
36
+ elif isinstance(input_, dict):
37
+ return input_
38
+ else:
39
+ raise TypeError(f"Could not convert input to dict: {type(input_).__name__} given.")
40
+
41
+ def clear_dir(dir_path: str) -> None:
26
42
  """
27
- Generates a current timestamp in ISO format with colons and periods replaced by
28
- underscores.
43
+ Clear all files within the specified directory.
29
44
 
30
- Returns:
31
- str: The current timestamp.
45
+ Args:
46
+ dir_path (str): The path to the directory to be cleared.
32
47
 
33
- Examples:
34
- >>> isinstance(get_timestamp(), str)
35
- True
48
+ Raises:
49
+ FileNotFoundError: If the specified directory does not exist.
36
50
  """
37
- return datetime.now().isoformat().replace(":", "_").replace(".", "_")
51
+ if not os.path.exists(dir_path):
52
+ raise FileNotFoundError("The specified directory does not exist.")
53
+
54
+ for filename in os.listdir(dir_path):
55
+ file_path = os.path.join(dir_path, filename)
56
+ try:
57
+ if os.path.isfile(file_path) or os.path.islink(file_path):
58
+ os.unlink(file_path)
59
+ logging.info(f'Successfully deleted {file_path}')
60
+ except Exception as e:
61
+ logging.error(f'Failed to delete {file_path}. Reason: {e}')
62
+ raise
38
63
 
64
+ def change_dict_key(dict_: Dict[Any, Any], old_key: str, new_key: str) -> None:
65
+ """
66
+ Change a key in a dictionary to a new key.
67
+
68
+ Args:
69
+ dict_ (Dict[Any, Any]): The dictionary whose key needs to be changed.
70
+ old_key (str): The old key that needs to be replaced.
71
+ new_key (str): The new key to replace the old key.
72
+
73
+ Returns:
74
+ None: The function modifies the dictionary in place and returns None.
75
+ """
76
+ if old_key in dict_:
77
+ dict_[new_key] = dict_.pop(old_key)
78
+
39
79
  def create_copy(input: Any, n: int = 1) -> Any:
40
80
  """
41
- Creates a deep copy of the given input. If 'n' is greater than 1, returns a
42
- list of deep copies.
81
+ Create a deep copy of the input. If n > 1, returns a list of deep copies of the input.
43
82
 
44
83
  Args:
45
- input (Any): The object to be copied.
46
- n (int, optional): The number of copies to create. Defaults to 1.
84
+ input (Any): The input to be copied.
85
+ n (int, optional): The number of copies to make. Defaults to 1.
47
86
 
48
87
  Returns:
49
- Any: A deep copy of the input, or a list of deep copies.
88
+ Any: A deep copy of the input, or a list of deep copies if n > 1.
50
89
 
51
90
  Raises:
52
91
  ValueError: If 'n' is not a positive integer.
53
-
54
- Examples:
55
- >>> create_copy([1, 2, 3], 2)
56
- [[1, 2, 3], [1, 2, 3]]
57
- >>> create_copy("Hello")
58
- 'Hello'
59
92
  """
60
93
  if not isinstance(n, int) or n < 1:
61
94
  raise ValueError(f"'n' must be a positive integer: {n}")
62
95
  return copy.deepcopy(input) if n == 1 else [copy.deepcopy(input) for _ in range(n)]
63
96
 
97
+ def create_id(n: int = 32) -> str:
98
+ """
99
+ Generate a unique ID using the current time and random bytes, hashed with SHA-256.
100
+
101
+ Args:
102
+ n (int, optional): The length of the ID to be returned. Defaults to 32.
103
+
104
+ Returns:
105
+ str: A unique ID of the specified length.
106
+ """
107
+ current_time = datetime.now().isoformat().encode('utf-8')
108
+ random_bytes = os.urandom(32)
109
+ return hashlib.sha256(current_time + random_bytes).hexdigest()[:n]
110
+
64
111
  def create_path(
65
112
  dir: str, filename: str, timestamp: bool = True, dir_exist_ok: bool = True,
66
113
  time_prefix: bool = False
67
114
  ) -> str:
68
115
  """
69
- Creates a file path with an optional timestamp, ensuring the directory exists.
116
+ Create a file path with optional timestamp inclusion and directory creation.
70
117
 
71
118
  Args:
72
- dir (str): The directory in which the file will be placed.
119
+ dir_path (str): The directory path where the file will be located.
73
120
  filename (str): The name of the file.
74
121
  timestamp (bool, optional): Flag to include a timestamp in the filename. Defaults to True.
75
- dir_exist_ok (bool, optional): Flag to create the directory if it doesn't exist. Defaults to True.
76
- time_prefix (bool, optional): Flag to place the timestamp as a prefix. Defaults to False.
122
+ dir_exist_ok (bool, optional): Flag to allow existing directories. Defaults to True.
123
+ time_prefix (bool, optional): Flag to place the timestamp as a prefix or suffix. Defaults to False.
77
124
 
78
125
  Returns:
79
- str: The full path of the file with the optional timestamp.
126
+ str: The constructed file path including the directory, filename, and optional timestamp.
80
127
 
81
- Examples:
82
- >>> create_path("/tmp", "log.txt", timestamp=False)
83
- '/tmp/log.txt'
84
- >>> isinstance(create_path("/tmp", "report", time_prefix=True), str)
85
- True
128
+ Notes:
129
+ If the directory does not exist, it will be created unless dir_exist_ok is False.
130
+ The timestamp format is YYYYMMDD_HHMMSS.
86
131
  """
87
132
  dir = dir + '/' if not dir.endswith('/') else dir
88
133
  if '.' in filename:
@@ -94,82 +139,19 @@ def create_path(
94
139
  filename = f"{timestamp_str}_{name}" if time_prefix else f"{name}_{timestamp_str}"
95
140
  return f"{dir}{filename}.{ext}" if ext else f"{dir}{filename}"
96
141
 
97
- def split_path(path: str) -> tuple:
98
- """
99
- Splits a file path into its directory name and file name.
100
-
101
- Args:
102
- path (str): The file path to split.
103
-
104
- Returns:
105
- tuple: A tuple containing the directory name and file name.
106
-
107
- Examples:
108
- >>> split_path("/home/user/document.txt")
109
- ('/home/user', 'document.txt')
110
- >>> split_path("document.txt")
111
- ('', 'document.txt')
112
- """
113
- folder_name = os.path.dirname(path)
114
- file_name = os.path.basename(path)
115
- return (folder_name, file_name)
116
-
117
- def str_to_num(
118
- input: str, upper_bound: float = None, lower_bound: float = None,
119
- num_type: type = int, precision: int = None
120
- ) -> Any:
121
- """
122
- Converts the first number in a string to a specified numeric type and checks if it
123
- falls within specified bounds.
124
-
125
- Args:
126
- input (str): The string containing the number.
127
- upper_bound (float, optional): The upper limit for the number. Defaults to None.
128
- lower_bound (float, optional): The lower limit for the number. Defaults to None.
129
- num_type (type): The numeric type to which the number will be converted. Default is int.
130
- precision (int, optional): The precision for floating point numbers. Defaults to None.
131
-
132
- Returns:
133
- Any: The converted number in the specified type.
134
-
135
- Raises:
136
- ValueError: If no numeric value is found, or the number is outside the specified bounds.
137
-
138
- Examples:
139
- >>> str_to_num("Value is 20.5", upper_bound=30, num_type=float)
140
- 20.5
141
- >>> str_to_num("Temperature -5 degrees", lower_bound=0)
142
- ValueError: Number -5 is less than the lower bound of 0.
143
- """
144
- number_str = _extract_first_number(input)
145
- if number_str is None:
146
- raise ValueError(f"No numeric values found in the string: {input}")
147
-
148
- number = _convert_to_num(number_str, num_type, precision)
149
-
150
- if upper_bound is not None and number > upper_bound:
151
- raise ValueError(f"Number {number} is greater than the upper bound of {upper_bound}.")
152
- if lower_bound is not None and number < lower_bound:
153
- raise ValueError(f"Number {number} is less than the lower bound of {lower_bound}.")
154
-
155
- return number
156
-
157
142
  def get_bins(input: List[str], upper: int) -> List[List[int]]:
158
143
  """
159
- Distributes a list of strings into bins where the total length of strings in each
160
- bin is less than a specified upper limit.
144
+ Organize indices of the input list into bins where the cumulative length of strings does not exceed the upper limit.
161
145
 
162
146
  Args:
163
- input (List[str]): The list of strings to be distributed.
164
- upper (int): The upper limit for the total length of strings in each bin.
147
+ input (List[str]): The input list of strings.
148
+ upper (int): The upper limit for the cumulative length of strings in each bin.
165
149
 
166
150
  Returns:
167
- List[List[int]]: A list of lists, where each inner list contains indices of the
168
- input list that make up a bin.
151
+ List[List[int]]: A list of bins, each bin is a list of indices from the input list.
169
152
 
170
- Examples:
171
- >>> get_bins(["one", "two", "three", "four", "five"], 10)
172
- [[0, 1], [2], [3, 4]]
153
+ Notes:
154
+ This function can be used to partition data into chunks where each chunk has a maximum cumulative length.
173
155
  """
174
156
  current = 0
175
157
  bins = []
@@ -190,227 +172,277 @@ def get_bins(input: List[str], upper: int) -> List[List[int]]:
190
172
 
191
173
  return bins
192
174
 
193
- def create_id(n: int = 32) -> str:
175
+ def get_cpu_architecture() -> str:
194
176
  """
195
- Creates a unique identifier using a combination of the current time and random bytes.
177
+ Determine the CPU architecture of the system.
196
178
 
197
- Args:
198
- n (int, optional): The length of the identifier. Defaults to 32.
179
+ Returns:
180
+ str: A string indicating the CPU architecture. Returns 'apple_silicon' for ARM architectures,
181
+ and 'other_cpu' for all other CPU types.
182
+ """
183
+ arch = platform.machine()
184
+ if 'arm' in arch or 'aarch64' in arch:
185
+ return 'apple_silicon'
186
+ else:
187
+ return 'other_cpu'
188
+
189
+ def get_timestamp() -> str:
190
+ """
191
+ Generate a timestamp string in ISO format with colons and periods replaced by underscores.
199
192
 
200
193
  Returns:
201
- str: The generated unique identifier.
194
+ str: The current timestamp in 'YYYY-MM-DDTHH_MM_SS_SSSSSS' format.
195
+ """
196
+ return datetime.now().isoformat().replace(":", "_").replace(".", "_")
202
197
 
203
- Examples:
204
- >>> len(create_id())
205
- 32
206
- >>> len(create_id(16))
207
- 16
198
+ def split_path(path: str) -> tuple:
208
199
  """
209
- current_time = datetime.now().isoformat().encode('utf-8')
210
- random_bytes = os.urandom(32)
211
- return hashlib.sha256(current_time + random_bytes).hexdigest()[:n]
200
+ Split a file path into folder name and file name.
201
+
202
+ Args:
203
+ path (str): The full path to split.
212
204
 
213
- def directory_cleaner(directory: str) -> None:
205
+ Returns:
206
+ tuple: A tuple containing the folder name and file name.
214
207
  """
215
- Deletes all files within a given directory.
208
+ folder_name = os.path.dirname(path)
209
+ file_name = os.path.basename(path)
210
+ return (folder_name, file_name)
211
+
212
+ def str_to_num(
213
+ input: str, upper_bound: float = None, lower_bound: float = None,
214
+ num_type: type = int, precision: int = None
215
+ ) -> Any:
216
+ """
217
+ Convert a string to a number, enforcing optional upper and lower bounds.
216
218
 
217
219
  Args:
218
- directory (str): The path of the directory to clean.
220
+ input (str): The input string containing the number to convert.
221
+ upper_bound (float, optional): The maximum allowable value of the number.
222
+ lower_bound (float, optional): The minimum allowable value of the number.
223
+ num_type (type): The type of number to convert to (e.g., int, float).
224
+ precision (int, optional): The precision for rounding if converting to a float.
219
225
 
220
- Raises:
221
- FileNotFoundError: If the specified directory does not exist.
222
- Exception: If a file in the directory cannot be deleted.
226
+ Returns:
227
+ Any: The number extracted and converted from the string, of type specified by num_type.
223
228
 
224
- Examples:
225
- >>> directory_cleaner("/path/to/nonexistent/directory")
226
- FileNotFoundError: The specified directory does not exist.
229
+ Raises:
230
+ ValueError: If no numeric values are found, or if the converted number violates the specified bounds.
227
231
  """
228
- if not os.path.exists(directory):
229
- raise FileNotFoundError("The specified directory does not exist.")
232
+ number_str = _extract_first_number(input)
233
+ if number_str is None:
234
+ raise ValueError(f"No numeric values found in the string: {input}")
230
235
 
231
- for filename in os.listdir(directory):
232
- file_path = os.path.join(directory, filename)
233
- try:
234
- if os.path.isfile(file_path) or os.path.islink(file_path):
235
- os.unlink(file_path)
236
- logging.info(f'Successfully deleted {file_path}')
237
- except Exception as e:
238
- logging.error(f'Failed to delete {file_path}. Reason: {e}')
239
- raise
236
+ number = _convert_to_num(number_str, num_type, precision)
237
+
238
+ if upper_bound is not None and number > upper_bound:
239
+ raise ValueError(f"Number {number} is greater than the upper bound of {upper_bound}.")
240
+ if lower_bound is not None and number < lower_bound:
241
+ raise ValueError(f"Number {number} is less than the lower bound of {lower_bound}.")
242
+
243
+ return number
240
244
 
241
245
  def strip_lower(input: Any) -> str:
242
246
  """
243
- Strips and converts a given input to lower case.
247
+ Convert an input to string, strip leading and trailing spaces, and convert to lowercase.
244
248
 
245
249
  Args:
246
250
  input (Any): The input to be processed.
247
251
 
248
252
  Returns:
249
- str: The processed string in lower case.
250
-
253
+ str: The processed string if conversion is successful, otherwise returns 'False' as a string.
254
+
251
255
  Raises:
252
- Exception: If the input cannot be converted to a string.
253
-
254
- Examples:
255
- >>> strip_lower(" Hello WORLD ")
256
- 'hello world'
257
- >>> strip_lower(123)
258
- '123'
256
+ ValueError: If the input cannot be converted to a string.
259
257
  """
260
258
  try:
261
259
  return str(input).strip().lower()
262
260
  except:
263
- return False
261
+ raise ValueError(f"Could not convert input to string: {input}")
264
262
 
265
- def _extract_first_number(inputstr: str) -> str:
263
+ def install_import(package_name, module_name=None, import_name=None, pip_name=None):
266
264
  """
267
- Extracts the first number from a given string.
265
+ Dynamically import a module or package, installing it via pip if necessary.
268
266
 
269
267
  Args:
270
- inputstr (str): The string from which the number will be extracted.
268
+ package_name (str): The base name of the package.
269
+ module_name (str, optional): The name of the module within the package, if applicable.
270
+ import_name (str, optional): The specific name to import from the module or package.
271
+ pip_name (str, optional): The name of the package in pip, if different from package_name.
271
272
 
272
273
  Returns:
273
- str: The first number found in the string, returned as a string.
274
- Returns None if no number is found.
274
+ None: This function does not return a value but prints the outcome of the operation.
275
275
 
276
- Examples:
277
- >>> extract_first_number("The 2 little pigs")
278
- '2'
279
- >>> extract_first_number("No numbers")
280
- None
276
+ Note:
277
+ This function attempts to import the specified package or module and installs it using pip if the import fails.
281
278
  """
282
- numbers = re.findall(r'-?\d+\.?\d*', inputstr)
283
- return numbers[0] if numbers else None
284
-
285
- def _convert_to_num(number_str: str, num_type: type = int, precision: int = None) -> Any:
279
+ if pip_name is None:
280
+ pip_name = package_name # Defaults to package_name if pip_name is not explicitly provided
281
+
282
+ full_import_path = package_name if module_name is None else f"{package_name}.{module_name}"
283
+ try:
284
+ if import_name:
285
+ # For importing a specific name from a module or sub-module
286
+ module = __import__(full_import_path, fromlist=[import_name])
287
+ getattr(module, import_name)
288
+ else:
289
+ # For importing the module or package itself
290
+ __import__(full_import_path)
291
+ print(f"Successfully imported {import_name or full_import_path}.")
292
+ except ImportError:
293
+ print(f"Module {full_import_path} or attribute {import_name} not found. Installing {pip_name}...")
294
+ subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name])
295
+ # Retry the import after installation
296
+ if import_name:
297
+ module = __import__(full_import_path, fromlist=[import_name])
298
+ getattr(module, import_name)
299
+ else:
300
+ __import__(full_import_path)
301
+
302
+ def is_schema(dict_: Dict, schema: Dict) -> bool:
286
303
  """
287
- Converts a string representation of a number to a specified numeric type.
304
+ Validate if a dictionary matches a given schema.
288
305
 
289
306
  Args:
290
- number_str (str): The string representation of the number.
291
- num_type (type): The type to which the number will be converted. Default is int.
292
- precision (int, optional): The precision for floating point numbers. Defaults to None.
307
+ dict_ (Dict): The dictionary to validate.
308
+ schema (Dict): A schema with keys and their expected types.
293
309
 
294
310
  Returns:
295
- Any: The converted number in the specified type.
296
-
297
- Raises:
298
- ValueError: If an invalid number type is provided.
311
+ bool: True if the dictionary matches the schema, False otherwise.
299
312
 
300
- Examples:
301
- >>> convert_to_num("3.142", float, 2)
302
- 3.14
303
- >>> convert_to_num("100", int)
304
- 100
313
+ Note:
314
+ The function checks if each key in the schema exists in the dictionary and matches the expected type.
305
315
  """
306
- if num_type is int:
307
- return int(float(number_str))
308
- elif num_type is float:
309
- return round(float(number_str), precision) if precision is not None else float(number_str)
310
- else:
311
- raise ValueError(f"Invalid number type: {num_type}")
316
+ for key, expected_type in schema.items():
317
+ if not isinstance(dict_[key], expected_type):
318
+ return False
319
+ return True
312
320
 
313
- def change_dict_key(dict_: Dict[Any, Any], old_key: str, new_key: str) -> None:
321
+ def is_package_installed(package_name: str) -> bool:
314
322
  """
315
- Changes a key in a dictionary to a new key.
323
+ Check if a package is installed in the current environment.
316
324
 
317
325
  Args:
318
- dict_ (Dict[Any, Any]): The dictionary to change the key in.
319
- old_key (str): The old key to be changed.
320
- new_key (str): The new key to change to.
326
+ package_name (str): The name of the package to check.
327
+
328
+ Returns:
329
+ bool: True if the package is installed, False otherwise.
321
330
  """
322
- if old_key in dict_:
323
- dict_[new_key] = dict_.pop(old_key)
331
+ package_spec = importlib.util.find_spec(package_name)
332
+ return package_spec is not None
324
333
 
325
- def as_dict(input_: Any) -> Dict[Any, Any]:
334
+ def is_same_dtype(input_: Any, dtype: type = None) -> bool:
326
335
  """
327
- Converts a JSON string or a dictionary to a dictionary.
336
+ Check if all elements in a collection have the same data type as specified or as the first element's type.
328
337
 
329
338
  Args:
330
- input_: A JSON string or dictionary to convert.
339
+ input_ (Any): The input collection, can be a list or a dictionary.
340
+ dtype (type, optional): The data type to check against. If not provided, the type of the first element is used.
331
341
 
332
342
  Returns:
333
- The input converted to a dictionary.
343
+ bool: True if all elements in the collection match the specified data type, False otherwise.
334
344
 
335
- Raises:
336
- ValueError: If the input cannot be converted to a dictionary.
345
+ Note:
346
+ For dictionaries, it checks the data type of the values.
337
347
  """
338
- if isinstance(input_, str):
339
- try:
340
- return json.loads(input_)
341
- except Exception as e:
342
- raise f"Could not convert input to dict: {e}"
348
+
349
+ if isinstance(input_, list):
350
+ dtype = dtype or type(input_[0])
351
+ return all(isinstance(i, dtype) for i in input_)
352
+
343
353
  elif isinstance(input_, dict):
344
- return input_
354
+ dtype = dtype or type(list(input_.values())[0])
355
+ return (isinstance(v, dtype) for _, v in input_.items())
356
+
345
357
  else:
346
- raise f"Could not convert input to dict: {input_}"
358
+ dtype = dtype or type(input_)
359
+ return isinstance(input_, dtype)
347
360
 
348
- def is_schema(dict_: Dict, schema: Dict):
361
+ def to_df(
362
+ item: Any, how: str = 'all', drop_kwargs: Dict = {}, reset_index: bool = True,
363
+ **kwargs
364
+ ) -> pd.DataFrame:
349
365
  """
350
- Checks if a dictionary matches a given schema.
366
+ Convert various item types (list, pandas DataFrame) into a pandas DataFrame with options for cleaning and resetting the index.
351
367
 
352
368
  Args:
353
- dict_: The dictionary to check.
354
- schema: The schema to validate against.
369
+ item (Any): The item to be converted into a DataFrame. Can be a list or a DataFrame.
370
+ how (str, optional): How to drop rows with missing values. Defaults to 'all'.
371
+ drop_kwargs (Dict, optional): Additional keyword arguments for pd.DataFrame.dropna(). Defaults to {}.
372
+ reset_index (bool, optional): Whether to reset the index of the final DataFrame. Defaults to True.
373
+ **kwargs: Additional keyword arguments for pd.DataFrame.reset_index() if reset_index is True.
355
374
 
356
375
  Returns:
357
- True if the dictionary matches the schema, False otherwise.
376
+ pd.DataFrame: The resulting DataFrame after conversions and cleaning.
377
+
378
+ Raises:
379
+ ValueError: If there's an error during conversion or processing of the DataFrame.
358
380
  """
359
- for key, expected_type in schema.items():
360
- if not isinstance(dict_[key], expected_type):
361
- return False
362
- return True
381
+ try:
382
+ dfs = ''
383
+
384
+ if isinstance(item, List):
385
+ if is_same_dtype(item, pd.DataFrame):
386
+ dfs = pd.concat(item)
387
+ dfs = pd.DataFrame(item)
363
388
 
364
- def timestamp_to_datetime(timestamp):
365
- return datetime.fromtimestamp(timestamp)
389
+ elif isinstance(item, pd.DataFrame):
390
+ dfs = item
366
391
 
367
- def install_import(package_name, module_name=None, import_name=None, pip_name=None):
392
+ drop_kwargs['how'] = how
393
+ dfs = dfs.dropna(**drop_kwargs)
394
+
395
+ if reset_index:
396
+ drop = kwargs.pop('drop', True)
397
+ inplace = kwargs.pop('inplace', True)
398
+ dfs.reset_index(drop=drop, inplace=inplace, **kwargs)
399
+
400
+ return dfs
368
401
 
369
- """
370
- For a simple package import where package name matches pip name
371
- import xx
372
- install_and_import('xx')
402
+ except Exception as e:
403
+ raise ValueError(f'Error converting items to DataFrame: {e}')
373
404
 
405
+ def timestamp_to_datetime(timestamp: float) -> datetime:
406
+ """
407
+ Convert a timestamp into a datetime object.
374
408
 
375
- For importing a module from a package
376
- import xx.yy
377
- install_and_import('xx', 'yy')
378
-
379
- For importing a specific attribute from a module/sub-module
380
- from xx.yy import zz
381
- install_and_import('xx', 'yy', 'zz')
409
+ Args:
410
+ timestamp (float): The timestamp to convert.
382
411
 
383
- For cases where pip name differs from package name
384
- install_and_import('xx', pip_name='different_pip_name')
412
+ Returns:
413
+ datetime: The datetime object corresponding to the given timestamp.
414
+ """
415
+ return datetime.fromtimestamp(timestamp)
385
416
 
386
- For full example with different pip name
387
- install_and_import('xx', 'yy', 'zz', 'different_pip_name')
417
+ def _extract_first_number(inputstr: str) -> str:
388
418
  """
419
+ Extract the first number from a string.
389
420
 
390
- if pip_name is None:
391
- pip_name = package_name # Defaults to package_name if pip_name is not explicitly provided
392
-
393
- full_import_path = package_name if module_name is None else f"{package_name}.{module_name}"
421
+ Args:
422
+ input (str): The input string to search for numbers.
423
+
424
+ Returns:
425
+ str: The first number found in the string, or None if no numbers are found.
426
+ """
427
+ numbers = re.findall(r'-?\d+\.?\d*', inputstr)
428
+ return numbers[0] if numbers else None
429
+
430
+ def _convert_to_num(number_str: str, num_type: type = int, precision: int = None) -> Any:
431
+ """
432
+ Convert a string to a specified numeric type, optionally rounding to a given precision.
394
433
 
395
- try:
396
- if import_name:
397
- # For importing a specific name from a module or sub-module
398
- module = __import__(full_import_path, fromlist=[import_name])
399
- getattr(module, import_name)
400
- else:
401
- # For importing the module or package itself
402
- __import__(full_import_path)
403
- print(f"Successfully imported {import_name or full_import_path}.")
404
- except ImportError:
405
- print(f"Module {full_import_path} or attribute {import_name} not found. Installing {pip_name}...")
406
- subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name])
407
- # Retry the import after installation
408
- if import_name:
409
- module = __import__(full_import_path, fromlist=[import_name])
410
- getattr(module, import_name)
411
- else:
412
- __import__(full_import_path)
413
-
414
- def is_package_installed(package_name):
415
- package_spec = importlib.util.find_spec(package_name)
416
- return package_spec is not None
434
+ Args:
435
+ number_str (str): The number in string format to convert.
436
+ num_type (type): The type of number to convert to, e.g., int or float.
437
+ precision (int, optional): The number of decimal places to round to, for floating-point conversions.
438
+
439
+ Returns:
440
+ Any: The converted number, either as an int or float, depending on num_type.
441
+ """
442
+ if num_type is int:
443
+ return int(float(number_str))
444
+ elif num_type is float:
445
+ return round(float(number_str), precision) if precision is not None else float(number_str)
446
+ else:
447
+ raise ValueError(f"Invalid number type: {num_type}")
448
+