oneCInteraction 1.0.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agcl-x
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: oneCInteraction
3
+ Version: 1.0.0
4
+ Summary: A Python library for interacting with 1C:Enterprise databases via COM connection.
5
+ Author: agcl
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: Microsoft :: Windows
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: pytz
13
+ Requires-Dist: pywin32; platform_system == "Windows"
14
+ Dynamic: license-file
15
+
16
+ # 1C Interaction Library (`onec_interaction`)
17
+
18
+ A modular, clean Python library for interacting with 1C:Enterprise databases using Windows COM connection (`V83.COMConnector`).
19
+
20
+ ## Installation
21
+
22
+ Install the package locally in editable mode:
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ ## Basic Usage
28
+
29
+ ```python
30
+ from onec_interaction import Connection, Customer, Order, OrderItem
31
+
32
+ # 1. Initialize the connection details
33
+ c_conn = Connection(
34
+ s_oneCDatabasePathIn="C:\\Path\\To\\1C\\Database",
35
+ s_usernameIn="admin",
36
+ s_passwordIn="password"
37
+ )
38
+
39
+ # 2. Configure default parameters
40
+ c_conn.s_warehouse_code = "WH001"
41
+ c_conn.s_counteragent_code = "CA001"
42
+ c_conn.s_organisation_code = "ORG001"
43
+
44
+ # 3. Establish COM connection
45
+ c_conn.initiate_connection()
46
+
47
+ # 4. Use composition managers
48
+ # Fetch product details
49
+ c_product = c_conn.nomenclature.get(s_articleIn="ART001")
50
+ print(f"Product: {c_product.s_name}, Price: {c_product.l_variety[0].n_price}")
51
+
52
+ # Close connection when done
53
+ c_conn.close_connection()
54
+ ```
@@ -0,0 +1,39 @@
1
+ # 1C Interaction Library (`onec_interaction`)
2
+
3
+ A modular, clean Python library for interacting with 1C:Enterprise databases using Windows COM connection (`V83.COMConnector`).
4
+
5
+ ## Installation
6
+
7
+ Install the package locally in editable mode:
8
+ ```bash
9
+ pip install -e .
10
+ ```
11
+
12
+ ## Basic Usage
13
+
14
+ ```python
15
+ from onec_interaction import Connection, Customer, Order, OrderItem
16
+
17
+ # 1. Initialize the connection details
18
+ c_conn = Connection(
19
+ s_oneCDatabasePathIn="C:\\Path\\To\\1C\\Database",
20
+ s_usernameIn="admin",
21
+ s_passwordIn="password"
22
+ )
23
+
24
+ # 2. Configure default parameters
25
+ c_conn.s_warehouse_code = "WH001"
26
+ c_conn.s_counteragent_code = "CA001"
27
+ c_conn.s_organisation_code = "ORG001"
28
+
29
+ # 3. Establish COM connection
30
+ c_conn.initiate_connection()
31
+
32
+ # 4. Use composition managers
33
+ # Fetch product details
34
+ c_product = c_conn.nomenclature.get(s_articleIn="ART001")
35
+ print(f"Product: {c_product.s_name}, Price: {c_product.l_variety[0].n_price}")
36
+
37
+ # Close connection when done
38
+ c_conn.close_connection()
39
+ ```
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "oneCInteraction"
7
+ version = "1.0.0"
8
+ description = "A Python library for interacting with 1C:Enterprise databases via COM connection."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ authors = [
12
+ { name = "agcl" }
13
+ ]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: Microsoft :: Windows",
18
+ ]
19
+ dependencies = [
20
+ "pytz",
21
+ "pywin32; platform_system == 'Windows'"
22
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from .connection import Connection
2
+ from .structures import (
3
+ Nomenclature,
4
+ Variety,
5
+ Characteristic,
6
+ Group,
7
+ Customer,
8
+ OrderItem,
9
+ Order
10
+ )
11
+
12
+ __all__ = [
13
+ 'Connection',
14
+ 'Nomenclature',
15
+ 'Variety',
16
+ 'Characteristic',
17
+ 'Group',
18
+ 'Customer',
19
+ 'OrderItem',
20
+ 'Order'
21
+ ]
@@ -0,0 +1,109 @@
1
+ from .log import log_sys
2
+ from . import structures
3
+
4
+ class CharacteristicsManager:
5
+ def __init__(self, c_connectionIn):
6
+ self.c_connection = c_connectionIn
7
+
8
+ @property
9
+ def c_v8(self):
10
+ return self.c_connection.c_v8
11
+
12
+ @staticmethod
13
+ def parse_name(s_charNameIn: str) -> list:
14
+ """Parses a characteristic name string into a list of Characteristic objects."""
15
+ if not s_charNameIn or s_charNameIn in ["NULL", "Без характеристики"]:
16
+ return []
17
+
18
+ if " - " in s_charNameIn:
19
+ l_parts = s_charNameIn.split(" - ")
20
+ s_value = l_parts[1] if len(l_parts) > 1 else l_parts[0]
21
+ return [structures.Characteristic("Характеристика", s_value)]
22
+ elif ":" in s_charNameIn:
23
+ l_parts = s_charNameIn.split(":")
24
+ s_name = l_parts[0].strip()
25
+ s_value = l_parts[1].strip() if len(l_parts) > 1 else ""
26
+ return [structures.Characteristic(s_name, s_value)]
27
+ else:
28
+ return [structures.Characteristic("Характеристика", s_charNameIn)]
29
+
30
+ def get(self, c_charRefIn, s_charNameIn: str = "") -> list:
31
+ """Fetches characteristic properties from registry or parses description."""
32
+ l_characteristics = []
33
+ if c_charRefIn is not None and not c_charRefIn.IsEmpty():
34
+ try:
35
+ c_query = self.c_v8.NewObject("Query")
36
+ c_query.Text = """
37
+ SELECT
38
+ Properties.Свойство.Наименование AS PropName,
39
+ Properties.Значение.Наименование AS ValName
40
+ FROM
41
+ РегистрСведений.ЗначенияСвойствОбъектов AS Properties
42
+ WHERE
43
+ Properties.Объект = &CharRef
44
+ """
45
+ c_query.SetParameter("CharRef", c_charRefIn)
46
+ c_res = c_query.Execute()
47
+ if c_res is not None and not c_res.IsEmpty():
48
+ c_sel = c_res.Select()
49
+ while c_sel.Next():
50
+ l_characteristics.append(structures.Characteristic(c_sel.PropName, c_sel.ValName))
51
+ except Exception as e:
52
+ log_sys(f"Error fetching characteristics for {s_charNameIn}: {e}", 1)
53
+
54
+ # Fallback to string parsing if properties were not found in registry
55
+ if not l_characteristics:
56
+ l_characteristics = self.parse_name(s_charNameIn)
57
+
58
+ return l_characteristics
59
+
60
+ def get_variety(self, c_charRefIn, s_charNameIn: str, n_priceIn: float, n_priceOptIn: float, d_stocksIn: dict):
61
+ """Creates a Variety object with its characteristics and stock counts."""
62
+ l_characteristics = self.get(c_charRefIn, s_charNameIn)
63
+ return structures.Variety(
64
+ n_priceIn=n_priceIn,
65
+ n_priceOptIn=n_priceOptIn,
66
+ d_countIn=d_stocksIn,
67
+ l_characteristicsIn=l_characteristics
68
+ )
69
+
70
+ def fetch_batch(self, l_charRefsIn: list) -> dict:
71
+ """Batch fetches properties and values for a list of characteristics."""
72
+ if not self.c_v8 or not l_charRefsIn:
73
+ return {}
74
+
75
+ log_sys(f"Batch fetching properties for {len(l_charRefsIn)} characteristics...")
76
+
77
+ c_charRefsV8 = self.c_v8.NewObject("ValueList")
78
+ for c_ref in l_charRefsIn:
79
+ if c_ref and not c_ref.IsEmpty():
80
+ c_charRefsV8.Add(c_ref)
81
+
82
+ d_charProps = {} # {char_uuid: [Characteristic]}
83
+
84
+ if c_charRefsV8.Count() > 0:
85
+ try:
86
+ c_propQuery = self.c_v8.NewObject("Query")
87
+ c_propQuery.Text = """
88
+ SELECT
89
+ Properties.Объект AS CharRef,
90
+ Properties.Свойство.Наименование AS PropName,
91
+ Properties.Значение.Наименование AS ValName
92
+ FROM
93
+ РегистрСведений.ЗначенияСвойствОбъектов AS Properties
94
+ WHERE
95
+ Properties.Объект В (&CharRefs)
96
+ """
97
+ c_propQuery.SetParameter("CharRefs", c_charRefsV8)
98
+ c_propRes = c_propQuery.Execute()
99
+ if c_propRes is not None and not c_propRes.IsEmpty():
100
+ c_sel = c_propRes.Select()
101
+ while c_sel.Next():
102
+ s_uuid = self.c_v8.String(c_sel.CharRef.UUID())
103
+ if s_uuid not in d_charProps:
104
+ d_charProps[s_uuid] = []
105
+ d_charProps[s_uuid].append(structures.Characteristic(c_sel.PropName, c_sel.ValName))
106
+ except Exception as e:
107
+ log_sys(f"Error batch fetching characteristics: {e}", 1)
108
+
109
+ return d_charProps
@@ -0,0 +1,98 @@
1
+ import sys
2
+ import pythoncom
3
+ import pytz
4
+ from .log import log_sys
5
+
6
+ # Handle path for win32com on local environments
7
+ PYWIN32_PATH = r'C:\Users\Администратор\AppData\Local\Programs\Python\Python314\Lib\site-packages\pywin32_system32'
8
+ if PYWIN32_PATH not in sys.path:
9
+ sys.path.append(PYWIN32_PATH)
10
+
11
+ import win32com.client
12
+
13
+ from .nomenclature import NomenclatureManager
14
+ from .groups import GroupsManager
15
+ from .orders import OrdersManager
16
+ from .characteristics import CharacteristicsManager
17
+
18
+ class Connection:
19
+ """Manages the COM connection to 1C and hosts managers to interact with different modules."""
20
+
21
+ tz_kiev = pytz.timezone('Europe/Kiev')
22
+
23
+ def __init__(
24
+ self,
25
+ s_oneCDatabasePathIn: str,
26
+ s_usernameIn: str,
27
+ s_passwordIn: str
28
+ ):
29
+ """Initializes connection details, saves required codes, and instantiates managers."""
30
+ self.s_connection_string = f"File='{s_oneCDatabasePathIn}';Usr='{s_usernameIn}';Pwd='{s_passwordIn}';"
31
+ self.c_v8 = None
32
+ self.d_price_types = {}
33
+
34
+ # Save codes to self for order creation
35
+ self.s_warehouse_code = ""
36
+ self.s_counteragent_code = ""
37
+ self.s_organisation_code = ""
38
+ self.sl_price_types = ["Розничная", "Оптовая"]
39
+
40
+ # Managers (Composition)
41
+ self.nomenclature = NomenclatureManager(self)
42
+ self.groups = GroupsManager(self)
43
+ self.orders = OrdersManager(self)
44
+ self.characteristics = CharacteristicsManager(self)
45
+
46
+ def initiate_connection(self) -> None:
47
+ """Establishes COM connection to 1C and caches price type references."""
48
+ log_sys('Trying to initiate connection...')
49
+ try:
50
+ pythoncom.CoInitialize()
51
+ c_connector = win32com.client.Dispatch("V83.COMConnector")
52
+ self.c_v8 = c_connector.Connect(self.s_connection_string)
53
+ log_sys("Successfully connected to 1C.")
54
+ self._cache_price_types()
55
+ except Exception as e:
56
+ log_sys(f"Failed to connect to 1C: {e}")
57
+ self.c_v8 = None
58
+
59
+ def _cache_price_types(self) -> None:
60
+ """Caches references for retail and wholesale price types from 1C catalog."""
61
+ if not self.c_v8:
62
+ return
63
+
64
+ log_sys("Caching price types...")
65
+ for s_ptName in self.sl_price_types:
66
+ self.get_price_type_ref(s_ptName)
67
+
68
+ def get_price_type_ref(self, s_nameIn: str):
69
+ """Returns the cached price type reference, or queries 1C if not yet cached."""
70
+ if s_nameIn in self.d_price_types:
71
+ return self.d_price_types[s_nameIn]
72
+
73
+ if not self.c_v8:
74
+ return None
75
+
76
+ try:
77
+ c_query = self.c_v8.NewObject("Query")
78
+ c_query.Text = "SELECT Ссылка FROM Справочник.ТипыЦенНоменклатуры WHERE Наименование = &Name"
79
+ c_query.SetParameter("Name", s_nameIn)
80
+ c_ptExec = c_query.Execute()
81
+ if c_ptExec is not None and not c_ptExec.IsEmpty():
82
+ c_ptRes = c_ptExec.Select()
83
+ c_ptRes.Next()
84
+ self.d_price_types[s_nameIn] = c_ptRes.Ссылка
85
+ return c_ptRes.Ссылка
86
+ else:
87
+ log_sys(f"Cannot find price type '{s_nameIn}' in 1C constants/catalog", 1)
88
+ return self.c_v8.String("")
89
+ except Exception as e:
90
+ log_sys(f"Error fetching price type '{s_nameIn}': {e}", 1)
91
+ return self.c_v8.String("")
92
+
93
+ def close_connection(self) -> None:
94
+ """Closes the COM connection and uninitializes PythonCOM."""
95
+ log_sys("Closing 1C connection...")
96
+ self.c_v8 = None
97
+ pythoncom.CoUninitialize()
98
+ log_sys("1C connection closed successfully.")
@@ -0,0 +1,153 @@
1
+ from .log import log_sys
2
+ from . import structures
3
+
4
+ class GroupsManager:
5
+ def __init__(self, c_connectionIn):
6
+ self.c_connection = c_connectionIn
7
+
8
+ @property
9
+ def c_v8(self):
10
+ return self.c_connection.c_v8
11
+
12
+ def get_tree(self, sl_ignoredCategoriesNamesIn: list) -> list:
13
+ """Retrieves and builds hierarchical group structure excluding ignored names."""
14
+ if not self.c_v8:
15
+ log_sys("Failed to get hierarchical groups: No connection to 1C.", 1)
16
+ return []
17
+
18
+ try:
19
+ c_ignoredVl = self.c_v8.NewObject("ValueList")
20
+
21
+ if sl_ignoredCategoriesNamesIn:
22
+ log_sys(f"Finding references for ignored categories: {sl_ignoredCategoriesNamesIn}")
23
+ c_refQuery = self.c_v8.NewObject("Query")
24
+
25
+ l_whereClauses = []
26
+ for i in range(len(sl_ignoredCategoriesNamesIn)):
27
+ l_whereClauses.append(f"Наименование = &Name{i}")
28
+
29
+ c_refQuery.Text = f"SELECT Ссылка FROM Справочник.Номенклатура WHERE ({' OR '.join(l_whereClauses)}) AND ЭтоГруппа = ИСТИНА"
30
+
31
+ for i, s_name in enumerate(sl_ignoredCategoriesNamesIn):
32
+ c_refQuery.SetParameter(f"Name{i}", s_name)
33
+
34
+ c_refRes = c_refQuery.Execute()
35
+ if not c_refRes.IsEmpty():
36
+ c_sel = c_refRes.Select()
37
+ while c_sel.Next():
38
+ c_ignoredVl.Add(c_sel.Ссылка)
39
+
40
+ log_sys("Fetching nomenclature groups (excluding ignored branches)...")
41
+ c_query = self.c_v8.NewObject("Query")
42
+ s_queryText = """
43
+ SELECT Ссылка AS Ref,
44
+ Наименование AS Name,
45
+ Код AS Code,
46
+ Родитель AS Parent
47
+ FROM Справочник.Номенклатура
48
+ WHERE ЭтоГруппа = ИСТИНА AND ПометкаУдаления = ЛОЖЬ
49
+ """
50
+
51
+ if c_ignoredVl.Count() > 0:
52
+ s_queryText += " AND NOT Ссылка В ИЕРАРХИИ(&IgnoredRefs)"
53
+ c_query.SetParameter("IgnoredRefs", c_ignoredVl)
54
+
55
+ s_queryText += " ORDER BY Родитель, Наименование"
56
+ c_query.Text = s_queryText
57
+
58
+ c_result = c_query.Execute()
59
+
60
+ d_groupsDict = {}
61
+ l_rootGroups = []
62
+
63
+ if not c_result.IsEmpty():
64
+ c_selection = c_result.Select()
65
+ while c_selection.Next():
66
+ c_groupRef = c_selection.Ref
67
+ s_groupName = c_selection.Name
68
+ s_groupCode = self.c_v8.String(c_selection.Code)
69
+ c_parentRef = c_selection.Parent
70
+
71
+ s_refKey = self.c_v8.String(c_groupRef.UUID())
72
+ c_groupObj = structures.Group(
73
+ s_groupNameIn=s_groupName,
74
+ l_nomenclaturesIn=[],
75
+ c_refIn=c_groupRef,
76
+ s_codeIn=s_groupCode,
77
+ s_uuidIn=s_refKey
78
+ )
79
+
80
+ s_parentKey = self.c_v8.String(c_parentRef.UUID()) if not c_parentRef.IsEmpty() else None
81
+
82
+ d_groupsDict[s_refKey] = {
83
+ 'obj': c_groupObj,
84
+ 'parent': s_parentKey
85
+ }
86
+
87
+ for s_ref, d_data in d_groupsDict.items():
88
+ s_parentKey = d_data['parent']
89
+ if s_parentKey and s_parentKey in d_groupsDict:
90
+ d_groupsDict[s_parentKey]['obj'].l_subGroups.append(d_data['obj'])
91
+ else:
92
+ l_rootGroups.append(d_data['obj'])
93
+
94
+ log_sys(f"Successfully fetched and organized {len(d_groupsDict)} groups.")
95
+ return l_rootGroups
96
+
97
+ except Exception as e:
98
+ log_sys(f"Error in getHierarchicalGroups: {e}", 1)
99
+ return []
100
+
101
+ def get_by_name(self, s_nameIn: str):
102
+ """Finds a single category Group by its name."""
103
+ if not self.c_v8:
104
+ log_sys("Failed to get category: No connection to 1C. Returning None", 1)
105
+ return None
106
+
107
+ try:
108
+ log_sys(f"Trying to get category by name: {s_nameIn}...")
109
+ c_query = self.c_v8.NewObject("Query")
110
+ c_query.Text = """
111
+ SELECT TOP 1 Ссылка AS Ref,
112
+ Наименование AS Name,
113
+ Код AS Code
114
+ FROM Справочник.Номенклатура
115
+ WHERE Наименование = &Name AND ЭтоГруппа = ИСТИНА AND ПометкаУдаления = ЛОЖЬ
116
+ """
117
+ c_query.SetParameter("Name", s_nameIn)
118
+
119
+ c_result = c_query.Execute()
120
+ if c_result.IsEmpty():
121
+ log_sys(f"Category '{s_nameIn}' not found. Returning None", 1)
122
+ return None
123
+
124
+ c_selection = c_result.Select()
125
+ c_selection.Next()
126
+
127
+ s_groupCode = self.c_v8.String(c_selection.Code)
128
+ log_sys(f"Category '{s_nameIn}' successfully found.")
129
+ return structures.Group(s_groupNameIn=c_selection.Name, l_nomenclaturesIn=[], c_refIn=c_selection.Ref, s_codeIn=s_groupCode)
130
+ except Exception as e:
131
+ log_sys(f"Error in getCategoryByName: {e}", 1)
132
+ return None
133
+
134
+ def get_full_path(self, c_groupRefIn) -> str:
135
+ """Returns the full hierarchical path of the group (e.g. 'Parent > Subgroup')."""
136
+ if c_groupRefIn is None or c_groupRefIn.IsEmpty():
137
+ return ""
138
+
139
+ l_pathParts = []
140
+ c_currentRef = c_groupRefIn
141
+
142
+ try:
143
+ while c_currentRef is not None and not c_currentRef.IsEmpty():
144
+ c_groupObj = c_currentRef.GetObject()
145
+ l_pathParts.insert(0, c_groupObj.Наименование)
146
+ c_currentRef = c_groupObj.Родитель
147
+
148
+ s_fullPath = " > ".join(l_pathParts)
149
+ log_sys(f"Full group path built: '{s_fullPath}'")
150
+ return s_fullPath
151
+ except Exception as e:
152
+ log_sys(f"Error in getFullGroupPath: {e}", 1)
153
+ return ""
@@ -0,0 +1,99 @@
1
+ import inspect
2
+ import os
3
+ from datetime import datetime
4
+ import json
5
+ from pathlib import Path
6
+ import shutil
7
+ import sys
8
+
9
+ # Load config from the shared data folder
10
+ COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
11
+ CONFIG_PATH = os.path.abspath(os.path.join(COMMON_DIR, '..', 'data', 'config.json'))
12
+
13
+ try:
14
+ with open(CONFIG_PATH, 'r', encoding='utf-8-sig') as f:
15
+ config = json.load(f)
16
+ except Exception as e:
17
+ config = {}
18
+
19
+ # LOGS_DIR is resolved dynamically inside the shared work/log/ directory
20
+ WORK_DIR = os.path.abspath(os.path.join(COMMON_DIR, '..'))
21
+
22
+ main_module = sys.modules.get('__main__')
23
+ if main_module and hasattr(main_module, '__file__') and main_module.__file__:
24
+ main_dir = os.path.dirname(os.path.abspath(main_module.__file__))
25
+ project_name = os.path.basename(main_dir)
26
+ else:
27
+ project_name = 'unknown'
28
+
29
+ LOGS_DIR = os.path.join(WORK_DIR, 'log', project_name)
30
+
31
+ def log_usr(message, errorflag = 0, user_id = None):
32
+ folder_path = os.path.join(LOGS_DIR, 'user')
33
+ if user_id is None:
34
+ user_id = "no_ID_user"
35
+ log_path = os.path.join(folder_path, f"{user_id}.log")
36
+
37
+ os.makedirs(folder_path, exist_ok=True)
38
+
39
+ log_text = f"[{datetime.now().strftime('%H:%M %d.%m.%Y')}]{'[ERROR]' if errorflag else '\t'} {message}\n"
40
+ log(log_path, log_text)
41
+
42
+ def log_sys(message, errorFlag = 0, user_id = None):
43
+ folder_path = os.path.join(LOGS_DIR, 'system')
44
+
45
+ caller_frame = inspect.stack()[1]
46
+ caller_filename = os.path.basename(caller_frame.filename)
47
+
48
+ log_path = os.path.join(folder_path, f"{caller_filename.split('.')[0]}.log")
49
+
50
+ os.makedirs(folder_path, exist_ok=True)
51
+
52
+ log_text = f"[{datetime.now().strftime('%H:%M %d.%m.%Y')}]{'[ERROR]' if errorFlag else '\t'} {message}\n"
53
+ log(log_path, log_text)
54
+
55
+ def log(file_path, log_text):
56
+ with open(file_path, "a", encoding="utf-8") as f:
57
+ f.write(log_text)
58
+
59
+ def archiveLog():
60
+ format = "zip"
61
+ source_dir = LOGS_DIR
62
+
63
+ if not os.path.exists(source_dir):
64
+ log_sys(f"Source directory '{source_dir}' not found. Archiving aborted.", 1)
65
+ return 0
66
+
67
+ filename = datetime.now().strftime('%d-%m-%Y')
68
+ config_path = config.get("LogDumpsPath")
69
+
70
+ if config_path and os.access(config_path, os.W_OK):
71
+ log_sys("Dump folder path from config is valid")
72
+ base_dump_path = Path(config_path)
73
+ else:
74
+ log_sys("Dump folder path from config is not valid. Dump path set to default")
75
+ base_dump_path = (Path(LOGS_DIR).parent.parent / "logDumps").resolve()
76
+
77
+ base_dump_path.mkdir(parents=True, exist_ok=True)
78
+
79
+ full_archive_path = base_dump_path / filename
80
+
81
+ log_sys(f"Backup path generated: {full_archive_path}.{format}")
82
+
83
+ try:
84
+ shutil.make_archive(str(full_archive_path), format, source_dir)
85
+ log_sys(f"Log archive created successfully: {filename}.{format}")
86
+ return 1
87
+ except Exception as e:
88
+ log_sys(f"Error during archiving logs: {e}", 1)
89
+ return 0
90
+
91
+ def clearLog():
92
+ log_path = LOGS_DIR
93
+ try:
94
+ shutil.rmtree(log_path)
95
+ log_sys(f"Logs cleared")
96
+ return 1
97
+ except Exception as e:
98
+ log_sys(f"Error during clearing logs: {e}", 1)
99
+ return 0