tgshops-integrations 3.0__py3-none-any.whl → 3.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,8 +23,8 @@ from tgshops_integrations.nocodb_connector.model_mapping import (
23
23
  PRODUCT_IMAGES_LOOKUP_FIELD,
24
24
  PRODUCT_EXTERNAL_ID,
25
25
  )
26
- from services.nocodb_connector.categories import CategoryManager
27
- from services.nocodb_connector.products import ProductManager
26
+ from tgshops_integrations.nocodb_connector.categories import CategoryManager
27
+ from tgshops_integrations.nocodb_connector.products import ProductManager
28
28
  from loguru import logger
29
29
 
30
30
 
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel
2
- from typing import Any, List, Optional
2
+ from typing import Any, List,Dict, Optional
3
3
  from datetime import datetime
4
4
 
5
5
  from pydantic import BaseModel, Field, schema, validator
@@ -28,12 +28,12 @@ class ExternalProductModel(BaseModel):
28
28
  class ProductModel(BaseModel):
29
29
  id: Optional[str]
30
30
  external_id: Optional[str]
31
- category: Optional[List[int]]
32
- category_name: Optional[List[str]]
31
+ product_properties: Optional[List[str]]
32
+ categories_structure: Optional[Dict[str, Any]]
33
33
  name: str
34
34
  description: Optional[str]
35
35
  price: Optional[float]
36
- final_price: Optional[int]
36
+ final_price: Optional[float]
37
37
  currency: Optional[str]
38
38
  stock_qty: int
39
39
  orders_qty: int = Field(0, hidden_field=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tgshops-integrations
3
- Version: 3.0
3
+ Version: 3.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,18 +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=Gzr8gZWQ3B3tiqpE_iSqtAkINDbsm7QT_v_bjT1b8V4,6223
3
+ tgshops_integrations/middlewares/gateway.py,sha256=-YQi3-62k6gkxJLx27FESe6wlRrD4_Ltqk8wZJ8ExYg,6247
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
- tgshops_integrations/models/products.py,sha256=zkRqXLU-tAReDF5R9j3q58le686dw3PapxcFOGUpk7Q,1376
6
+ tgshops_integrations/models/products.py,sha256=CpQ5LpeigklgVnkKPCm5jR9hfJsoFbWKSsDiTI32RSo,1405
7
7
  tgshops_integrations/nocodb_connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- tgshops_integrations/nocodb_connector/categories.py,sha256=eoAWRA0aSE84_ONrwflM1ZFXLSCFjYl_CFqUf7lA_EU,9063
9
8
  tgshops_integrations/nocodb_connector/categories_management.py,sha256=dvPXcrgruJyuWFT7Ho-8YRcjJxhR3Hcw0hJiVKQgcyA,6603
10
9
  tgshops_integrations/nocodb_connector/client.py,sha256=U2rNozjluWVul-VZroQW-8b2j6nEtKrhOkFMz1L8KmI,12022
11
10
  tgshops_integrations/nocodb_connector/model_mapping.py,sha256=ixv-uT1Pmt1szZCQ5pGsMbwRvmnHZmn0NqG77nxZkDM,8551
12
- tgshops_integrations/nocodb_connector/products.py,sha256=kAp7lUaRO7CkU_SumbIdLOJf38SmDBEyBBZCYyyyOFM,9313
13
11
  tgshops_integrations/nocodb_connector/products_management.py,sha256=t_ZDyIAPTe_6UrxLuJ32Uhlk8ssErP6Eo0n-TIZfEko,6046
14
12
  tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
15
- tgshops_integrations-3.0.dist-info/METADATA,sha256=E4e2SV-nFT2HLUEzRZ90PWI1BTAzoaTtnXm-ZYalN5k,2774
16
- tgshops_integrations-3.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
17
- tgshops_integrations-3.0.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
18
- tgshops_integrations-3.0.dist-info/RECORD,,
13
+ tgshops_integrations-3.2.dist-info/METADATA,sha256=daiTMAx10_7qtGpB4ILTHYZCEnU9eFtuYENzwki-VTc,2774
14
+ tgshops_integrations-3.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
15
+ tgshops_integrations-3.2.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
16
+ tgshops_integrations-3.2.dist-info/RECORD,,
@@ -1,168 +0,0 @@
1
- from typing import List
2
- from loguru import logger
3
-
4
- from aiocache import cached
5
- from tgshops_integrations.models.categories import CategoryModel,CategoryResponseModel,CategoryListResponseModel
6
- from tgshops_integrations.models.products import ProductModel
7
- from tgshops_integrations.nocodb_connector.client import custom_key_builder, NocodbClient
8
- from tgshops_integrations.nocodb_connector.model_mapping import CATEGORY_IMAGE_FIELD, CATEGORY_NAME_FIELD, CATEGORY_PARENT_FIELD, \
9
- CATEGORY_PARENT_ID_FIELD, PRODUCT_NAME_FIELD,CATEGORY_ID_OF_CATEGORY_FIELD, dump_category_data, get_pagination_info, parse_category_data
10
-
11
-
12
- class CategoryManager(NocodbClient):
13
- def __init__(self,table_id=None,logging=False,config_type=None,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None,filter_buttons=[]):
14
- super().__init__(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY,SOURCE=SOURCE)
15
- self.NOCODB_HOST = NOCODB_HOST
16
- self.NOCODB_API_KEY = NOCODB_API_KEY
17
- self.SOURCE=SOURCE
18
- self.CONFIG_TYPE=config_type
19
- self.categories_table=table_id
20
- self.external_categories={}
21
- self.logging=logging
22
- self.filter_categories=[]
23
- self.filter_buttons=filter_buttons
24
- self.required_fields = [CATEGORY_NAME_FIELD]
25
- self.projection = ["Id", CATEGORY_NAME_FIELD, CATEGORY_PARENT_ID_FIELD, CATEGORY_ID_OF_CATEGORY_FIELD]
26
- # self.projection = ["Id"]
27
-
28
- @cached(ttl=30, key_builder=custom_key_builder)
29
- async def get_categories(self, table_id: str) -> List[CategoryModel]:
30
- records = await self.get_table_records(table_id, self.required_fields, self.projection)
31
- return [parse_category_data(record) for record in records]
32
-
33
- @cached(ttl=30, key_builder=custom_key_builder)
34
- async def get_categories_v2(self,
35
- table_id: str,
36
- offset: int = None,
37
- limit: int = None) -> CategoryModel:
38
- response = (await self.get_table_records_v2(table_name=self.categories_table,
39
- required_fields=self.required_fields,
40
- projection=self.projection,
41
- offset=offset,
42
- limit=limit))
43
- page_info = get_pagination_info(page_info=response['pageInfo'])
44
- categories = [parse_category_data(record) for record in response['list']]
45
- return CategoryListResponseModel(categories=categories, page_info=page_info)
46
-
47
- @cached(ttl=30, key_builder=custom_key_builder)
48
- async def get_category(self, table_id: str, category_id: str) -> CategoryModel:
49
- record = await self.get_table_record(self.categories_table, category_id, self.required_fields, self.projection)
50
- return parse_category_data(record)
51
-
52
- async def create_category(self, table_id: str, category: CategoryModel) -> CategoryModel:
53
- category_json = dump_category_data(category)
54
- record = await self.create_table_record(self.categories_table, category_json)
55
- return parse_category_data(record)
56
-
57
- @cached(ttl=30, key_builder=custom_key_builder)
58
- async def get_categories_in_category(self, table_id: str, category_id: str) -> List[CategoryModel]:
59
- # ! In case category_id == 0,
60
- # we need to get all categories without parent by field CATEGORY_PARENT_FIELD not CATEGORY_PARENT_ID_FIELD
61
- records = await self.get_table_records(
62
- table_name=self.categories_table,
63
- required_fields=self.required_fields,
64
- projection=self.projection,
65
- extra_where=(f"({CATEGORY_PARENT_ID_FIELD},eq,{category_id})"
66
- if category_id else f"({CATEGORY_PARENT_FIELD},eq,0)"))
67
- return [parse_category_data(record) for record in records]
68
-
69
- @cached(ttl=30, key_builder=custom_key_builder)
70
- async def get_categories_in_category_v2(self,
71
- table_id: str,
72
- category_id: str,
73
- offset: int,
74
- limit: int) -> CategoryModel:
75
-
76
- response = await self.get_table_records_v2(
77
- table_name=self.categories_table,
78
- required_fields=self.required_fields,
79
- projection=self.projection,
80
- extra_where=(f"({CATEGORY_PARENT_ID_FIELD},eq,{category_id})"
81
- if category_id else f"({CATEGORY_PARENT_FIELD},eq,0)"),
82
- offset=offset,
83
- limit=limit)
84
- categories = [parse_category_data(record) for record in response['list']]
85
- page_info = get_pagination_info(page_info=response['pageInfo'])
86
- return CategoryModel(categories=categories, page_info=page_info)
87
-
88
- async def update_categories(self,external_products: List[ProductModel]) -> List[ProductModel]:
89
- # Get the names of the tables from the DB for further handling
90
- self.categories=await self.get_product_categories(table_id=self.categories_table, table_name=PRODUCT_NAME_FIELD)
91
-
92
- categories_list=[*self.categories.keys()]
93
- categories_to_create=[]
94
-
95
- for product in external_products:
96
- # Check for new categories
97
- # DEPRECATED IF some category number exists on external side
98
- # if product.category:
99
- # for external_category_id,category_name in zip(product.category,product.category_name):
100
- # if category_name not in [*self.categories.keys()]:
101
- # new_category= await self.create_product_category(table_id=self.categories_table,category_name=category_name,category_id=external_category_id,table_name=PRODUCT_NAME_FIELD)
102
- # if self.logging:
103
- # logger.info(f"New Category {new_category}")
104
- # self.external_categories[new_category["Id"]]=external_category_id
105
- # self.categories=await self.get_product_categories(table_id=self.categories_table, table_name=PRODUCT_NAME_FIELD)
106
- #Else if there is just the name, create the category with new id
107
- # else:
108
- #Needs the buttons to be initialized, can connect items to buttons , which allows filtered acces through the menu
109
- for num,category_name in enumerate(product.category_name):
110
- if category_name not in categories_list:
111
- if self.filter_buttons:
112
- parent_id=self.categories[self.filter_buttons[num]]
113
- else:
114
- parent_id=self.categories[product.category_II_name[0]]
115
- categories_to_create.append([category_name,parent_id])
116
- categories_list.append(category_name)
117
-
118
- if categories_to_create:
119
- categories_to_create.sort(key=lambda x: x[0])
120
- if [*self.categories.values()]:
121
- new_id=max(self.categories.values())+1
122
- else:
123
- new_id=1
124
- for category,parent_id in categories_to_create:
125
- new_category= await self.create_product_category(table_id=self.categories_table,category_name=category,category_id=new_id,table_name=PRODUCT_NAME_FIELD)
126
- if self.logging:
127
- logger.info(f"New Category {new_category}")
128
- if self.filter_buttons:
129
- #Rewind categories
130
- await self.link_categories(parent_id=parent_id,child_id=new_id)
131
- new_id+=1
132
-
133
- async def link_categories(self,parent_id:int,child_id:int):
134
- metadata = await self.get_table_meta(self.categories_table)
135
-
136
- linked_column = None
137
- for col in metadata['columns']:
138
- if (col["title"]=="Set parent category" and col["uidt"] == "Links"):
139
- linked_column = col
140
- break
141
- await self.link_table_record(
142
- linked_column["base_id"],
143
- linked_column["fk_model_id"],
144
- child_id,
145
- linked_column["id"],
146
- parent_id)
147
-
148
- async def unlink_categories(self,parent_id:int,child_id:int):
149
- metadata = await self.get_table_meta(self.categories_table)
150
-
151
- linked_column = None
152
- for col in metadata['columns']:
153
- if col["uidt"] == "Links":
154
- linked_column = col
155
- break
156
- await self.unlink_table_record(
157
- linked_column["base_id"],
158
- linked_column["fk_model_id"],
159
- parent_id,
160
- linked_column["id"],
161
- child_id)
162
-
163
- async def map_categories(self,external_products: List[ProductModel]) -> List[ProductModel]:
164
- for num,product in enumerate(external_products):
165
- if not product.category:
166
- if product.category_name:
167
- external_products[num].category=[str(self.categories[category_name]) for category_name in product.category_name]
168
- return external_products
@@ -1,181 +0,0 @@
1
- from typing import List,Optional
2
-
3
- from aiocache import cached
4
- from tgshops_integrations.models.products import ProductModel
5
- from tgshops_integrations.models.products import ProductModel, ProductModel
6
- from tgshops_integrations.nocodb_connector.client import custom_key_builder, NocodbClient
7
- from tgshops_integrations.nocodb_connector.model_mapping import dump_product_data,dump_product_data_with_check, get_pagination_info, ID_FIELD, \
8
- parse_product_data, PRODUCT_CATEGORY_ID_LOOKUP_FIELD, PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD, \
9
- PRODUCT_STOCK_FIELD,PRODUCT_EXTERNAL_ID,PRODUCT_IMAGES_LOOKUP_FIELD
10
-
11
- from tgshops_integrations.nocodb_connector.tables import *
12
- from loguru import logger
13
- import hashlib
14
-
15
-
16
- class ProductManager(NocodbClient):
17
-
18
- def __init__(self, table_id=None, logging=False, NOCODB_HOST=None, NOCODB_API_KEY=None, SOURCE=None):
19
- super().__init__(NOCODB_HOST=NOCODB_HOST, NOCODB_API_KEY=NOCODB_API_KEY, SOURCE=SOURCE)
20
- self.NOCODB_HOST = NOCODB_HOST
21
- self.NOCODB_API_KEY = NOCODB_API_KEY
22
- self.SOURCE = SOURCE
23
- self.logging = logging
24
- self.required_fields = [PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD]
25
- self.projection = []
26
- self.external_categories = {}
27
- self.products_table = table_id
28
- self.actual_products = []
29
- self.columns = []
30
-
31
- def hash_product(self,product,special_attributes=False):
32
- if special_attributes:
33
- hash_string = ''.join(attr.description for attr in product.extra_attributes if attr.name.endswith('*'))
34
- # hash_string = f"{product.external_id}{product.price}{product.category_name.sort()}{product.name}{product.description}"
35
- else:
36
- # Concatenate relevant attributes into a single string
37
- hash_string = f"{product.external_id}{product.price}{product.category_name.sort()}{product.name}{product.description}"
38
- # hash_string = f"{product.external_id}{product.price}{product.category_name}{product.name}{product.description}{product.preview_url}"
39
- # Hash the concatenated string
40
- hash_object = hashlib.sha256(hash_string.encode())
41
- hex_dig = hash_object.hexdigest()
42
- return hex_dig
43
-
44
- @cached(ttl=30, key_builder=custom_key_builder)
45
- async def get_products(self, table_id: str) -> List[ProductModel]:
46
- records = await self.get_table_records(self.products_table, self.required_fields, self.projection)
47
- return [parse_product_data(record) for record in records]
48
-
49
- # @cached(ttl=30, key_builder=custom_key_builder)
50
- async def get_products_v2(self, offset: int, limit: int, table_id: Optional[str] = None) -> List[ProductModel]:
51
- # Get the names of the tables from the DB for further handling
52
- await self.get_all_tables()
53
- response = await self.get_table_records_v2(table_name=self.products_table,
54
- required_fields=self.required_fields,
55
- projection=self.projection,
56
- offset=offset,
57
- limit=limit)
58
- products = [await parse_product_data(record) for record in response['list']]
59
-
60
- return products
61
-
62
- @cached(ttl=180, key_builder=custom_key_builder)
63
- async def search_products(self, table_id: str, search_string: str, limit: int) -> List[ProductModel]:
64
- records = await self.get_table_records(
65
- table_name=self.products_table,
66
- required_fields=self.required_fields,
67
- projection=self.projection,
68
- extra_where=f"({PRODUCT_NAME_FIELD},like,%{search_string}%)", # Update with actual product name field
69
- limit=limit
70
- )
71
- return [parse_product_data(record) for record in records]
72
-
73
- @cached(ttl=180, key_builder=custom_key_builder)
74
- async def search_products_v2(self, table_id: str, search_string: str, limit: int) -> List[ProductModel]:
75
- records = (await self.get_table_records_v2(
76
- table_name=self.products_table,
77
- required_fields=self.required_fields,
78
- projection=self.projection,
79
- extra_where=f"({PRODUCT_NAME_FIELD},like,%{search_string}%)", # Update with actual product name field
80
- limit=limit
81
- ))['list']
82
- return [parse_product_data(record) for record in records]
83
-
84
- @cached(ttl=60, key_builder=custom_key_builder)
85
- async def get_product(self, table_id: str, product_id: str) -> ProductModel:
86
- record = await self.get_table_record(self.products_table, product_id)
87
- return parse_product_data(record)
88
-
89
- @cached(ttl=60, key_builder=custom_key_builder)
90
- async def get_product_v2(self, table_id: str, product_id: str) -> ProductModel:
91
- record = await self.get_table_record(self.products_table, product_id)
92
- product = parse_product_data(record)
93
-
94
- related_products = await self.get_table_records_v2(
95
- table_name=self.products_table,
96
- required_fields=self.required_fields,
97
- projection=self.projection,
98
- extra_where=(f'({PRODUCT_STOCK_FIELD},gt,0)~and'
99
- f"({PRODUCT_CATEGORY_ID_LOOKUP_FIELD},eq,{product.category[0]})~and"
100
- f'({PRODUCT_NAME_FIELD},neq,{product.name})'),
101
- limit=5
102
- )
103
- related_products = [parse_product_data(product) for product in related_products['list']]
104
-
105
- product.related_products = related_products
106
- return product
107
-
108
- @cached(ttl=60, key_builder=custom_key_builder)
109
- async def get_product_in_category(self, table_id: str, category_id: str = None) -> List[ProductModel]:
110
- if category_id is None:
111
- return await self.get_products(table_id=self.products_table)
112
-
113
- records = await self.get_table_records(
114
- table_name=self.products_table,
115
- required_fields=self.required_fields,
116
- projection=self.projection,
117
- extra_where=(f'({PRODUCT_STOCK_FIELD},gt,0)~and'
118
- f"({PRODUCT_CATEGORY_ID_LOOKUP_FIELD},eq,{category_id})")
119
- )
120
- return [parse_product_data(record) for record in records]
121
-
122
- @cached(ttl=60, key_builder=custom_key_builder)
123
- async def get_product_in_category_v2(self,
124
- table_id: str,
125
- offset: int,
126
- limit: int,
127
- category_id: str = None) -> ProductModel:
128
- if category_id is None:
129
- return await self.get_products_v2(table_id=self.products_table, offset=offset, limit=limit)
130
-
131
- response = (await self.get_table_records_v2(
132
- table_name=self.products_table,
133
- required_fields=self.required_fields,
134
- projection=self.projection,
135
- extra_where=(f'({PRODUCT_STOCK_FIELD},gt,0)~and'
136
- f"({PRODUCT_CATEGORY_ID_LOOKUP_FIELD},eq,{category_id})"),
137
- offset=offset,
138
- limit=limit
139
- ))
140
- page_info = get_pagination_info(page_info=response['pageInfo'])
141
- products = [parse_product_data(record) for record in response['list']]
142
- return ProductModel(products=products, page_info=page_info)
143
-
144
- @cached(ttl=60, key_builder=custom_key_builder)
145
- async def get_products_by_ids(self, table_id: str, product_ids: list) -> List[ProductModel]:
146
- product_ids_str = ','.join(str(product_id) for product_id in product_ids)
147
-
148
- records = await self.get_table_records(
149
- table_name=self.products_table,
150
- required_fields=self.required_fields,
151
- projection=self.projection,
152
- extra_where=f"({ID_FIELD},in,{product_ids_str})")
153
- return [parse_product_data(record) for record in records]
154
-
155
- @cached(ttl=60, key_builder=custom_key_builder)
156
- async def update_attributes(self,products:List[ProductModel]):
157
- system_attributes = [PRODUCT_EXTERNAL_ID,PRODUCT_IMAGES_LOOKUP_FIELD]
158
- attributes=await self.get_table_meta(table_name=self.products_table)
159
- self.columns =[item['title'].lower() for item in attributes.get('columns', [])]
160
-
161
- #TODO Requires Validation
162
- for attribute_name in system_attributes:
163
- if attribute_name.lower() not in self.columns:
164
- response =await self.create_table_column(table_name=self.products_table,name=attribute_name)
165
- logger.info(f"Created attribute: {attribute_name}")
166
-
167
- for item in products:
168
- attributes=await self.get_table_meta(table_name=self.products_table)
169
- self.columns =[item['title'].lower() for item in attributes.get('columns', [])]
170
-
171
- for attribute in item.extra_attributes:
172
- if attribute.name.rstrip().lower() not in self.columns:
173
- response =await self.create_table_column(table_name=self.products_table,name=attribute.name.lower())
174
- logger.info(f"Created attribute: {attribute.name.lower()}")
175
-
176
-
177
- def find_product_id_by_name(self,name: str):
178
- for product in self.actual_products.products:
179
- if product.name == name:
180
- return product.id
181
- return None # Return None if no product is found with the given name