bella-openapi 1.0.2.1__py3-none-any.whl → 1.0.2.3__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.
bella_openapi/__init__.py CHANGED
@@ -3,6 +3,8 @@ from .log import operation_log, submit_log
3
3
  from .openapi_contexvar import trace_id_context, caller_id_context, request_url_context
4
4
  from .auth_billing import ErrorInfo, async_authenticate_decorator_args, authenticate_user, print_context, \
5
5
  get_context, set_context, clean_context, report
6
+ from .domtree import StandardDomTree, StandardNode
7
+
6
8
  __all__ = ["validate_token", "operation_log",
7
9
  "support_model",
8
10
  "account_balance_enough",
@@ -18,5 +20,7 @@ __all__ = ["validate_token", "operation_log",
18
20
  "get_context",
19
21
  "set_context",
20
22
  "clean_context",
21
- "report"
23
+ "report",
24
+ "StandardDomTree",
25
+ "StandardNode"
22
26
  ]
@@ -0,0 +1,3 @@
1
+ from .standard_domtree import StandardDomTree, StandardNode
2
+
3
+ __all__ = ["StandardDomTree", "StandardNode"]
@@ -0,0 +1,492 @@
1
+ """
2
+ StandardDomTree - 新的DOM树协议定义
3
+
4
+ 这个模块定义了新的标准化DOM树结构,用于统一文档解析结果的格式。
5
+ 目前解析过程仍基于原有的DOM树结构,然后通过转换方法将其转换为标准格式。
6
+
7
+ 注意:当前解析器仍使用原有的DOM树结构,后续计划将直接解析为StandardDomTree格式。
8
+
9
+ """
10
+
11
+ from typing import List, Optional, Literal, Any, Union
12
+ from pydantic import BaseModel, Field
13
+
14
+ from bella_openapi.domtree.utils import count_tokens
15
+
16
+ # 布局类型映射表:将旧解析器的布局类型映射到新的标准类型
17
+ # 注意:这个映射表是临时的,后续计划统一布局类型定义
18
+ layout_type_mapping = {
19
+ "Catalog": "Catalog",
20
+ "Title": "Title",
21
+ "List": "ListItem",
22
+ "Formula": "Formula",
23
+ "Code": "Code",
24
+ "Text": "Text",
25
+
26
+ "Figure": "Figure",
27
+ "FigureName": "FigureName",
28
+ "FigureNote": "Text", # 目前实际解析出来没有
29
+
30
+ "Table": "Table",
31
+ "TableName": "TableName",
32
+ "TableNote": "Text", # 目前实际解析出来没有
33
+ }
34
+
35
+
36
+ class SourceFile(BaseModel):
37
+ id: str # 文件ID,唯一标识符,类型为string
38
+ name: str # 文件名,文档的名称,类型为string
39
+ type: Optional[str] # 文件类型,例如:pdf、docx等
40
+ mime_type: Optional[str] # MIME类型,例如:application/pdf、application/msword等
41
+
42
+
43
+ class StandardPosition(BaseModel):
44
+ bbox: List[float] # 文档中的矩形坐标信息,例如:[90.1,263.8,101.8,274.3]
45
+ page: int # 页码
46
+
47
+
48
+ class StandardImage(BaseModel):
49
+ type: Literal["image_url", "image_base64", "image_file"] # 图片类型约束
50
+ url: Optional[str] = None # 链接地址
51
+ base64: Optional[str] = None # 图片base64编码
52
+ file_id: Optional[str] = None # 上传到file-api的文件ID
53
+
54
+
55
+ class Cell(BaseModel):
56
+ """表格单元格"""
57
+ path: Optional[List[Union[int, List[int]]]] = Field(default_factory=list) # 单元格路径
58
+ text: Optional[str] = None # 文本内容
59
+ nodes: Optional[List['StandardNode']] = Field(default_factory=list)
60
+
61
+
62
+ class StandardRow(BaseModel):
63
+ """表格行"""
64
+ cells: List[Cell] = Field(default_factory=list) # 单元格列表
65
+
66
+
67
+ class StandardBaseElement(BaseModel):
68
+ type: str # 类型,例如["Text","Title","List","Catalog","Table","Figure","Formula","Code","ListItem"]
69
+ positions: List[StandardPosition] # 位置信息,可能跨页所以是个数组
70
+
71
+
72
+ class StandardElement(StandardBaseElement):
73
+ text: Optional[str] = None # 文本信息,图片ocr的文字
74
+
75
+
76
+ class StandardTableElement(StandardBaseElement):
77
+ name: Optional[str] = None # 如果类型是Table、Figure为其名字
78
+ description: Optional[str] = None # 如果类型是Table、Figure为其描述
79
+ rows: List[StandardRow] = Field(default_factory=list) # 表格行
80
+
81
+
82
+ class StandardImageElement(StandardElement):
83
+ name: Optional[str] = None # 如果类型是Table、Figure为其名字
84
+ description: Optional[str] = None # 如果类型是Table、Figure为其描述
85
+ image: Optional[StandardImage] = None # 图片信息
86
+
87
+
88
+ class StandardNode(BaseModel):
89
+ source_file: Optional[SourceFile] = None # 文档来源,表示文档的来源信息
90
+ summary: Optional[str] = None # 摘要,文档的简要概述
91
+ tokens: Optional[int] = None # token预估数量,文档中的token数量估计
92
+ path: Optional[List[int]] = Field(default_factory=list) # 编号的层级信息,例如:1.2.1
93
+ element: Optional[Union[StandardElement, StandardImageElement, StandardTableElement]] = None # 元素信息,当前节点的元素详情
94
+ children: Optional[List["StandardNode"]] = Field(default_factory=list) # 子节点信息,当前节点的所有子节点
95
+
96
+
97
+ class StandardDomTree(BaseModel):
98
+ root: StandardNode # 根节点
99
+
100
+ def to_markdown(self) -> str:
101
+ """
102
+ 将StandardDomTree转换为Markdown格式的字符串
103
+
104
+ Returns:
105
+ str: Markdown格式的字符串
106
+ """
107
+ markdown_res = ""
108
+
109
+ def _generate_markdown(node: StandardNode, level: int, low_than_text: int = 0) -> str:
110
+ nonlocal markdown_res
111
+ child_low_than_text = 0
112
+
113
+ if node.element:
114
+ # 根据不同的type生成Markdown
115
+ if node.element.type == "Figure" and isinstance(node.element, StandardImageElement):
116
+ # 添加图片名称
117
+ if node.element.name:
118
+ markdown_res += f"**{node.element.name}**\n\n"
119
+ if node.element.image and node.element.image.url:
120
+ markdown_res += f"![Figure]({node.element.image.url})\n\n"
121
+ if node.element.text:
122
+ md_ocr_res = self._convert_to_markdown_quote(node.element.text)
123
+ markdown_res += f"{md_ocr_res}\n\n"
124
+ # 添加图片描述
125
+ if node.element.description:
126
+ markdown_res += f"*{node.element.description}*\n\n"
127
+
128
+ elif node.element.type == "Table" and isinstance(node.element, StandardTableElement):
129
+ # 添加表格名称
130
+ if node.element.name:
131
+ markdown_res += f"**{node.element.name}**\n\n"
132
+ table_md = self._list_to_html_table(node.element.rows)
133
+ markdown_res += f"{table_md}\n\n"
134
+ # 添加表格描述
135
+ if node.element.description:
136
+ markdown_res += f"*{node.element.description}*\n\n"
137
+
138
+ elif (level <= 6 # 标题必须小于等于6级
139
+ and (node.element.type in ["Title"] # 认定为Title 或者 父节点非text的ListItem
140
+ or (node.element.type in ["ListItem"] and not low_than_text))):
141
+ # Title只能识别6级,大于6级的按普通文本处理
142
+ markdown_res += '#' * level + f" {node.element.text}\n\n"
143
+ elif node.element.type in ["Title"]:
144
+ markdown_res += f"{node.element.text}\n\n"
145
+ elif node.element.type in ["Text"]:
146
+ markdown_res += f"{node.element.text}\n\n"
147
+ child_low_than_text = low_than_text + 1 # Text节点的子节点标记
148
+ elif node.element.type in ["ListItem"]:
149
+ markdown_res += '\t' * (low_than_text - 1) + f"- {node.element.text}\n\n"
150
+ # Formula、Catalog、Code等元素的处理
151
+ else:
152
+ markdown_res += f"{node.element.text}\n\n"
153
+
154
+ for child in node.children:
155
+ _generate_markdown(child, level + 1, child_low_than_text)
156
+
157
+ # 从根节点开始生成,跳过根节点本身
158
+ for child in self.root.children:
159
+ _generate_markdown(child, 1, 0)
160
+
161
+ return markdown_res
162
+
163
+ def _convert_to_markdown_quote(self, text: str) -> str:
164
+ """将文本转换为markdown引用格式"""
165
+ if not text:
166
+ return ""
167
+ lines = text.split('\n')
168
+ quoted_lines = ['> ' + line for line in lines]
169
+ return '\n'.join(quoted_lines)
170
+
171
+ def _list_to_html_table(self, rows: List[StandardRow]) -> str:
172
+ """将表格行转换为HTML表格"""
173
+ if not rows:
174
+ return ""
175
+
176
+ html_text = "<table>"
177
+ for row in rows:
178
+ html_text += "<tr>"
179
+ for cell in row.cells:
180
+ # 从path中提取rowspan和colspan信息
181
+ if len(cell.path) >= 4:
182
+ start_row, end_row, start_col, end_col = cell.path[:4]
183
+ rowspan = end_row - start_row + 1
184
+ colspan = end_col - start_col + 1
185
+ else:
186
+ rowspan = colspan = 1
187
+
188
+ cell_text = cell.text or ""
189
+ html_text += f"<td rowspan='{rowspan}' colspan='{colspan}'>{cell_text}</td>"
190
+ html_text += "</tr>"
191
+ html_text += "</table>"
192
+ return html_text
193
+
194
+ @classmethod
195
+ def from_domtree_dict(cls, domtree: dict, file_info):
196
+ """
197
+ 将旧的DOM树字典格式转换为新的StandardDomTree格式
198
+
199
+ 注意:这是临时的转换方法,用于兼容现有的解析器输出。
200
+ 后续计划将解析器直接输出StandardDomTree格式,避免这个转换步骤。
201
+
202
+ Args:
203
+ domtree: 源DOM树字典对象(旧格式)
204
+ file_info: 文件信息字典,包含文件ID、文件名等信息
205
+ Returns:
206
+ StandardDomTree: 转换后的标准化DOM树对象
207
+ """
208
+ # 创建 SourceFile 对象
209
+ source_file = None
210
+ if file_info:
211
+ source_file = SourceFile(
212
+ id=file_info['id'],
213
+ name=file_info['filename'],
214
+ type=file_info.get('type'),
215
+ mime_type=file_info.get('mime_type')
216
+ )
217
+
218
+ # 转换根节点,构建树结构(不计算path)
219
+ standard_root = cls._from_domtree_nodes(domtree.get('root'), source_file)
220
+
221
+ return cls(root=standard_root)
222
+
223
+ @classmethod
224
+ def _from_domtree_nodes(cls, node: dict, source_file: SourceFile) -> StandardNode:
225
+ """
226
+ 处理所有节点
227
+ """
228
+ # 根节点,创建一个空的 StandardNode
229
+ standard_root_node = StandardNode(
230
+ source_file=source_file,
231
+ summary="",
232
+ tokens=0, # 先设置为 0,后面再计算
233
+ path=None,
234
+ element=None,
235
+ children=[]
236
+ )
237
+
238
+ # 递归处理子节点
239
+ for child in node.get('child', []):
240
+ standard_child = cls._from_domtree_node_to_base_info(child) # 不传递path参数
241
+ if standard_child: # 确保子节点不为 None
242
+ standard_root_node.children.append(standard_child)
243
+
244
+ # 处理 FigureName 和 TableName 节点(合并节点)
245
+ cls._process_special_nodes(standard_root_node)
246
+
247
+ # 计算所有节点的path
248
+ cls._calculate_paths(standard_root_node)
249
+
250
+ # 计算 token 数量:子节点 token 数量之和
251
+ tokens = 0
252
+ for child in standard_root_node.children:
253
+ tokens += child.tokens
254
+
255
+ # 设置 token 数量
256
+ standard_root_node.tokens = tokens
257
+
258
+ return standard_root_node
259
+
260
+ @classmethod
261
+ def _calculate_paths(cls, node: StandardNode, parent_path: List[int] = None):
262
+ """
263
+ 计算所有节点的path
264
+
265
+ Args:
266
+ node: 当前处理的节点
267
+ parent_path: 父节点的path
268
+ """
269
+ if parent_path is None:
270
+ parent_path = []
271
+
272
+ # 为子节点计算path
273
+ for i, child in enumerate(node.children, start=1):
274
+ child_path = parent_path + [i]
275
+ child.path = child_path
276
+
277
+ # 递归计算子节点的path
278
+ cls._calculate_paths(child, child_path)
279
+
280
+ @classmethod
281
+ def _process_special_nodes(cls, node: StandardNode):
282
+ """
283
+ 处理特殊节点(FigureName 和 TableName)
284
+
285
+ 注意:这是临时的处理逻辑,用于处理旧解析器输出的特殊节点类型。
286
+ 后续计划将解析器直接输出正确的节点结构,避免这种后处理。
287
+
288
+ Args:
289
+ node: 当前处理的节点
290
+ """
291
+ if not node or not node.children:
292
+ return
293
+
294
+ # 创建一个新的子节点列表,用于存储处理后的子节点
295
+ new_children = []
296
+ i = 0
297
+
298
+ while i < len(node.children):
299
+ current = node.children[i]
300
+
301
+ # 检查当前节点是否为 FigureName 或 TableName
302
+ if current.element and current.element.type in ['FigureName', 'TableName']:
303
+ target_type = 'Figure' if current.element.type == 'FigureName' else 'Table'
304
+ merged = False
305
+
306
+ # 检查前一个节点
307
+ if i > 0:
308
+ prev_sibling = node.children[i - 1]
309
+ if prev_sibling.element and prev_sibling.element.type == target_type:
310
+ # 找到对应类型的前一个兄弟节点,合并节点
311
+ if cls._merge_nodes(prev_sibling, current, target_type):
312
+ merged = True
313
+
314
+ # 如果没有与前一个节点合并,检查后一个节点
315
+ if not merged and i < len(node.children) - 1:
316
+ next_sibling = node.children[i + 1]
317
+ if next_sibling.element and next_sibling.element.type == target_type:
318
+ # 找到对应类型的后一个兄弟节点,合并节点
319
+ if cls._merge_nodes(next_sibling, current, target_type):
320
+ merged = True
321
+
322
+ # 如果没有找到对应类型的兄弟节点,将当前节点类型改为 Text
323
+ if not merged:
324
+ current.element.type = 'text'
325
+ new_children.append(current)
326
+ else:
327
+ new_children.append(current)
328
+
329
+ i += 1
330
+
331
+ # 更新子节点列表
332
+ node.children = new_children
333
+
334
+ # 递归处理子节点
335
+ for child in node.children:
336
+ cls._process_special_nodes(child)
337
+
338
+ @classmethod
339
+ def _merge_nodes(cls, target_node: StandardNode, source_node: StandardNode, node_type: str) -> bool:
340
+ """
341
+ 合并两个节点,将source_node的信息合并到target_node中
342
+
343
+ Args:
344
+ target_node: 目标节点(Figure或Table节点)
345
+ source_node: 源节点(FigureName或TableName节点)
346
+ node_type: 节点类型('Figure'或'Table')
347
+
348
+ Returns:
349
+ bool: 是否成功合并
350
+ """
351
+ if node_type == 'Figure' and isinstance(target_node.element, StandardImageElement):
352
+ # 将 FigureName 的文本作为 Figure 的 name
353
+ target_node.element.name = source_node.element.text
354
+ # 更新 tokens 计数
355
+ target_node.tokens += source_node.tokens
356
+ # 将 FigureName 的位置添加到 Figure 中
357
+ target_node.element.positions += source_node.element.positions
358
+ return True
359
+ elif node_type == 'Table' and isinstance(target_node.element, StandardTableElement):
360
+ # 将 TableName 的文本作为 Table 的 name
361
+ target_node.element.name = source_node.element.text
362
+ # 更新 tokens 计数
363
+ target_node.tokens += source_node.tokens
364
+ # 将 Table 的位置添加到 Figure 中
365
+ target_node.element.positions += source_node.element.positions
366
+ return True
367
+ return False
368
+
369
+ @classmethod
370
+ def _from_domtree_node_to_base_info(cls, node: dict) -> Optional[StandardNode]:
371
+ """
372
+ 将单个旧格式Node转换为StandardNode
373
+
374
+ 注意:这是临时的转换方法,用于处理旧解析器输出的节点格式。
375
+ 后续计划将解析器直接输出StandardNode格式,避免这种转换。
376
+
377
+ Args:
378
+ node: 源Node对象(旧格式字典)
379
+ Returns:
380
+ StandardNode: 转换后的标准化节点对象
381
+ """
382
+ if not node:
383
+ return
384
+
385
+ element = node['element']
386
+
387
+ text = ""
388
+ # 映射的类型
389
+ element_type = layout_type_mapping.get(element['layout_type'], "Text") # 默认类型为 text
390
+ positions = [StandardPosition(bbox=element['bbox'], page=element['page_num'][0])] # 位置列表,目前page_num元素个数只会是1个
391
+
392
+ standard_node = None
393
+ if element_type == "Figure":
394
+ # 处理图片信息
395
+ image = None
396
+ text = element.get('image_ocr_result', '')
397
+ if 'image_link' in element and element['image_link']:
398
+ image = StandardImage(
399
+ type="image_url",
400
+ url=element['image_link']
401
+ )
402
+
403
+ # 创建 StandardNode
404
+ standard_node = StandardNode(
405
+ summary="",
406
+ tokens=0, # 先设置为 0,后面再计算
407
+ path=[], # 初始化为空列表,后续再计算
408
+ element=StandardImageElement(
409
+ type=element_type,
410
+ positions=positions,
411
+ name="",
412
+ description="",
413
+ text=text,
414
+ image=image,
415
+ ),
416
+ children=[]
417
+ )
418
+ elif element_type == "Table":
419
+ rows = []
420
+ cell_texts = [] # 收集所有单元格的文本,用于计算 token 数量
421
+ if 'rows' in element:
422
+ for row_data in element['rows']:
423
+ cells = []
424
+ if 'cells' in row_data:
425
+ for cell_data in row_data['cells']:
426
+ cell_text = cell_data.get('text', '')
427
+ cell_texts.append(cell_text)
428
+ # 不计算cell_path,后续再计算
429
+ cell = Cell(
430
+ path=[cell_data['start_row'], cell_data['end_row'], cell_data['start_col'],
431
+ cell_data['end_col']],
432
+ text=cell_text,
433
+ # 目前只会有一个元素,且是Text类型,Path重新从头编号,相对cell是root
434
+ nodes=[StandardNode(summary="", tokens=count_tokens(cell_text), path=[1], children=[],
435
+ element=StandardElement(
436
+ type='Text',
437
+ positions=[],
438
+ text=cell_text
439
+ )
440
+ )]
441
+ )
442
+ cells.append(cell)
443
+ # 使用 StandardRow 的构造函数创建行
444
+ row = StandardRow(cells=cells)
445
+ rows.append(row)
446
+
447
+ # 将所有单元格的文本合并,用于计算 token 数量
448
+ text = " ".join(cell_texts)
449
+
450
+ standard_node = StandardNode(
451
+ summary="",
452
+ tokens=0, # 先设置为 0,后面再计算
453
+ path=[], # 初始化为空列表,后续再计算
454
+ element=StandardTableElement(
455
+ type=element_type,
456
+ positions=positions,
457
+ name="",
458
+ description="",
459
+ rows=rows
460
+ ),
461
+ children=[]
462
+ )
463
+ else:
464
+ text = element.get('text', '')
465
+ standard_node = StandardNode(
466
+ summary="",
467
+ tokens=0, # 先设置为 0,后面再计算
468
+ path=[], # 初始化为空列表,后续再计算
469
+ element=StandardElement(
470
+ type=element_type,
471
+ positions=positions,
472
+ text=text
473
+ ),
474
+ children=[]
475
+ )
476
+
477
+ # 递归处理子节点
478
+ if 'child' in node:
479
+ for child in node['child']:
480
+ standard_child = cls._from_domtree_node_to_base_info(child)
481
+ if standard_child: # 确保子节点不为 None
482
+ standard_node.children.append(standard_child)
483
+
484
+ # 计算 token 数量:自身 text 的 token 数量 + 子节点 token 数量
485
+ tokens = count_tokens(text)
486
+ for child in standard_node.children:
487
+ tokens += child.tokens
488
+
489
+ # 设置 token 数量
490
+ standard_node.tokens = tokens
491
+
492
+ return standard_node
@@ -0,0 +1,12 @@
1
+ import tiktoken
2
+
3
+ # 自研模型均用gpt-4计算(可能有误差,可忽略)
4
+ def count_tokens(text: str, model: str = "gpt-4") -> int:
5
+ if not text:
6
+ return 0
7
+ encoding = tiktoken.encoding_for_model(model)
8
+ tokens = encoding.encode(text)
9
+ # 计算标记列表的长度,即标记的数量
10
+ token_count = len(tokens)
11
+ # 返回标记的数量
12
+ return token_count
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bella-openapi
3
- Version: 1.0.2.1
3
+ Version: 1.0.2.3
4
4
  Summary: client for openapi service.
5
5
  Home-page:
6
6
  Author: ['tangxiaolong', 'fanqiangwei', 'zhangxiaojia', 'liumin', 'wangyukun']
@@ -11,6 +11,7 @@ Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: httpx<=0.26.0,>=0.10.0
13
13
  Requires-Dist: Werkzeug==3.0.1
14
+ Requires-Dist: tiktoken>=0.5.0
14
15
  Dynamic: author
15
16
  Dynamic: classifier
16
17
  Dynamic: description
@@ -1,4 +1,4 @@
1
- bella_openapi/__init__.py,sha256=cTyozxQ6fo0FSPwfTJWn7DD-CyPtfKYCMuSFGqddnZA,897
1
+ bella_openapi/__init__.py,sha256=NPsPxysa625xRIKgL6je4q1ZSJfnVar4Mb-2RljNwfs,1006
2
2
  bella_openapi/auth_billing.py,sha256=Hn0KS8GuG48etnvnd1Faej4IfFXD3tjzalUzDnpZh7Q,3520
3
3
  bella_openapi/authorize.py,sha256=cO6J-wx9dmmkDAeqpXT7QlyCr13hO-HSC5SWQSw2gZw,2150
4
4
  bella_openapi/config.py,sha256=Dn8vnToDaOesPGboauxCCwNrW5awQLeSkmDjNjXS4bQ,319
@@ -13,10 +13,13 @@ bella_openapi/bella_trace/record_log.py,sha256=qSZXp_VTzIzMVlQNnZKLIbmyGvggFSQL5
13
13
  bella_openapi/bella_trace/trace_requests.py,sha256=ADA8J_gbC3TwUo5LWQ3c_yTmCSZRaWzq1FC0iUOnst0,1370
14
14
  bella_openapi/console/__init__.py,sha256=uSfr5v6JLRSqTlftjK_ZU1pnbkEyxAPbuQbMyYX_phk,64
15
15
  bella_openapi/console/models.py,sha256=Hh1UuYHIxFtF9r5QK-pSJPFrSqbZUHv6spLvPbCeX08,1274
16
+ bella_openapi/domtree/__init__.py,sha256=qxFd6d9WZ4ThWQAFtHAhSseo2h2FB2-5KZWyGOooAbo,107
17
+ bella_openapi/domtree/standard_domtree.py,sha256=lIJP2gReyF2RgAxtyncSB6xl_XAzKCpOb-FTNaf-MIw,19952
18
+ bella_openapi/domtree/utils.py,sha256=-ItZYh9Gj8QyOkZzjCC5xWPYU-FkzJllGC0oUO21Kp4,394
16
19
  bella_openapi/middleware/__init__.py,sha256=XWvZG1xO30ZXIn10YVYthmT1BV-9fonMEP_jVRZbAlQ,157
17
20
  bella_openapi/middleware/context_middleware.py,sha256=YawQyKAxMzvlDs_MxcuQKh90pP6VoMKzCBDS94qmlzQ,3870
18
- bella_openapi-1.0.2.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
19
- bella_openapi-1.0.2.1.dist-info/METADATA,sha256=8_UzDGUg5KiWPERvi8bnPWsxpx0GDBBox3uxfgF01D4,9346
20
- bella_openapi-1.0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- bella_openapi-1.0.2.1.dist-info/top_level.txt,sha256=EZuq3F6tKeF-vmZQi6_S2XzmES7SPW7HAbGN1Uv9vN8,14
22
- bella_openapi-1.0.2.1.dist-info/RECORD,,
21
+ bella_openapi-1.0.2.3.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
22
+ bella_openapi-1.0.2.3.dist-info/METADATA,sha256=PbC6CexkWl5ZOR1_0dXPtD6HpydmJ9H_TsOMRH0Kp_E,9377
23
+ bella_openapi-1.0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ bella_openapi-1.0.2.3.dist-info/top_level.txt,sha256=EZuq3F6tKeF-vmZQi6_S2XzmES7SPW7HAbGN1Uv9vN8,14
25
+ bella_openapi-1.0.2.3.dist-info/RECORD,,