tgshops-integrations 2.4__py3-none-any.whl → 3.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- tgshops_integrations/middlewares/gateway.py +115 -89
- tgshops_integrations/models/products.py +2 -3
- tgshops_integrations/nocodb_connector/categories_management.py +149 -0
- tgshops_integrations/nocodb_connector/client.py +189 -181
- tgshops_integrations/nocodb_connector/model_mapping.py +93 -84
- tgshops_integrations/nocodb_connector/products_management.py +151 -0
- {tgshops_integrations-2.4.dist-info → tgshops_integrations-3.1.dist-info}/METADATA +1 -1
- tgshops_integrations-3.1.dist-info/RECORD +18 -0
- tgshops_integrations-2.4.dist-info/RECORD +0 -16
- {tgshops_integrations-2.4.dist-info → tgshops_integrations-3.1.dist-info}/WHEEL +0 -0
- {tgshops_integrations-2.4.dist-info → tgshops_integrations-3.1.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,17 @@
|
|
1
1
|
import json
|
2
2
|
import secrets
|
3
|
-
|
4
|
-
import markdown
|
5
|
-
|
6
|
-
from tgshops_integrations.models.categories import CategoryModel
|
7
|
-
from tgshops_integrations.models.products import ExtraAttribute, ProductModel
|
8
|
-
from tgshops_integrations.models.categories import CategoryResponseModel,PaginationResponseModel
|
9
|
-
from tgshops_integrations.models.products import ProductModel
|
10
|
-
|
11
3
|
import importlib.util
|
12
4
|
from pathlib import Path
|
13
5
|
|
6
|
+
from tgshops_integrations.models.categories import CategoryModel, CategoryResponseModel, PaginationResponseModel
|
7
|
+
from tgshops_integrations.models.products import ExtraAttribute, ProductModel
|
8
|
+
|
14
9
|
|
15
|
-
# Helper function to load config.py dynamically
|
16
|
-
def load_config(config_path):
|
10
|
+
# Helper function to load `config.py` dynamically
|
11
|
+
def load_config(config_path: str):
|
12
|
+
"""
|
13
|
+
Dynamically load a config file from the provided path.
|
14
|
+
"""
|
17
15
|
config_path = Path(config_path)
|
18
16
|
if config_path.exists():
|
19
17
|
spec = importlib.util.spec_from_file_location("config", config_path)
|
@@ -23,18 +21,22 @@ def load_config(config_path):
|
|
23
21
|
else:
|
24
22
|
raise FileNotFoundError(f"Configuration file not found at {config_path}")
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
global
|
31
|
-
|
32
|
-
global
|
33
|
-
global
|
34
|
-
|
24
|
+
|
25
|
+
# Initialize model mapping constants dynamically from config
|
26
|
+
def initialize_model_mapping(config_path: str):
|
27
|
+
"""
|
28
|
+
Load and initialize global constants for model mapping from a config file.
|
29
|
+
"""
|
30
|
+
global CATEGORY_IMAGE_FIELD, ID_FIELD, CATEGORY_NAME_FIELD, CATEGORY_PARENT_ID_FIELD, CATEGORY_PARENT_FIELD
|
31
|
+
global CATEGORY_ID_OF_CATEGORY_FIELD, PRODUCT_NAME_FIELD, PRODUCT_DESCRIPTION_FIELD, PRODUCT_PRICE_FIELD
|
32
|
+
global PRODUCT_CURRENCY_FIELD, PRODUCT_STOCK_FIELD, PRODUCT_CATEGORY_NAME_FIELD, PRODUCT_CATEGORY_ID_FIELD
|
33
|
+
global PRODUCT_IMAGE_FIELD, PRODUCT_DISCOUNT_PRICE_FIELD, PRODUCT_CATEGORY_ID_LOOKUP_FIELD, PRODUCT_IMAGES_LOOKUP_FIELD
|
34
|
+
global PRODUCT_REQUIRED_OPTIONS_FIELD, PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD, PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD
|
35
|
+
global PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD, PRODUCT_ID_FIELD, PRODUCT_EXTERNAL_ID, PRODUCT_CHECKOUT_MODE
|
36
|
+
global NEW_ID_FIELD, NOCODB_CHECKOUT_MODES
|
37
|
+
|
35
38
|
config = load_config(config_path)
|
36
|
-
|
37
|
-
# Step 3: Load all required constants from config.py
|
39
|
+
|
38
40
|
CATEGORY_IMAGE_FIELD = config.CATEGORY_IMAGE_FIELD
|
39
41
|
ID_FIELD = config.ID_FIELD
|
40
42
|
CATEGORY_NAME_FIELD = config.CATEGORY_NAME_FIELD
|
@@ -48,7 +50,6 @@ def initialize_model_mapping(config_path):
|
|
48
50
|
PRODUCT_CURRENCY_FIELD = config.PRODUCT_CURRENCY_FIELD
|
49
51
|
PRODUCT_STOCK_FIELD = config.PRODUCT_STOCK_FIELD
|
50
52
|
PRODUCT_CATEGORY_NAME_FIELD = config.PRODUCT_CATEGORY_NAME_FIELD
|
51
|
-
|
52
53
|
PRODUCT_CATEGORY_ID_FIELD = config.PRODUCT_CATEGORY_ID_FIELD
|
53
54
|
PRODUCT_IMAGE_FIELD = config.PRODUCT_IMAGE_FIELD
|
54
55
|
PRODUCT_DISCOUNT_PRICE_FIELD = config.PRODUCT_DISCOUNT_PRICE_FIELD
|
@@ -62,22 +63,27 @@ def initialize_model_mapping(config_path):
|
|
62
63
|
PRODUCT_EXTERNAL_ID = config.PRODUCT_EXTERNAL_ID
|
63
64
|
PRODUCT_CHECKOUT_MODE = config.PRODUCT_CHECKOUT_MODE
|
64
65
|
NEW_ID_FIELD = config.NEW_ID_FIELD
|
65
|
-
|
66
66
|
NOCODB_CHECKOUT_MODES = config.NOCODB_CHECKOUT_MODES
|
67
67
|
|
68
68
|
|
69
69
|
def get_pagination_info(page_info: dict) -> PaginationResponseModel:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
70
|
+
"""
|
71
|
+
Parses pagination information into a model.
|
72
|
+
"""
|
73
|
+
return PaginationResponseModel(
|
74
|
+
total_rows=page_info['totalRows'],
|
75
|
+
page=page_info['page'],
|
76
|
+
page_size=page_info['pageSize'],
|
77
|
+
is_first_page=page_info['isFirstPage'],
|
78
|
+
is_last_page=page_info['isLastPage']
|
79
|
+
)
|
80
|
+
|
76
81
|
|
77
82
|
def parse_category_data(data: dict) -> CategoryResponseModel:
|
78
|
-
|
79
|
-
|
80
|
-
|
83
|
+
"""
|
84
|
+
Parses raw category data into a structured model.
|
85
|
+
"""
|
86
|
+
preview_url = data.get(CATEGORY_IMAGE_FIELD, [{}])[0].get("url", "") if data.get(CATEGORY_IMAGE_FIELD) else ""
|
81
87
|
return CategoryResponseModel(
|
82
88
|
id=str(data[ID_FIELD]),
|
83
89
|
name=data.get(CATEGORY_NAME_FIELD, ""),
|
@@ -87,22 +93,26 @@ def parse_category_data(data: dict) -> CategoryResponseModel:
|
|
87
93
|
|
88
94
|
|
89
95
|
def dump_category_data(data: CategoryModel) -> dict:
|
96
|
+
"""
|
97
|
+
Converts a CategoryModel into a dictionary suitable for API calls.
|
98
|
+
"""
|
90
99
|
return {
|
91
100
|
CATEGORY_NAME_FIELD: data.name,
|
92
101
|
CATEGORY_PARENT_FIELD: data.parent_category,
|
93
102
|
CATEGORY_IMAGE_FIELD: [
|
94
|
-
{"url": data.preview_url,
|
103
|
+
{"url": data.preview_url, "title": f"{secrets.token_hex(6)}.jpeg", "mimetype": "image/jpeg"}
|
104
|
+
]
|
95
105
|
}
|
96
106
|
|
97
107
|
|
98
108
|
def dump_product_data(data: ProductModel) -> dict:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
109
|
+
"""
|
110
|
+
Converts a ProductModel into a dictionary suitable for API calls.
|
111
|
+
"""
|
112
|
+
preview_url = [
|
113
|
+
{"url": image_url, "title": f"{secrets.token_hex(6)}.jpeg", "mimetype": "image/jpeg"}
|
114
|
+
for image_url in data.preview_url
|
115
|
+
] if data.preview_url else []
|
106
116
|
|
107
117
|
return {
|
108
118
|
PRODUCT_NAME_FIELD: data.name,
|
@@ -110,23 +120,23 @@ def dump_product_data(data: ProductModel) -> dict:
|
|
110
120
|
PRODUCT_PRICE_FIELD: data.price,
|
111
121
|
PRODUCT_CURRENCY_FIELD: data.currency,
|
112
122
|
PRODUCT_STOCK_FIELD: data.stock_qty,
|
113
|
-
PRODUCT_CATEGORY_NAME_FIELD:[data.category_name] if data.category_name else None,
|
114
|
-
|
115
|
-
PRODUCT_CATEGORY_ID_FIELD: [{'Id': data.category}] if data.category else None,
|
123
|
+
PRODUCT_CATEGORY_NAME_FIELD: [data.category_name] if data.category_name else None,
|
124
|
+
PRODUCT_CATEGORY_ID_FIELD: [{"Id": data.category}] if data.category else None,
|
116
125
|
PRODUCT_IMAGE_FIELD: preview_url,
|
117
126
|
PRODUCT_DISCOUNT_PRICE_FIELD: data.final_price
|
118
127
|
}
|
119
128
|
|
129
|
+
|
120
130
|
def dump_product_data_with_check(data: ProductModel, data_check: dict) -> dict:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
extra_data = {
|
131
|
+
"""
|
132
|
+
Converts a ProductModel into a dictionary and validates categories.
|
133
|
+
"""
|
134
|
+
preview_url = [
|
135
|
+
{"url": image_url, "title": f"{secrets.token_hex(6)}.jpeg", "mimetype": "image/jpeg"}
|
136
|
+
for image_url in data.preview_url
|
137
|
+
] if data.preview_url else []
|
138
|
+
|
139
|
+
extra_data = {attr.name: attr.description for attr in data.extra_attributes}
|
130
140
|
|
131
141
|
product_data = {
|
132
142
|
PRODUCT_ID_FIELD: data.id,
|
@@ -136,54 +146,53 @@ def dump_product_data_with_check(data: ProductModel, data_check: dict) -> dict:
|
|
136
146
|
PRODUCT_PRICE_FIELD: data.price,
|
137
147
|
PRODUCT_CURRENCY_FIELD: data.currency,
|
138
148
|
PRODUCT_STOCK_FIELD: data.stock_qty,
|
139
|
-
PRODUCT_CATEGORY_NAME_FIELD:[data.
|
140
|
-
PRODUCT_CATEGORY_ID_FIELD: [{
|
149
|
+
PRODUCT_CATEGORY_NAME_FIELD: [data.product_properties] if data.product_properties else None,
|
150
|
+
PRODUCT_CATEGORY_ID_FIELD: [{"Id": data_check[item]} for item in data.product_properties] if data.product_properties else None,
|
141
151
|
PRODUCT_IMAGE_FIELD: preview_url,
|
142
152
|
PRODUCT_CHECKOUT_MODE: NOCODB_CHECKOUT_MODES,
|
143
153
|
PRODUCT_DISCOUNT_PRICE_FIELD: data.final_price,
|
144
|
-
|
145
|
-
|
146
|
-
if
|
154
|
+
}
|
155
|
+
|
156
|
+
if extra_data:
|
147
157
|
product_data.update(extra_data)
|
148
158
|
return product_data
|
149
159
|
|
150
160
|
|
151
161
|
async def parse_product_data(data: dict) -> ProductModel:
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
162
|
+
"""
|
163
|
+
Parses raw product data into a ProductModel.
|
164
|
+
"""
|
165
|
+
preview_url = [image['url'] for image in data.get(PRODUCT_IMAGE_FIELD, [])]
|
166
|
+
|
167
|
+
# Dynamically add extra attributes
|
168
|
+
extra_attributes = [
|
169
|
+
ExtraAttribute(name=key, description=str(value))
|
170
|
+
for key, value in data.items()
|
171
|
+
if key not in {
|
172
|
+
ID_FIELD, PRODUCT_NAME_FIELD, PRODUCT_DESCRIPTION_FIELD, PRODUCT_PRICE_FIELD, PRODUCT_CURRENCY_FIELD,
|
173
|
+
PRODUCT_STOCK_FIELD, PRODUCT_CATEGORY_ID_FIELD, PRODUCT_IMAGE_FIELD, PRODUCT_CATEGORY_NAME_FIELD,
|
174
|
+
PRODUCT_DISCOUNT_PRICE_FIELD, PRODUCT_CATEGORY_ID_LOOKUP_FIELD, PRODUCT_REQUIRED_OPTIONS_FIELD,
|
175
|
+
PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD, PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD,
|
176
|
+
PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD, "UpdatedAt", "CreatedAt"
|
177
|
+
} and value is not None and isinstance(value, (str, int, float))
|
178
|
+
]
|
165
179
|
|
166
180
|
product = ProductModel(
|
167
|
-
id=str(data
|
181
|
+
id=str(data.get(ID_FIELD, data.get(NEW_ID_FIELD, ""))),
|
168
182
|
external_id=data.get(PRODUCT_EXTERNAL_ID, ""),
|
169
183
|
name=data.get(PRODUCT_NAME_FIELD, ""),
|
170
|
-
description=data.get(PRODUCT_DESCRIPTION_FIELD, "")
|
184
|
+
description=data.get(PRODUCT_DESCRIPTION_FIELD, ""),
|
171
185
|
price=data.get(PRODUCT_PRICE_FIELD, 0.0),
|
172
|
-
|
186
|
+
final_price=data.get(PRODUCT_DISCOUNT_PRICE_FIELD, 0.0),
|
187
|
+
currency=data.get(PRODUCT_CURRENCY_FIELD, "RUB"),
|
173
188
|
stock_qty=data.get(PRODUCT_STOCK_FIELD, 0),
|
174
189
|
preview_url=preview_url,
|
175
|
-
category_name=data.get(PRODUCT_CATEGORY_NAME_FIELD, [])
|
176
|
-
category=data.get(PRODUCT_CATEGORY_ID_LOOKUP_FIELD, [])
|
177
|
-
# category=[],
|
190
|
+
category_name=data.get(PRODUCT_CATEGORY_NAME_FIELD, []),
|
191
|
+
category=data.get(PRODUCT_CATEGORY_ID_LOOKUP_FIELD, []),
|
178
192
|
extra_attributes=extra_attributes,
|
179
|
-
extra_option_choice_required=
|
180
|
-
|
193
|
+
extra_option_choice_required=bool(data.get(PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD, [])),
|
194
|
+
|
195
|
+
metadata=data
|
181
196
|
)
|
182
|
-
if data.get(PRODUCT_DISCOUNT_PRICE_FIELD, data.get(PRODUCT_PRICE_FIELD, 0.0)):
|
183
|
-
product.final_price = data.get(PRODUCT_DISCOUNT_PRICE_FIELD, data.get(PRODUCT_PRICE_FIELD, 0.0))
|
184
197
|
|
185
198
|
return product
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
import hashlib
|
3
|
+
from aiocache import cached
|
4
|
+
from loguru import logger
|
5
|
+
from models.products import ProductModel
|
6
|
+
from tgshops_integrations.nocodb_connector.client import custom_key_builder, NocodbClient
|
7
|
+
from tgshops_integrations.nocodb_connector.model_mapping import (
|
8
|
+
dump_product_data,
|
9
|
+
dump_product_data_with_check,
|
10
|
+
get_pagination_info,
|
11
|
+
parse_product_data,
|
12
|
+
ID_FIELD,
|
13
|
+
PRODUCT_CATEGORY_ID_LOOKUP_FIELD,
|
14
|
+
PRODUCT_NAME_FIELD,
|
15
|
+
PRODUCT_PRICE_FIELD,
|
16
|
+
PRODUCT_STOCK_FIELD,
|
17
|
+
PRODUCT_EXTERNAL_ID,
|
18
|
+
PRODUCT_IMAGES_LOOKUP_FIELD,
|
19
|
+
)
|
20
|
+
|
21
|
+
class ProductManager(NocodbClient):
|
22
|
+
def __init__(self, table_id=None, logging=False, NOCODB_HOST=None, NOCODB_API_KEY=None, SOURCE=None):
|
23
|
+
super().__init__(NOCODB_HOST=NOCODB_HOST, NOCODB_API_KEY=NOCODB_API_KEY, SOURCE=SOURCE)
|
24
|
+
self.logging = logging
|
25
|
+
self.required_fields = [PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD]
|
26
|
+
self.projection = []
|
27
|
+
self.products_table = table_id
|
28
|
+
self.columns = []
|
29
|
+
|
30
|
+
def hash_product(self, product: ProductModel, special_attributes=False) -> str:
|
31
|
+
"""
|
32
|
+
Generates a hash of the product for comparison.
|
33
|
+
"""
|
34
|
+
if special_attributes:
|
35
|
+
hash_string = ''.join(attr.description for attr in product.extra_attributes if attr.name.endswith('*'))
|
36
|
+
else:
|
37
|
+
hash_string = f"{product.external_id}{product.price}{sorted(product.product_properties)}{product.name}{product.description}"
|
38
|
+
|
39
|
+
return hashlib.sha256(hash_string.encode()).hexdigest()
|
40
|
+
|
41
|
+
@cached(ttl=30, key_builder=custom_key_builder)
|
42
|
+
async def get_products(self) -> List[ProductModel]:
|
43
|
+
"""
|
44
|
+
Fetches all products from the table.
|
45
|
+
"""
|
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
|
+
async def get_products_v2(self, offset: int, limit: int) -> List[ProductModel]:
|
50
|
+
"""
|
51
|
+
Fetches paginated products.
|
52
|
+
"""
|
53
|
+
response = await self.get_table_records_v2(
|
54
|
+
table_name=self.products_table,
|
55
|
+
required_fields=self.required_fields,
|
56
|
+
projection=self.projection,
|
57
|
+
offset=offset,
|
58
|
+
limit=limit,
|
59
|
+
)
|
60
|
+
return [await parse_product_data(record) for record in response['list']]
|
61
|
+
|
62
|
+
@cached(ttl=180, key_builder=custom_key_builder)
|
63
|
+
async def search_products(self, search_string: str, limit: int) -> List[ProductModel]:
|
64
|
+
"""
|
65
|
+
Searches for products with names containing the search string.
|
66
|
+
"""
|
67
|
+
records = await self.get_table_records(
|
68
|
+
table_name=self.products_table,
|
69
|
+
required_fields=self.required_fields,
|
70
|
+
projection=self.projection,
|
71
|
+
extra_where=f"({PRODUCT_NAME_FIELD},like,%{search_string}%)",
|
72
|
+
limit=limit,
|
73
|
+
)
|
74
|
+
return [parse_product_data(record) for record in records]
|
75
|
+
|
76
|
+
@cached(ttl=60, key_builder=custom_key_builder)
|
77
|
+
async def get_product(self, product_id: str) -> ProductModel:
|
78
|
+
"""
|
79
|
+
Fetches a single product by its ID.
|
80
|
+
"""
|
81
|
+
record = await self.get_table_record(self.products_table, product_id)
|
82
|
+
return parse_product_data(record)
|
83
|
+
|
84
|
+
@cached(ttl=60, key_builder=custom_key_builder)
|
85
|
+
async def get_product_in_category(self, category_id: Optional[str] = None) -> List[ProductModel]:
|
86
|
+
"""
|
87
|
+
Fetches products within a specific category or all products if no category is specified.
|
88
|
+
"""
|
89
|
+
extra_where = None
|
90
|
+
if category_id:
|
91
|
+
extra_where = f"({PRODUCT_STOCK_FIELD},gt,0)~and({PRODUCT_CATEGORY_ID_LOOKUP_FIELD},eq,{category_id})"
|
92
|
+
|
93
|
+
records = await self.get_table_records(
|
94
|
+
table_name=self.products_table,
|
95
|
+
required_fields=self.required_fields,
|
96
|
+
projection=self.projection,
|
97
|
+
extra_where=extra_where,
|
98
|
+
)
|
99
|
+
return [parse_product_data(record) for record in records]
|
100
|
+
|
101
|
+
async def update_product(self, product: ProductModel):
|
102
|
+
"""
|
103
|
+
Updates an existing product in the table.
|
104
|
+
"""
|
105
|
+
data = dump_product_data_with_check(data=product, data_check=self.category_manager.categories)
|
106
|
+
await self.update_table_record(
|
107
|
+
table_name=self.products_table,
|
108
|
+
record_id=product.id,
|
109
|
+
updated_data=data,
|
110
|
+
)
|
111
|
+
logger.info(f"Updated product {product.external_id}")
|
112
|
+
|
113
|
+
async def create_product(self, checked_data: dict, product: ProductModel) -> ProductModel:
|
114
|
+
"""
|
115
|
+
Creates a new product in the table.
|
116
|
+
"""
|
117
|
+
external_id = checked_data.pop("ID")
|
118
|
+
metadata = await self.get_table_meta(self.products_table)
|
119
|
+
images_column = next(
|
120
|
+
column["id"]
|
121
|
+
for column in metadata["columns"]
|
122
|
+
if column["column_name"] == "Images"
|
123
|
+
)
|
124
|
+
|
125
|
+
checked_data[PRODUCT_IMAGES_LOOKUP_FIELD] = [image['title'] for image in checked_data['Images']]
|
126
|
+
|
127
|
+
for num, item in enumerate(checked_data['Images']):
|
128
|
+
item['url'] = await self.save_image_to_nocodb(
|
129
|
+
source_column_id=self.SOURCE,
|
130
|
+
image_url=item['url'],
|
131
|
+
image_name=item['title'],
|
132
|
+
product_table_name=self.products_table,
|
133
|
+
images_column_id=images_column,
|
134
|
+
)
|
135
|
+
|
136
|
+
record = await self.create_table_record(table_name=self.products_table, record=checked_data)
|
137
|
+
logger.info(f"Created product {external_id}")
|
138
|
+
return record
|
139
|
+
|
140
|
+
async def get_all_products(self) -> List[ProductModel]:
|
141
|
+
"""
|
142
|
+
Fetches all products in paginated portions.
|
143
|
+
"""
|
144
|
+
all_products = []
|
145
|
+
portion = 200
|
146
|
+
for i in range(10): # Limit to 10 iterations
|
147
|
+
products_portion = await self.get_products_v2(offset=i * portion, limit=portion)
|
148
|
+
all_products.extend(products_portion)
|
149
|
+
if len(products_portion) < portion:
|
150
|
+
break
|
151
|
+
return all_products
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: tgshops-integrations
|
3
|
-
Version:
|
3
|
+
Version: 3.1
|
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
tgshops_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
tgshops_integrations/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
tgshops_integrations/middlewares/gateway.py,sha256=-YQi3-62k6gkxJLx27FESe6wlRrD4_Ltqk8wZJ8ExYg,6247
|
4
|
+
tgshops_integrations/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
tgshops_integrations/models/categories.py,sha256=EG6C8g5dOfXB2MH-vtqH13aqB7_VyOobY2FHpDb-fsY,977
|
6
|
+
tgshops_integrations/models/products.py,sha256=zkRqXLU-tAReDF5R9j3q58le686dw3PapxcFOGUpk7Q,1376
|
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
|
+
tgshops_integrations/nocodb_connector/categories_management.py,sha256=dvPXcrgruJyuWFT7Ho-8YRcjJxhR3Hcw0hJiVKQgcyA,6603
|
10
|
+
tgshops_integrations/nocodb_connector/client.py,sha256=U2rNozjluWVul-VZroQW-8b2j6nEtKrhOkFMz1L8KmI,12022
|
11
|
+
tgshops_integrations/nocodb_connector/model_mapping.py,sha256=ixv-uT1Pmt1szZCQ5pGsMbwRvmnHZmn0NqG77nxZkDM,8551
|
12
|
+
tgshops_integrations/nocodb_connector/products.py,sha256=kAp7lUaRO7CkU_SumbIdLOJf38SmDBEyBBZCYyyyOFM,9313
|
13
|
+
tgshops_integrations/nocodb_connector/products_management.py,sha256=t_ZDyIAPTe_6UrxLuJ32Uhlk8ssErP6Eo0n-TIZfEko,6046
|
14
|
+
tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
|
15
|
+
tgshops_integrations-3.1.dist-info/METADATA,sha256=oBAxZoo2AU7A_BKg0kfhnzZMeYli2Du1v901AI_M0Vk,2774
|
16
|
+
tgshops_integrations-3.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
17
|
+
tgshops_integrations-3.1.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
|
18
|
+
tgshops_integrations-3.1.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
tgshops_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
tgshops_integrations/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
tgshops_integrations/middlewares/gateway.py,sha256=Fj7pnFcVxKmrFU4dRN5RI23lM4gG61In7X2fybudS3o,6368
|
4
|
-
tgshops_integrations/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
tgshops_integrations/models/categories.py,sha256=EG6C8g5dOfXB2MH-vtqH13aqB7_VyOobY2FHpDb-fsY,977
|
6
|
-
tgshops_integrations/models/products.py,sha256=i0vP_eJMVCB-W25BCoodIB0AhsMTqYiDO48N-B6Ueo0,1379
|
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
|
-
tgshops_integrations/nocodb_connector/client.py,sha256=RD8UDS4AXrN1GFmjakZsdTGbP_u2bsoxfopq7UV6MT0,11725
|
10
|
-
tgshops_integrations/nocodb_connector/model_mapping.py,sha256=nsul7OjUHgGhpV-iwwaAvb9SnBlwj4evHDcXSd-v1zk,8919
|
11
|
-
tgshops_integrations/nocodb_connector/products.py,sha256=kAp7lUaRO7CkU_SumbIdLOJf38SmDBEyBBZCYyyyOFM,9313
|
12
|
-
tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
|
13
|
-
tgshops_integrations-2.4.dist-info/METADATA,sha256=aDJyso77_TGjthlxfi-BIduQkt1643LBK9fTB0KWCIg,2774
|
14
|
-
tgshops_integrations-2.4.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
15
|
-
tgshops_integrations-2.4.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
|
16
|
-
tgshops_integrations-2.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|