tgshops-integrations 1.0__py3-none-any.whl → 1.2__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.
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from aiocache import cached
6
6
  from tgshops_integrations.models.products import ProductModel
7
7
  from tgshops_integrations.nocodb_connector.client import NocodbClient
8
+
8
9
  from tgshops_integrations.nocodb_connector.model_mapping import dump_product_data,dump_product_data_with_check, get_pagination_info, ID_FIELD, \
9
10
  parse_product_data, PRODUCT_CATEGORY_ID_LOOKUP_FIELD, PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD, \
10
11
  PRODUCT_STOCK_FIELD
@@ -16,41 +17,35 @@ from tgshops_integrations.nocodb_connector.tables import *
16
17
 
17
18
  from loguru import logger
18
19
 
19
- # Step 1: Define the path to config.py (one level above)
20
- config_path = Path(__file__).resolve().parent.parent / '../config.py'
21
-
22
- # Step 2: Load config.py dynamically using importlib
23
- spec = importlib.util.spec_from_file_location("config", config_path)
24
- config = importlib.util.module_from_spec(spec)
25
- spec.loader.exec_module(config)
26
-
27
- # Step 3: Access variables from config.py
28
- NOCODB_CATEGORIES = config.NOCODB_CATEGORIES
29
- NOCODB_PRODUCTS = config.NOCODB_PRODUCTS
30
- NOCODB_STATUSES = config.NOCODB_STATUSES
31
- NOCODB_BOT_MESSAGES = config.NOCODB_BOT_MESSAGES
32
- NOCODB_ORDERS = config.NOCODB_ORDERS
33
-
34
20
  class Gateway(NocodbClient):
35
21
 
36
- def __init__(self,logging=False,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None,filter_buttons=[],special_attributes=False):
22
+ def __init__(self,logging=False,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None,filter_buttons=[],config_path=None,special_attributes=False):
37
23
  super().__init__(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY,SOURCE=SOURCE)
38
- self.NOCODB_HOST = NOCODB_HOST
39
- self.NOCODB_API_KEY = NOCODB_API_KEY
40
- self.logging=logging
41
- self.required_fields = [PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD]
24
+ if config_path:
25
+ self.load_config_from_path(config_path)
26
+
27
+ self.logging = logging
28
+ self.required_fields = [self.config.PRODUCT_NAME_FIELD, self.config.PRODUCT_PRICE_FIELD]
42
29
  self.projection = []
43
- self.special_attributes=special_attributes
44
- self.filter_buttons=filter_buttons
30
+ self.special_attributes = special_attributes
31
+ self.filter_buttons = filter_buttons
32
+
33
+ def load_config_from_path(self,config_path):
34
+ if config_path.exists():
35
+ spec = importlib.util.spec_from_file_location("config", config_path)
36
+ self.config = importlib.util.module_from_spec(spec)
37
+ spec.loader.exec_module(self.config)
38
+ else:
39
+ raise FileNotFoundError(f"Configuration file not found at {config_path}")
45
40
 
46
41
  async def load_data(self,SOURCE=None):
47
42
  self.SOURCE=SOURCE
48
43
  await self.get_all_tables()
49
- self.category_manager=CategoryManager(table_id=self.tables_list[NOCODB_CATEGORIES],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True,filter_buttons=self.filter_buttons)
50
- self.product_manager=ProductManager(table_id=self.tables_list[NOCODB_PRODUCTS],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True)
44
+ self.category_manager=CategoryManager(table_id=self.tables_list[self.config.NOCODB_CATEGORIES],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True,filter_buttons=self.filter_buttons)
45
+ self.product_manager=ProductManager(table_id=self.tables_list[self.config.NOCODB_PRODUCTS],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True)
51
46
 
52
47
  async def create_product(self,product: ProductModel) -> ProductModel:
53
- products_table = self.tables_list[NOCODB_PRODUCTS]
48
+ products_table = self.tables_list[self.config.NOCODB_PRODUCTS]
54
49
  data = dump_product_data_with_check(data=product ,data_check=self.category_manager.categories)
55
50
  # product_json = dump_product_data_with_check(data=product,data_check=self.categories)
56
51
  external_id = data.pop("ID")
@@ -80,7 +75,7 @@ class Gateway(NocodbClient):
80
75
  return actual_products
81
76
 
82
77
  async def update_products(self, external_products: List[ProductModel]):
83
- products_table = self.tables_list[NOCODB_PRODUCTS]
78
+ products_table = self.tables_list[self.config.NOCODB_PRODUCTS]
84
79
  await self.product_manager.update_attributes(products=external_products)
85
80
  # Updates categories if there were a new ones created
86
81
  external_products=await self.category_manager.map_categories(external_products=external_products)
@@ -99,7 +94,7 @@ class Gateway(NocodbClient):
99
94
  await self.create_product(product=product)
100
95
 
101
96
  async def update_product(self, product: ProductModel):
102
- products_table = self.tables_list[NOCODB_PRODUCTS]
97
+ products_table = self.tables_list[self.config.NOCODB_PRODUCTS]
103
98
  data = dump_product_data_with_check(data=product ,data_check=self.category_manager.categories)
104
99
 
105
100
  await self.update_table_record(
@@ -115,40 +110,8 @@ class Gateway(NocodbClient):
115
110
  return product.id
116
111
  return None # Return None if no product is found with the given name
117
112
 
118
- async def create_table_column(self, name: str, table_id: Optional[str] = None):
119
-
120
- BEARER_TOKEN = "jpdxJtyfDXdjbvxKAcIij1HA8HGalgalLLXZ46DV"
121
-
122
- headers = {
123
- "Authorization": f"Bearer {BEARER_TOKEN}"
124
- }
125
- if not table_id:
126
- table_id = self.tables_list[NOCODB_PRODUCTS]
127
-
128
- response = await self.httpx_client.post(
129
- f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/meta/tables/{table_id}/columns",
130
- json={
131
- "column_name": name,
132
- "dt": "character varying",
133
- "dtx": "specificType",
134
- "ct": "varchar(45)",
135
- "clen": 45,
136
- "dtxp": "45",
137
- "dtxs": "",
138
- "altered": 1,
139
- "uidt": "SingleLineText",
140
- "uip": "",
141
- "uicn": "",
142
- "title": name
143
- },
144
- headers=headers
145
- )
146
- logger.info(response.text())
147
-
148
- return response.json()
149
-
150
113
  async def delete_all_products(self):
151
114
  items = await self.product_manager.get_products_v2(offset=0,limit=200)
152
- products_table = self.tables_list[NOCODB_PRODUCTS]
115
+ products_table = self.tables_list[self.config.NOCODB_PRODUCTS]
153
116
  for num,item in enumerate(items):
154
117
  await self.delete_table_record(products_table, item.id)
@@ -267,10 +267,22 @@ class NocodbClient:
267
267
  files = {'file': (image_name, file, 'image/jpeg')}
268
268
 
269
269
  url = f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/storage/upload?path=noco/{source_column_id}/{product_table_name}/{images_column_id}"
270
- timeout = httpx.Timeout(100.0)
271
- response = await self.httpx_client.post(url,files=files,headers=self.httpx_client.headers,timeout=timeout)
272
- if response.status_code == 200:
273
- return response.json()[0]['url']
274
- raise Exception(response.text)
270
+ timeout = httpx.Timeout(200.0)
271
+ try:
272
+ # First attempt
273
+ response = await self.httpx_client.post(url, files=files, headers=self.httpx_client.headers, timeout=timeout)
274
+ if response.status_code == 200:
275
+ return response.json()[0]['url']
276
+
277
+ # If first attempt fails, retry once
278
+ response = await self.httpx_client.post(url, files=files, headers=self.httpx_client.headers, timeout=timeout)
279
+ if response.status_code == 200:
280
+ return response.json()[0]['url']
281
+
282
+ except httpx.HTTPStatusError as e:
283
+ raise Exception(f"HTTP error occurred: {e}")
284
+ except Exception as e:
285
+ raise Exception(f"Request failed: {str(e)}")
286
+
275
287
  else:
276
288
  return ""
@@ -11,41 +11,58 @@ from tgshops_integrations.models.products import ProductModel
11
11
  import importlib.util
12
12
  from pathlib import Path
13
13
 
14
- # Step 1: Define the path to config.py (one level above)
15
- config_path = Path(__file__).resolve().parent.parent / '../config.py'
16
-
17
- # Step 2: Load config.py dynamically using importlib
18
- spec = importlib.util.spec_from_file_location("config", config_path)
19
- config = importlib.util.module_from_spec(spec)
20
- spec.loader.exec_module(config)
21
-
22
- # Step 3: Load all required constants from config.py
23
- CATEGORY_IMAGE_FIELD = config.CATEGORY_IMAGE_FIELD
24
- ID_FIELD = config.ID_FIELD
25
- CATEGORY_NAME_FIELD = config.CATEGORY_NAME_FIELD
26
- CATEGORY_PARENT_ID_FIELD = config.CATEGORY_PARENT_ID_FIELD
27
- CATEGORY_PARENT_FIELD = config.CATEGORY_PARENT_FIELD
28
-
29
- PRODUCT_NAME_FIELD = config.PRODUCT_NAME_FIELD
30
- PRODUCT_DESCRIPTION_FIELD = config.PRODUCT_DESCRIPTION_FIELD
31
- PRODUCT_PRICE_FIELD = config.PRODUCT_PRICE_FIELD
32
- PRODUCT_CURRENCY_FIELD = config.PRODUCT_CURRENCY_FIELD
33
- PRODUCT_STOCK_FIELD = config.PRODUCT_STOCK_FIELD
34
- PRODUCT_CATEGORY_NAME_FIELD = config.PRODUCT_CATEGORY_NAME_FIELD
35
- PRODUCT_CATEGORY_ID_FIELD = config.PRODUCT_CATEGORY_ID_FIELD
36
- PRODUCT_IMAGE_FIELD = config.PRODUCT_IMAGE_FIELD
37
- PRODUCT_DISCOUNT_PRICE_FIELD = config.PRODUCT_DISCOUNT_PRICE_FIELD
38
- PRODUCT_CATEGORY_ID_LOOKUP_FIELD = config.PRODUCT_CATEGORY_ID_LOOKUP_FIELD
39
- PRODUCT_REQUIRED_OPTIONS_FIELD = config.PRODUCT_REQUIRED_OPTIONS_FIELD
40
- PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD
41
- PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD
42
- PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD = config.PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD
43
- PRODUCT_ID_FIELD = config.PRODUCT_ID_FIELD
44
- PRODUCT_EXTERNAL_ID = config.PRODUCT_EXTERNAL_ID
45
- PRODUCT_CHECKOUT_MODE = config.PRODUCT_CHECKOUT_MODE
46
- NEW_ID_FIELD = config.NEW_ID_FIELD
47
-
48
- NOCODB_CHECKOUT_MODES = config.NOCODB_CHECKOUT_MODES
14
+
15
+ # Helper function to load config.py dynamically
16
+ def load_config(config_path):
17
+ config_path = Path(config_path)
18
+ if config_path.exists():
19
+ spec = importlib.util.spec_from_file_location("config", config_path)
20
+ config = importlib.util.module_from_spec(spec)
21
+ spec.loader.exec_module(config)
22
+ return config
23
+ else:
24
+ raise FileNotFoundError(f"Configuration file not found at {config_path}")
25
+
26
+ # Modify this to accept config_path dynamically
27
+ def initialize_model_mapping(config_path):
28
+ global CATEGORY_IMAGE_FIELD, ID_FIELD, CATEGORY_NAME_FIELD, CATEGORY_PARENT_ID_FIELD, CATEGORY_PARENT_FIELD,CATEGORY_ID_OF_CATEGORY_FIELD
29
+ global PRODUCT_NAME_FIELD, PRODUCT_DESCRIPTION_FIELD, PRODUCT_PRICE_FIELD, PRODUCT_CURRENCY_FIELD, PRODUCT_STOCK_FIELD
30
+ global PRODUCT_CATEGORY_NAME_FIELD, PRODUCT_CATEGORY_ID_FIELD, PRODUCT_IMAGE_FIELD, PRODUCT_DISCOUNT_PRICE_FIELD
31
+ global PRODUCT_CATEGORY_ID_LOOKUP_FIELD, PRODUCT_REQUIRED_OPTIONS_FIELD, PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD
32
+ global PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD, PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD, PRODUCT_ID_FIELD
33
+ global PRODUCT_EXTERNAL_ID, PRODUCT_CHECKOUT_MODE, NEW_ID_FIELD, NOCODB_CHECKOUT_MODES
34
+
35
+ config = load_config(config_path)
36
+
37
+ # Step 3: Load all required constants from config.py
38
+ CATEGORY_IMAGE_FIELD = config.CATEGORY_IMAGE_FIELD
39
+ ID_FIELD = config.ID_FIELD
40
+ CATEGORY_NAME_FIELD = config.CATEGORY_NAME_FIELD
41
+ CATEGORY_PARENT_ID_FIELD = config.CATEGORY_PARENT_ID_FIELD
42
+ CATEGORY_PARENT_FIELD = config.CATEGORY_PARENT_FIELD
43
+ CATEGORY_ID_OF_CATEGORY_FIELD = config.CATEGORY_ID_OF_CATEGORY_FIELD
44
+
45
+ PRODUCT_NAME_FIELD = config.PRODUCT_NAME_FIELD
46
+ PRODUCT_DESCRIPTION_FIELD = config.PRODUCT_DESCRIPTION_FIELD
47
+ PRODUCT_PRICE_FIELD = config.PRODUCT_PRICE_FIELD
48
+ PRODUCT_CURRENCY_FIELD = config.PRODUCT_CURRENCY_FIELD
49
+ PRODUCT_STOCK_FIELD = config.PRODUCT_STOCK_FIELD
50
+ PRODUCT_CATEGORY_NAME_FIELD = config.PRODUCT_CATEGORY_NAME_FIELD
51
+
52
+ PRODUCT_CATEGORY_ID_FIELD = config.PRODUCT_CATEGORY_ID_FIELD
53
+ PRODUCT_IMAGE_FIELD = config.PRODUCT_IMAGE_FIELD
54
+ PRODUCT_DISCOUNT_PRICE_FIELD = config.PRODUCT_DISCOUNT_PRICE_FIELD
55
+ PRODUCT_CATEGORY_ID_LOOKUP_FIELD = config.PRODUCT_CATEGORY_ID_LOOKUP_FIELD
56
+ PRODUCT_REQUIRED_OPTIONS_FIELD = config.PRODUCT_REQUIRED_OPTIONS_FIELD
57
+ PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD
58
+ PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD
59
+ PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD = config.PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD
60
+ PRODUCT_ID_FIELD = config.PRODUCT_ID_FIELD
61
+ PRODUCT_EXTERNAL_ID = config.PRODUCT_EXTERNAL_ID
62
+ PRODUCT_CHECKOUT_MODE = config.PRODUCT_CHECKOUT_MODE
63
+ NEW_ID_FIELD = config.NEW_ID_FIELD
64
+
65
+ NOCODB_CHECKOUT_MODES = config.NOCODB_CHECKOUT_MODES
49
66
 
50
67
 
51
68
  def get_pagination_info(page_info: dict) -> PaginationResponseModel:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tgshops-integrations
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: Library is intended to provide the integration of the external service or CRM system with the TelegramShops/It allows to configure the relationship between NocoDB list of the products used further to display in the shop/As a resultss the products can be synchronized and updated uppon the request.
5
5
  Home-page: https://git.the-devs.com/virtual-shops/shop-system/shop-backend-integrations/integration-library/integration-library
6
6
  Author: Dimi Latoff
@@ -1,16 +1,16 @@
1
1
  tgshops_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  tgshops_integrations/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- tgshops_integrations/middlewares/gateway.py,sha256=GxXs-lVtrdWq3WTh_j0Hi1ffGsIOCCKJfOu3eyiRI2g,7441
3
+ tgshops_integrations/middlewares/gateway.py,sha256=TLHASMEJMky-hYTqU5Q2Dh3C4hIjfAN-l6HoP6PzQpQ,6325
4
4
  tgshops_integrations/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  tgshops_integrations/models/categories.py,sha256=EG6C8g5dOfXB2MH-vtqH13aqB7_VyOobY2FHpDb-fsY,977
6
6
  tgshops_integrations/models/products.py,sha256=i0vP_eJMVCB-W25BCoodIB0AhsMTqYiDO48N-B6Ueo0,1379
7
7
  tgshops_integrations/nocodb_connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  tgshops_integrations/nocodb_connector/categories.py,sha256=YfQQ8UAkOonNZKO-oTH36vODu0eE-ElG-Me2bH5LdEM,9072
9
- tgshops_integrations/nocodb_connector/client.py,sha256=MD08P25jYWg0ZoLGk9cZ7cH8WJ6vtMn9oXW1N0LuyZ0,11504
10
- tgshops_integrations/nocodb_connector/model_mapping.py,sha256=xLmfHGZBM4aEf2ZxVuitx5ckXQFSF9VpYms6qy2ao8Y,7653
9
+ tgshops_integrations/nocodb_connector/client.py,sha256=FWEWSL5uxZJIIP8gNcfZT3KyBh1CLm9lsAElD93iyCU,12063
10
+ tgshops_integrations/nocodb_connector/model_mapping.py,sha256=CeGAwEZLK_NlK_eBTtsH6oRsjo6Z-QxWdC9KnwEhtPw,8770
11
11
  tgshops_integrations/nocodb_connector/products.py,sha256=23uXnmznJN6fZw3tZ3a7dJg06LkI2QaNfVhSKochPn4,8677
12
12
  tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
13
- tgshops_integrations-1.0.dist-info/METADATA,sha256=7D9nxdGywF15S1rf_XBFr3DKX3skyvq_P9O-EQ1oeyo,2774
14
- tgshops_integrations-1.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
15
- tgshops_integrations-1.0.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
16
- tgshops_integrations-1.0.dist-info/RECORD,,
13
+ tgshops_integrations-1.2.dist-info/METADATA,sha256=3PxRU6u73rR4bA2IzvCjKHV5UCEXEA02_J1fOL-46i4,2774
14
+ tgshops_integrations-1.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
15
+ tgshops_integrations-1.2.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
16
+ tgshops_integrations-1.2.dist-info/RECORD,,