IservETLLib 0.0.1__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.
@@ -0,0 +1,134 @@
1
+ import json
2
+ from sqlalchemy import create_engine, text, URL
3
+ from sqlalchemy.exc import SQLAlchemyError
4
+
5
+ class DBConfig:
6
+ def __init__(self, host, port, user, password, database):
7
+ self.host = host
8
+ self.port = port
9
+ self.user = user
10
+ self.password = password
11
+ self.database = database
12
+
13
+ def __str__(self):
14
+ return f"DatabaseConfig(host={self.host}, port={self.port}, user={self.user}, database={self.database})"
15
+
16
+ def __repr__(self):
17
+ return self.__str__()
18
+
19
+ class DBManager:
20
+ def __init__(self, application_name, config_string, logger):
21
+ self.db_config = self.parse_db_config(config_string)
22
+ self.url_object = URL.create(
23
+ drivername="postgresql",
24
+ username=self.db_config.user,
25
+ password=self.db_config.password,
26
+ host=self.db_config.host,
27
+ database=self.db_config.database,
28
+ )
29
+ self.application_name = application_name
30
+ if logger:
31
+ self.logger = logger
32
+
33
+ def parse_db_config(self, config_string):
34
+ """Парсит строку конфигурации создает DatabaseConfig"""
35
+ config_dict = {}
36
+
37
+ # Разделяем по точкам с запятой
38
+ pairs = config_string.split(';')
39
+
40
+ for pair in pairs:
41
+ if ':' in pair:
42
+ key, value = pair.split(':', 1) # split только по первому двоеточию
43
+ config_dict[key.strip()] = value.strip()
44
+
45
+ # Создаем объект конфигурации
46
+ return DBConfig(
47
+ host=config_dict.get('host', ''),
48
+ port=int(config_dict.get('port', 0)),
49
+ user=config_dict.get('user', ''),
50
+ password=config_dict.get('password', ''),
51
+ database=config_dict.get('database', '')
52
+ )
53
+
54
+ def query_database(self, connection_string, query, queryParams):
55
+ try:
56
+ engine = create_engine(connection_string,
57
+ connect_args={
58
+ "application_name": self.application_name
59
+ }
60
+ )
61
+
62
+ with engine.connect() as conn:
63
+ # Выполнение запроса с параметрами
64
+ result = conn.execute(query, queryParams)
65
+ #print("[query_database]", query, queryParams)
66
+ # Получение всех результатов
67
+ items = result.fetchall()
68
+
69
+ conn.commit()
70
+ return items
71
+
72
+ except SQLAlchemyError as e:
73
+ error_msg = f"Database error: {e}"
74
+ self.logger.log(f'{error_msg} {query} {queryParams}')
75
+ return None
76
+
77
+ finally:
78
+ # Гарантированное отключение от БД
79
+ if 'engine' in locals():
80
+ engine.dispose()
81
+
82
+
83
+ def of_users(sender_id):
84
+ query = text("""
85
+ SELECT * FROM core.of_users(
86
+ (SELECT to_jsonb(t) FROM core.sf_users(:sender_id) as t),
87
+ '{"c_profiles":["user", "brigade"]}'::jsonb
88
+ );
89
+ """
90
+ )
91
+ queryParams = {
92
+ "sender_id": sender_id
93
+ }
94
+ return query, queryParams
95
+
96
+
97
+ def of_levels(sender_id):
98
+ query = text("""
99
+ SELECT * FROM core.of_levels(
100
+ (SELECT to_jsonb(t) FROM core.sf_users(:sender_id) as t),
101
+ null
102
+ );
103
+ """
104
+ )
105
+ queryParams = {
106
+ "sender_id": sender_id
107
+ }
108
+ return query, queryParams
109
+
110
+ def cs_event_types():
111
+ query = text("""
112
+ SELECT id, c_name, c_const, b_disabled, b_default
113
+ FROM dbo.cs_event_types;
114
+ """
115
+ )
116
+ queryParams = {}
117
+ return query, queryParams
118
+
119
+ def of_arm_cd_user_events(sender_id, d_date_start, d_date_end):
120
+ query = text("""
121
+ SELECT * FROM dbo.of_arm_cd_user_events(
122
+ (SELECT to_jsonb(t) FROM core.sf_users(:sender_id) as t),
123
+ :json_params
124
+ );
125
+ """
126
+ )
127
+ queryParams = {
128
+ "sender_id": sender_id,
129
+ "json_params": json.dumps({
130
+ "d_date_start": d_date_start,
131
+ "d_date_end": d_date_end
132
+ })
133
+ }
134
+ return query, queryParams
IservETLLib/Helpers.py ADDED
@@ -0,0 +1,17 @@
1
+ import time
2
+ from datetime import datetime
3
+
4
+ class Helpers:
5
+ def generate_route_number(base_number):
6
+ """
7
+ Генерирует номер маршрута в формате BASE-YYYY-MM-DD-LAST2DIGITS
8
+
9
+ Parameters
10
+ ----------
11
+ base_number : int
12
+ Организационное подразделение пользователя
13
+ """
14
+
15
+ now = datetime.now()
16
+ timestamp_ms = int(time.time() * 1000)
17
+ return f"{base_number}-{now.strftime('%Y-%m-%d')}-{str(timestamp_ms)[-2:]}"
@@ -0,0 +1,80 @@
1
+ import os
2
+ import uuid
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from fluent import sender, event
6
+
7
+ class FluentConf:
8
+ def __init__(self, host, port):
9
+ self.host = host
10
+ self.port = port
11
+
12
+
13
+ class LogLevel(Enum):
14
+ INFO = 'INFO'
15
+ ERROR = 'ERROR'
16
+ CRITICAL = 'CRITICAL'
17
+
18
+ class Logger:
19
+ def __init__(self, application_name, file):
20
+ self.file = file
21
+ self.application_name = application_name
22
+ self.fluent = None
23
+
24
+ def init_fluent(self, level: LogLevel, fluent: FluentConf):
25
+ if not fluent:
26
+ raise Exception('Конфиг пустой')
27
+ return
28
+
29
+ sender.fluent = fluent
30
+ sender.setup(level, host=fluent.host, port=fluent.port)
31
+
32
+
33
+ def log(self, message):
34
+ self.__log(LogLevel.INFO, message)
35
+
36
+ def error(self, message, b_raise=True):
37
+ self.__log((LogLevel.CRITICAL if b_raise else LogLevel.ERROR), message)
38
+
39
+ def __log(self, level: LogLevel, message):
40
+ """
41
+ Parameters
42
+ ----------
43
+ level: LogLevel
44
+ уровень INFO ERROR CRITICAL
45
+ message : str
46
+ входная строка
47
+
48
+ Raises
49
+ -------
50
+ Exception
51
+ Исключение при записи в лог-файл
52
+ """
53
+ datetime_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
54
+ message = f'{level.name} {datetime_string}: {message}'
55
+ print(message)
56
+
57
+ file = self.file
58
+ try:
59
+ # Создаем директорию, если она не существует
60
+ dir = os.path.dirname(file)
61
+ if dir: # Если путь содержит директорию
62
+ os.makedirs(dir, exist_ok=True)
63
+
64
+ with open(file, 'a', encoding='utf-8') as f:
65
+ f.write(message + '\n')
66
+ except Exception as e:
67
+ print(f'Ошибка записи в лог-файл {file}: {e}')
68
+
69
+ if self.fluent:
70
+ event.Event(level.name, {
71
+ "id": f"etl.{self.application_name}.{uuid.uuid4()}",
72
+ "@timestamp": datetime_string,
73
+ "message": message
74
+ })
75
+
76
+ if level == LogLevel.CRITICAL:
77
+ raise Exception(message)
78
+
79
+
80
+
@@ -0,0 +1,2 @@
1
+ from .DatabaseManager import *
2
+ from .LogManager import *
IservETLLib/index.py ADDED
@@ -0,0 +1,26 @@
1
+ import pandas as pd
2
+ from DatabaseManager import DBManager
3
+ from LogManager import Logger, LogLevel, FluentConf
4
+ from Helpers import Helpers
5
+
6
+
7
+ CONNECTION_STRING = "host:192.168.17.46;port:5432;user:user;password:password;database:skr-dev-db"
8
+ ETL = "import_calendar.ipynb"
9
+ OUTPUT_DIR = "./files/output"
10
+
11
+ __CONNECTION_STRING=CONNECTION_STRING if 'CONNECTION_STRING' in locals() else None
12
+ __OUTPUT_DIR=OUTPUT_DIR if 'OUTPUT_DIR' in locals() else ''
13
+ __ETL=ETL if 'ETL' in locals() else 'unknown etl'
14
+ # __USR_ID=USR_ID if 'USR_ID' in locals() else 'null'
15
+ # # __FILEPATHNAME=FILEPATHNAME if 'FILEPATHNAME' in locals() else ''
16
+ # __FILEPATHNAME=os.path.join(OUTPUT_DIR.rsplit('/output', 1)[0], FILEPATHNAME) if 'FILEPATHNAME' in locals() else None #Используются на проде
17
+ __LOG_FILE = __OUTPUT_DIR + '/output.log'
18
+
19
+
20
+ if __name__ == "__main__":
21
+
22
+ logger = Logger(__ETL, __LOG_FILE)
23
+ dbm = DBManager(__ETL, __CONNECTION_STRING, logger)
24
+ # logger.init_fluent(LogLevel.INFO, FluentConf('10.199.236.8', 24224))
25
+ logger.log("asdas")
26
+ print(Helpers.generate_route_number('asdad'))
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: IservETLLib
3
+ Version: 0.0.1
4
+ Summary: Общий код для ETL jupiter notebooks
5
+ Author-email: k-avtonomov <k-avtonomov@it-serv.ru>, a-kostyuhin <a-kostyuhin@it-serv.ru>
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # **Библиотека для ETL** #
15
+
16
+ Для формирования xlsx отчета из нескольких csv файлов.
17
+
18
+ ## Разработка ##
19
+
20
+ source venv/bin/activate
21
+
22
+ ## Установка ##
23
+
24
+ pip install IservETLLib
25
+
26
+ ## Использование ##
27
+
28
+ python3 ./src/IservETLLib/index.py
29
+
30
+ <!-- python3 OUTPUT_NAME="res.xlsx" HEADERS_PATH="" META_NAME="excel.meta" SEP=; "Опросный лист ТУ_2025-07-28_14-28-44.csv" "Опросный лист УСПД_2025-07-28_14-33-04.csv" -->
31
+
32
+ python3 -m build
33
+ python3 -m twine upload --repository pypi dist/*
@@ -0,0 +1,10 @@
1
+ IservETLLib/DatabaseManager.py,sha256=kj8UW8vBPe8t5jpIRShRCPVjoNEKS_VSF_po6CGU5oI,4464
2
+ IservETLLib/Helpers.py,sha256=7cYtn6Gaxx7lcJQ--xnf6QR0JlRcpTGnA0PMGY9Io60,549
3
+ IservETLLib/LogManager.py,sha256=APaumC7EsRsNYwr4_YCm-ndEpv3_ZLtjZHINoC_oJvw,2288
4
+ IservETLLib/__init__.py,sha256=5Jya_pCZ0MgDy39AaCuze13pkpBc3oWVFhCE7pcgcwk,56
5
+ IservETLLib/index.py,sha256=uVNPJyrhTMpG_odz9Z4sJ-S8LhXSqcPqcty1DhArgcU,1095
6
+ iservetllib-0.0.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ iservetllib-0.0.1.dist-info/METADATA,sha256=onaVHd3cSmfncCyejVVW-hB8JOyEo79gOoLijTfjQfI,1018
8
+ iservetllib-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ iservetllib-0.0.1.dist-info/top_level.txt,sha256=KWjGCYc5VEWu2FPx4yNB4SozwwJUuPDSuDrJ5v6JLR8,12
10
+ iservetllib-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
File without changes
@@ -0,0 +1 @@
1
+ IservETLLib