hito_tools 24.8__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.

Potentially problematic release.


This version of hito_tools might be problematic. Click here for more details.

hito_tools/utils.py ADDED
@@ -0,0 +1,231 @@
1
+ import csv
2
+ import os
3
+ import re
4
+ import sys
5
+ from typing import Dict, List, Set
6
+
7
+ import yaml
8
+
9
+ from .core import debug
10
+ from .exceptions import SQLArrayMalformedValue, SQLInconsistentArrayLen, SQLInvalidArray
11
+
12
+ # Address fix CSV columns
13
+ ADDR_FIX_BUILT_ADDR = "Hito-based email"
14
+ ADDR_FIX_FIXED_ADDR = "Fixed email"
15
+
16
+
17
+ def get_config_path_default(input_file_dir=None, main_script=None):
18
+ """
19
+ Compute the default location to use for the configuration file path, using the directory of
20
+ the input file if the configuration file exists in it else the script directory. The file name
21
+ is based on the script name with the .cfg extension. The file path returned is an
22
+ absolute path so that it is handled properly by load_config_file (that adds the current
23
+ script directory if the path is relative).
24
+
25
+ :param input_file_dir: directory where the input file resides
26
+ :param main_script: path of the main script, defaults to sys.modules["__main__"]
27
+ :return: actual default for the configuration file absolute path + default file name
28
+ """
29
+ if main_script is None:
30
+ main_script = sys.modules["__main__"].__file__
31
+ config_file_name = "{}.cfg".format(os.path.splitext(os.path.basename(main_script))[0])
32
+ if input_file_dir is None:
33
+ config_file_path = None
34
+ else:
35
+ if input_file_dir == "":
36
+ input_file_dir = os.getcwd()
37
+ config_file_path = os.path.join(input_file_dir, config_file_name)
38
+ if config_file_path is None or not os.path.exists(config_file_path):
39
+ config_file_path = os.path.join(os.path.dirname(main_script), config_file_name)
40
+ config_file_path = os.path.abspath(config_file_path)
41
+ debug("DEBUG: using configuration file {}".format(config_file_path))
42
+ return config_file_path, config_file_name
43
+
44
+
45
+ def load_config_file(config_file: str, required: bool = False):
46
+ """
47
+ Load the config file, apply defaults and return the corresponding dict.
48
+ If config file is not absolute, prefix with directory where this script resides
49
+
50
+ :param config_file: config file name
51
+ :param required: if True and the file is missing, raise an Exception
52
+ :return: dict containing the options
53
+ """
54
+
55
+ if not os.path.isabs(config_file):
56
+ this_script_dir = os.path.dirname(sys.modules["__main__"].__file__)
57
+ if len(this_script_dir) == 0:
58
+ this_script_dir = os.path.curdir
59
+ config_file = os.path.join(this_script_dir, config_file)
60
+ else:
61
+ config_file = config_file
62
+ try:
63
+ with open(config_file, "r", encoding="utf-8") as f:
64
+ config_options = yaml.safe_load(f)
65
+ except IOError as e:
66
+ if e.errno == 2:
67
+ if required:
68
+ print("ERROR: Configuration file ({}) is missing.".format(config_file))
69
+ raise e
70
+ else:
71
+ print("WARNING: Configuration file ({}) is missing.".format(config_file))
72
+ # Return an empty dict if config file is missing
73
+ config_options = {}
74
+ else:
75
+ raise Exception(
76
+ "Error opening configuration file ({}): {} (errno={})".format(
77
+ config_file, e.strerror, e.errno
78
+ )
79
+ )
80
+ except (yaml.parser.ParserError, yaml.scanner.ScannerError) as e:
81
+ raise Exception(
82
+ "Configuration file ({}) has an invalid format: ({})".format(config_file, e)
83
+ )
84
+ except: # noqa: E722
85
+ raise
86
+
87
+ return config_options
88
+
89
+
90
+ def load_email_fixes(file: str) -> Dict[str, str]:
91
+ """
92
+ Read a CSV file mapping the email defined in Hito to theIJCLab (prenom.nom@ijclab.in2p3.fr)
93
+ adresses for persons who the IJCLab address cannot be guessed from the firstname/lastname
94
+ defined in Hito. Returns a dict where the key is the address built from Hito name and the
95
+ value the actual address to use.
96
+
97
+ :param file: CSV file name
98
+ :return: dict with the mapping to the correct email address
99
+ """
100
+
101
+ address_fixes: Dict[str, str] = {}
102
+
103
+ try:
104
+ with open(file, "r", encoding="utf-8") as f:
105
+ fix_reader = csv.DictReader(f, delimiter=";")
106
+ for e in fix_reader:
107
+ if (
108
+ re.match(r"\w[\w\-\.]+@.+\..+", e[ADDR_FIX_FIXED_ADDR])
109
+ or e[ADDR_FIX_FIXED_ADDR] == "-"
110
+ ):
111
+ address_fixes[e[ADDR_FIX_BUILT_ADDR]] = e[ADDR_FIX_FIXED_ADDR]
112
+ elif len(e[ADDR_FIX_FIXED_ADDR]) == 0:
113
+ print(f"WARNING: fixed address empty for {e[ADDR_FIX_BUILT_ADDR]}")
114
+ else:
115
+ print(f"ERROR: invalid fixed address for {e[ADDR_FIX_BUILT_ADDR]}")
116
+ except: # noqa: E722
117
+ print(f"Error reading address fixes CSV ({file})")
118
+ raise
119
+
120
+ return address_fixes
121
+
122
+
123
+ def str_to_list(string: str) -> List[str]:
124
+ """
125
+ Tokenize a string using / as a separator. Used to transform an Hito office or phone numer
126
+ into a list. Return an empty list is the string is empty.
127
+
128
+ :param string: string to parse
129
+ :return: list of string
130
+ """
131
+ if len(string) == 0:
132
+ return []
133
+
134
+ tokens = string.split("/")
135
+ str_list = [tokens[0].strip()]
136
+ if len(tokens) > 1:
137
+ m = re.match(r"(?P<prefix>(?:\d\d(?:\.| )*){4})\d\d$", str_list[0])
138
+ if not m:
139
+ m = re.match(r"(?P<prefix>\w+\-)\w+$", str_list[0])
140
+ if m:
141
+ prefix = m.group("prefix")
142
+ else:
143
+ prefix = ""
144
+ for tok in tokens[1:]:
145
+ tok = tok.strip()
146
+ if re.match(r"\w+$", tok):
147
+ str_list.append(f"{prefix}{tok}")
148
+ else:
149
+ str_list.append(tok)
150
+ return str_list
151
+
152
+
153
+ def list_to_str(object_list: List[str], separator: str = "|") -> str:
154
+ """
155
+ Take a list of strings and returns a string with each element separated by the given separator.
156
+
157
+ :param object_list: the list of strings to convert
158
+ :param separator: the separator to use in the return string
159
+ :return: input list as a string
160
+ """
161
+ if object_list is None:
162
+ return ""
163
+ else:
164
+ return separator.join(object_list)
165
+
166
+
167
+ def sql_serialize_list(values: Set[str]) -> str:
168
+ """
169
+ Build a SQL longtext value from a list of string
170
+
171
+ :param values: set of strings to serialize
172
+ :return: string in SQL longtext format
173
+ """
174
+ # Add an empty value if none are present
175
+ if len(values) == 0:
176
+ values.add("")
177
+ longtext = f"a:{len(values)}:{{"
178
+ i = 0
179
+ for v in values:
180
+ longtext += f'i:{i};s:{len(v)}:"{v}";'
181
+ i += 1
182
+ longtext += "}"
183
+ return longtext
184
+
185
+
186
+ def sql_longtext_to_list(value: str) -> List[str]:
187
+ """
188
+ Deserialize a SQL longtext value and return it as a list. If the value is NULL or (null),
189
+ return an empty list: this value will be updated only if there is a non-null value in Hito.
190
+
191
+ :param value: SQL longtext string
192
+ :return: list of string
193
+ """
194
+
195
+ if re.match(r"\(*null", value.lower()):
196
+ return []
197
+
198
+ m = re.match(r"a:(?P<list_len>\d+):\{(?P<list>.*)\}", value)
199
+ if not m:
200
+ raise SQLInvalidArray(value)
201
+
202
+ try:
203
+ list_length = int(m.group("list_len"))
204
+ tokens = m.group("list").split(";")
205
+ except Exception as e:
206
+ print(repr(e))
207
+ raise SQLInvalidArray(value)
208
+
209
+ # The string tokens is separated by ';' and there is one ';' at the end.
210
+ # Each token is made of two parts separated also by a ';' : the index and the value
211
+ actual_length = (len(tokens) - 1) / 2.0
212
+ if list_length != actual_length:
213
+ raise SQLInconsistentArrayLen(value, list_length, actual_length)
214
+
215
+ i = 0
216
+ value_list = []
217
+ for i in range(1, 2 * list_length, 2):
218
+ m = re.match(
219
+ (
220
+ r"s:(\d+):(?P<backslashes>\\\\)*(?:(?P<sq>')|(?P<dq>\"))(?P<string>.*)"
221
+ r"(?(backslashes)P=backslashes)(?(sq)(?P<fsq>')|(?(dq)(?P<fdq>\")))$"
222
+ ),
223
+ tokens[i],
224
+ )
225
+ if not m:
226
+ raise SQLArrayMalformedValue(value, tokens[i], i / 2)
227
+ # Do not add empty values
228
+ if len(m.group("string")) > 0:
229
+ value_list.append(m.group("string"))
230
+
231
+ return value_list
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, Michel Jouvin
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.1
2
+ Name: hito_tools
3
+ Version: 24.8
4
+ Summary: Modules for interacting with Hito and NSIP
5
+ Author: Michel Jouvin
6
+ Author-email: michel.jouvin@ijclab.in2p3.fr
7
+ Requires-Python: >=3.8,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: pandas (>=2.2)
15
+ Requires-Dist: requests (>=2.28,<3.0)
16
+ Description-Content-Type: text/markdown
17
+
18
+ # hito_tools module
19
+
20
+ Ce repository contient le module Pyhton `hito_tools` utilisé par les autres outils Python autour de Hito (OSITAH, hito2lists...).
21
+
22
+
23
+ ## Installation
24
+
25
+ ### Environnement Python
26
+
27
+ L'installation de [hito_tools](https://pypi.org/project/hito-tools) nécessite un environnement Python avec une version >= 3.8.
28
+ Il est conseillé d'utiliser un environnement
29
+ virtuel pour chaque groupe d'applications et de déployer le module `hito_tools` dans cet environnemnt. Il est recommandé d'utiliser
30
+ une distribution de Python totalement indépendante du système d'exploitation comme [pyenv](https://github.com/pyenv/pyenv),
31
+ [poetry](https://python-poetry.org) ou [Anaconda](https://www.anaconda.com/products/individual). Pour la création d'un
32
+ environnement virtuel avec Conda, voir la
33
+ [documentation spécifique](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-with-commands).
34
+
35
+ Les modules Python requis par ce module sont :
36
+ * pandas (conda-forge)
37
+ * requests (conda-forge)
38
+
39
+ Avec `conda`, il faut utiliser l'option `-c conda-forge` lors de la commande `conda install`.
40
+
41
+
42
+ ### Installation du module hito_tools
43
+
44
+ L'installation se fait avec la commande `pip` de l'environnement Python utilisé :
45
+
46
+ ```bash
47
+ pip install hito_tools
48
+ ```
49
+
@@ -0,0 +1,12 @@
1
+ hito_tools/ad.py,sha256=sfpIhJ7ZcxtYOW6ZH491xY7vHvhlwNtK4qZeEGRFyAI,292
2
+ hito_tools/agents.py,sha256=VmVG6Lm5CmHIsEaEn9A9hYPHWt5s2D4lIONNswr0uC8,14582
3
+ hito_tools/core.py,sha256=ulwB4pxHSYRw7V4wormmJcGHkXQ1k5j9u93jCUbZWbY,1505
4
+ hito_tools/exceptions.py,sha256=iBqHwI_800nYcXaujafRQes0z5_AkeyCVFw2wKVjPRU,3041
5
+ hito_tools/nsip.py,sha256=yDs0RjwJK94tYOpl4--GeARCWXQ9LNbz6TdOhE206k4,13691
6
+ hito_tools/projects.py,sha256=FuY2ow75mu9O--uG7ahrDoIEoJCmjIK7EOxaL9RXmIE,6316
7
+ hito_tools/teams.py,sha256=Iyc3c483fo-PsN-_gTO5sG_Pw3hYinnvsdZRMz9mZHE,974
8
+ hito_tools/utils.py,sha256=XfDFYOs5W8CpSBpcLAsePnJQILXIuVaoH2j3avj4Mzo,8395
9
+ hito_tools-24.8.dist-info/LICENSE,sha256=2C86YWCx1fvz92WySupcb6_t4NhHCVPE_ucy0YMTuoc,1550
10
+ hito_tools-24.8.dist-info/METADATA,sha256=Ow9mjp0W-jhE2EtMJkWmDx4Ik_urdqqJDbnvVdvuzuw,1913
11
+ hito_tools-24.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
12
+ hito_tools-24.8.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any