pytest-dsl 0.14.0__py3-none-any.whl → 0.15.1__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.
pytest_dsl/cli.py CHANGED
@@ -16,10 +16,8 @@ from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
16
16
  from pytest_dsl.core.auto_directory import (
17
17
  SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
18
18
  )
19
- from pytest_dsl.core.plugin_discovery import (
20
- load_all_plugins, scan_local_keywords
21
- )
22
- from pytest_dsl.core.keyword_manager import keyword_manager
19
+ from pytest_dsl.core.keyword_loader import load_all_keywords
20
+ from pytest_dsl.core.keyword_utils import list_keywords as utils_list_keywords
23
21
 
24
22
 
25
23
  def read_file(filename):
@@ -138,588 +136,25 @@ def parse_args():
138
136
  return args
139
137
 
140
138
 
141
- def load_all_keywords(include_remote=False):
142
- """加载所有可用的关键字
143
-
144
- Args:
145
- include_remote: 是否包含远程关键字,默认为False
146
- """
147
- # 首先导入内置关键字模块,确保内置关键字被注册
148
- try:
149
- import pytest_dsl.keywords # noqa: F401
150
- print("内置关键字模块加载完成")
151
- except ImportError as e:
152
- print(f"加载内置关键字模块失败: {e}")
153
-
154
- # 加载已安装的关键字插件
155
- load_all_plugins()
156
-
157
- # 扫描本地关键字
158
- scan_local_keywords()
159
-
160
- # 自动导入项目中的resources目录
161
- try:
162
- from pytest_dsl.core.custom_keyword_manager import (
163
- custom_keyword_manager)
164
-
165
- # 尝试从多个可能的项目根目录位置导入resources
166
- possible_roots = [
167
- os.getcwd(), # 当前工作目录
168
- os.path.dirname(os.getcwd()), # 上级目录
169
- ]
170
-
171
- # 尝试每个可能的根目录
172
- for project_root in possible_roots:
173
- if project_root and os.path.exists(project_root):
174
- resources_dir = os.path.join(project_root, "resources")
175
- if (os.path.exists(resources_dir) and
176
- os.path.isdir(resources_dir)):
177
- custom_keyword_manager.auto_import_resources_directory(
178
- project_root)
179
- break
180
- except Exception as e:
181
- print(f"自动导入resources目录时出现警告: {str(e)}")
182
-
183
- # 扫描项目中的自定义关键字(.resource文件中定义的)
184
- project_custom_keywords = scan_project_custom_keywords()
185
- if project_custom_keywords:
186
- print(f"发现 {len(project_custom_keywords)} 个项目自定义关键字")
187
-
188
- # 加载.resource文件中的关键字到关键字管理器
189
- from pytest_dsl.core.custom_keyword_manager import (
190
- custom_keyword_manager
191
- )
192
- from pathlib import Path
193
-
194
- project_root = Path(os.getcwd())
195
- resource_files = list(project_root.glob('**/*.resource'))
196
-
197
- for resource_file in resource_files:
198
- try:
199
- custom_keyword_manager.load_resource_file(str(resource_file))
200
- print(f"已加载资源文件: {resource_file}")
201
- except Exception as e:
202
- print(f"加载资源文件失败 {resource_file}: {e}")
203
-
204
- # 根据参数决定是否加载远程关键字
205
- if include_remote:
206
- print("正在扫描远程关键字...")
207
- # 这里可以添加远程关键字的扫描逻辑
208
- # 目前远程关键字是通过DSL文件中的@remote导入指令动态加载的
209
- else:
210
- print("跳过远程关键字扫描")
211
-
212
- return project_custom_keywords
213
-
214
-
215
- def categorize_keyword(keyword_name, keyword_info,
216
- project_custom_keywords=None):
217
- """判断关键字的类别"""
218
- # 优先使用存储的来源信息
219
- source_type = keyword_info.get('source_type')
220
- if source_type:
221
- if source_type == 'builtin':
222
- return 'builtin'
223
- elif source_type == 'plugin':
224
- return 'plugin'
225
- elif source_type in ['external', 'local']:
226
- return 'custom'
227
- elif source_type == 'project_custom':
228
- return 'project_custom'
229
-
230
- # 向后兼容:使用原有的判断逻辑
231
- if keyword_info.get('remote', False):
232
- return 'remote'
233
-
234
- # 检查是否是项目自定义关键字(DSL文件中定义的)
235
- if project_custom_keywords and keyword_name in project_custom_keywords:
236
- return 'project_custom'
237
-
238
- # 检查是否是内置关键字(通过检查函数所在模块)
239
- func = keyword_info.get('func')
240
- if func and hasattr(func, '__module__'):
241
- module_name = func.__module__
242
- if module_name and module_name.startswith('pytest_dsl.keywords'):
243
- return 'builtin'
244
-
245
- return 'custom'
246
-
247
-
248
- def get_keyword_source_info(keyword_info):
249
- """获取关键字的详细来源信息"""
250
- source_type = keyword_info.get('source_type', 'unknown')
251
- source_name = keyword_info.get('source_name', '未知')
252
-
253
- return {
254
- 'type': source_type,
255
- 'name': source_name,
256
- 'display_name': source_name,
257
- 'module': keyword_info.get('module_name', ''),
258
- 'plugin_module': keyword_info.get('plugin_module', '')
259
- }
260
-
261
-
262
- def group_keywords_by_source(keywords_dict, project_custom_keywords=None):
263
- """按来源分组关键字
264
-
265
- Returns:
266
- dict: 格式为 {source_group: {source_name: [keywords]}}
267
- """
268
- groups = {
269
- 'builtin': {},
270
- 'plugin': {},
271
- 'custom': {},
272
- 'project_custom': {},
273
- 'remote': {}
274
- }
275
-
276
- for keyword_name, keyword_info in keywords_dict.items():
277
- category = categorize_keyword(
278
- keyword_name, keyword_info, project_custom_keywords
279
- )
280
- source_info = get_keyword_source_info(keyword_info)
281
-
282
- # 特殊处理项目自定义关键字
283
- if category == 'project_custom' and project_custom_keywords:
284
- custom_info = project_custom_keywords[keyword_name]
285
- source_name = custom_info['file']
286
- else:
287
- source_name = source_info['name']
288
-
289
- if source_name not in groups[category]:
290
- groups[category][source_name] = []
291
-
292
- groups[category][source_name].append({
293
- 'name': keyword_name,
294
- 'info': keyword_info,
295
- 'source_info': source_info
296
- })
297
-
298
- return groups
299
-
300
-
301
- def format_keyword_info_text(keyword_name, keyword_info, show_category=True,
302
- project_custom_keywords=None):
303
- """格式化关键字信息为文本格式"""
304
- lines = []
305
-
306
- # 关键字名称和类别
307
- category = categorize_keyword(
308
- keyword_name, keyword_info, project_custom_keywords
309
- )
310
- category_names = {
311
- 'builtin': '内置',
312
- 'custom': '自定义',
313
- 'project_custom': '项目自定义',
314
- 'remote': '远程'
315
- }
316
-
317
- if show_category:
318
- category_display = category_names.get(category, '未知')
319
- lines.append(f"关键字: {keyword_name} [{category_display}]")
320
- else:
321
- lines.append(f"关键字: {keyword_name}")
322
-
323
- # 远程关键字特殊标识
324
- if keyword_info.get('remote', False):
325
- alias = keyword_info.get('alias', '未知')
326
- original_name = keyword_info.get('original_name', keyword_name)
327
- lines.append(f" 远程服务器: {alias}")
328
- lines.append(f" 原始名称: {original_name}")
329
-
330
- # 项目自定义关键字特殊标识
331
- if category == 'project_custom' and project_custom_keywords:
332
- custom_info = project_custom_keywords[keyword_name]
333
- lines.append(f" 文件位置: {custom_info['file']}")
334
-
335
- # 对于项目自定义关键字,使用从AST中提取的参数信息
336
- custom_parameters = custom_info.get('parameters', [])
337
- if custom_parameters:
338
- lines.append(" 参数:")
339
- for param_info in custom_parameters:
340
- param_name = param_info['name']
341
- param_mapping = param_info.get('mapping', '')
342
- param_desc = param_info.get('description', '')
343
- param_default = param_info.get('default', None)
344
-
345
- # 构建参数描述
346
- param_parts = []
347
- if param_mapping and param_mapping != param_name:
348
- param_parts.append(f"{param_name} ({param_mapping})")
349
- else:
350
- param_parts.append(param_name)
351
-
352
- param_parts.append(f": {param_desc}")
353
-
354
- # 添加默认值信息
355
- if param_default is not None:
356
- param_parts.append(f" (默认值: {param_default})")
357
-
358
- lines.append(f" {''.join(param_parts)}")
359
- else:
360
- lines.append(" 参数: 无")
361
- else:
362
- # 参数信息(对于其他类型的关键字)
363
- parameters = keyword_info.get('parameters', [])
364
- if parameters:
365
- lines.append(" 参数:")
366
- for param in parameters:
367
- param_name = getattr(param, 'name', str(param))
368
- param_mapping = getattr(param, 'mapping', '')
369
- param_desc = getattr(param, 'description', '')
370
- param_default = getattr(param, 'default', None)
371
-
372
- # 构建参数描述
373
- param_info = []
374
- if param_mapping and param_mapping != param_name:
375
- param_info.append(f"{param_name} ({param_mapping})")
376
- else:
377
- param_info.append(param_name)
378
-
379
- param_info.append(f": {param_desc}")
380
-
381
- # 添加默认值信息
382
- if param_default is not None:
383
- param_info.append(f" (默认值: {param_default})")
384
-
385
- lines.append(f" {''.join(param_info)}")
386
- else:
387
- lines.append(" 参数: 无")
388
-
389
- # 函数文档
390
- func = keyword_info.get('func')
391
- if func and hasattr(func, '__doc__') and func.__doc__:
392
- lines.append(f" 说明: {func.__doc__.strip()}")
393
-
394
- return '\n'.join(lines)
395
-
396
-
397
- def format_keyword_info_json(keyword_name, keyword_info,
398
- project_custom_keywords=None):
399
- """格式化关键字信息为JSON格式"""
400
- category = categorize_keyword(
401
- keyword_name, keyword_info, project_custom_keywords
402
- )
403
- source_info = get_keyword_source_info(keyword_info)
404
-
405
- keyword_data = {
406
- 'name': keyword_name,
407
- 'category': category,
408
- 'source_info': source_info,
409
- 'parameters': []
410
- }
411
-
412
- # 远程关键字特殊信息
413
- if keyword_info.get('remote', False):
414
- keyword_data['remote'] = {
415
- 'alias': keyword_info.get('alias', ''),
416
- 'original_name': keyword_info.get('original_name', keyword_name)
417
- }
418
-
419
- # 项目自定义关键字特殊信息
420
- if category == 'project_custom' and project_custom_keywords:
421
- custom_info = project_custom_keywords[keyword_name]
422
- keyword_data['file_location'] = custom_info['file']
423
-
424
- # 对于项目自定义关键字,使用从AST中提取的参数信息
425
- for param_info in custom_info.get('parameters', []):
426
- keyword_data['parameters'].append(param_info)
427
- else:
428
- # 参数信息(对于其他类型的关键字)
429
- parameters = keyword_info.get('parameters', [])
430
- for param in parameters:
431
- param_data = {
432
- 'name': getattr(param, 'name', str(param)),
433
- 'mapping': getattr(param, 'mapping', ''),
434
- 'description': getattr(param, 'description', '')
435
- }
436
-
437
- # 添加默认值信息
438
- param_default = getattr(param, 'default', None)
439
- if param_default is not None:
440
- param_data['default'] = param_default
441
-
442
- keyword_data['parameters'].append(param_data)
443
-
444
- # 函数文档
445
- func = keyword_info.get('func')
446
- if func and hasattr(func, '__doc__') and func.__doc__:
447
- keyword_data['documentation'] = func.__doc__.strip()
448
-
449
- return keyword_data
450
-
451
-
452
- def generate_html_report(keywords_data, output_file):
453
- """生成HTML格式的关键字报告"""
454
- from jinja2 import Environment, FileSystemLoader, select_autoescape
455
- import os
456
-
457
- # 准备数据
458
- summary = keywords_data['summary']
459
- keywords = keywords_data['keywords']
460
-
461
- # 按类别分组
462
- categories = {}
463
- for keyword in keywords:
464
- category = keyword['category']
465
- if category not in categories:
466
- categories[category] = []
467
- categories[category].append(keyword)
468
-
469
- # 按来源分组(用于更详细的分组视图)
470
- source_groups = {}
471
- for keyword in keywords:
472
- source_info = keyword.get('source_info', {})
473
- category = keyword['category']
474
- source_name = source_info.get('name', '未知来源')
475
-
476
- # 构建分组键
477
- if category == 'plugin':
478
- group_key = f"插件 - {source_name}"
479
- elif category == 'builtin':
480
- group_key = "内置关键字"
481
- elif category == 'project_custom':
482
- group_key = f"项目自定义 - {keyword.get('file_location', source_name)}"
483
- elif category == 'remote':
484
- group_key = f"远程 - {source_name}"
485
- else:
486
- group_key = f"自定义 - {source_name}"
487
-
488
- if group_key not in source_groups:
489
- source_groups[group_key] = []
490
- source_groups[group_key].append(keyword)
491
-
492
- # 按位置分组(用于全部关键字视图,保持向后兼容)
493
- location_groups = {}
494
- for keyword in keywords:
495
- # 优先使用file_location,然后使用source_info中的name
496
- location = keyword.get('file_location')
497
- if not location:
498
- source_info = keyword.get('source_info', {})
499
- location = source_info.get('name', '内置/插件')
500
-
501
- if location not in location_groups:
502
- location_groups[location] = []
503
- location_groups[location].append(keyword)
504
-
505
- # 类别名称映射
506
- category_names = {
507
- 'builtin': '内置',
508
- 'plugin': '插件',
509
- 'custom': '自定义',
510
- 'project_custom': '项目自定义',
511
- 'remote': '远程'
512
- }
513
-
514
- # 设置Jinja2环境
515
- template_dir = os.path.join(os.path.dirname(__file__), 'templates')
516
-
517
- env = Environment(
518
- loader=FileSystemLoader(template_dir),
519
- autoescape=select_autoescape(['html', 'xml'])
520
- )
521
-
522
- # 加载模板
523
- template = env.get_template('keywords_report.html')
524
-
525
- # 渲染模板
526
- html_content = template.render(
527
- summary=summary,
528
- keywords=keywords,
529
- categories=categories,
530
- source_groups=source_groups,
531
- location_groups=location_groups,
532
- category_names=category_names
533
- )
534
-
535
- # 写入文件
536
- with open(output_file, 'w', encoding='utf-8') as f:
537
- f.write(html_content)
538
-
539
- print(f"HTML报告已生成: {output_file}")
540
-
541
-
542
139
  def list_keywords(output_format='json', name_filter=None,
543
140
  category_filter='all', output_file=None,
544
141
  include_remote=False):
545
- """罗列所有关键字信息"""
546
- import json
547
-
142
+ """罗列所有关键字信息(简化版,调用统一的工具函数)"""
548
143
  print("正在加载关键字...")
549
- project_custom_keywords = load_all_keywords(include_remote=include_remote)
550
-
551
- # 获取所有注册的关键字
552
- all_keywords = keyword_manager._keywords
553
-
554
- if not all_keywords:
555
- print("未发现任何关键字")
556
- return
557
-
558
- # 过滤关键字
559
- filtered_keywords = {}
560
-
561
- for name, info in all_keywords.items():
562
- # 名称过滤
563
- if name_filter and name_filter.lower() not in name.lower():
564
- continue
565
144
 
566
- # 远程关键字过滤
567
- if not include_remote and info.get('remote', False):
568
- continue
569
-
570
- # 类别过滤
571
- if category_filter != 'all':
572
- keyword_category = categorize_keyword(
573
- name, info, project_custom_keywords
574
- )
575
- if keyword_category != category_filter:
576
- continue
577
-
578
- filtered_keywords[name] = info
579
-
580
- if not filtered_keywords:
581
- if name_filter:
582
- print(f"未找到包含 '{name_filter}' 的关键字")
583
- else:
584
- print(f"未找到 {category_filter} 类别的关键字")
585
- return
586
-
587
- # 输出统计信息
588
- total_count = len(filtered_keywords)
589
- category_counts = {}
590
- source_counts = {}
591
-
592
- for name, info in filtered_keywords.items():
593
- cat = categorize_keyword(name, info, project_custom_keywords)
594
- category_counts[cat] = category_counts.get(cat, 0) + 1
595
-
596
- # 统计各来源的关键字数量
597
- source_info = get_keyword_source_info(info)
598
- source_name = source_info['name']
599
- if cat == 'project_custom' and project_custom_keywords:
600
- custom_info = project_custom_keywords[name]
601
- source_name = custom_info['file']
602
-
603
- source_key = f"{cat}:{source_name}"
604
- source_counts[source_key] = source_counts.get(source_key, 0) + 1
605
-
606
- if output_format == 'text':
607
- print(f"\n找到 {total_count} 个关键字:")
608
- for cat, count in category_counts.items():
609
- cat_names = {
610
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
611
- 'project_custom': '项目自定义', 'remote': '远程'
612
- }
613
- print(f" {cat_names.get(cat, cat)}: {count} 个")
614
- print("-" * 60)
615
-
616
- # 按类别和来源分组显示
617
- grouped = group_keywords_by_source(
618
- filtered_keywords, project_custom_keywords
145
+ # 使用统一的工具函数
146
+ try:
147
+ utils_list_keywords(
148
+ output_format=output_format,
149
+ name_filter=name_filter,
150
+ category_filter=category_filter,
151
+ include_remote=include_remote,
152
+ output_file=output_file,
153
+ print_summary=True
619
154
  )
620
-
621
- for category in [
622
- 'builtin', 'plugin', 'custom', 'project_custom', 'remote'
623
- ]:
624
- if category not in grouped or not grouped[category]:
625
- continue
626
-
627
- cat_names = {
628
- 'builtin': '内置关键字',
629
- 'plugin': '插件关键字',
630
- 'custom': '自定义关键字',
631
- 'project_custom': '项目自定义关键字',
632
- 'remote': '远程关键字'
633
- }
634
- print(f"\n=== {cat_names[category]} ===")
635
-
636
- for source_name, keyword_list in grouped[category].items():
637
- if len(grouped[category]) > 1: # 如果有多个来源,显示来源名
638
- print(f"\n--- {source_name} ---")
639
-
640
- for keyword_data in keyword_list:
641
- name = keyword_data['name']
642
- info = keyword_data['info']
643
- print()
644
- print(format_keyword_info_text(
645
- name, info, show_category=False,
646
- project_custom_keywords=project_custom_keywords
647
- ))
648
-
649
- elif output_format == 'json':
650
- keywords_data = {
651
- 'summary': {
652
- 'total_count': total_count,
653
- 'category_counts': category_counts,
654
- 'source_counts': source_counts
655
- },
656
- 'keywords': []
657
- }
658
-
659
- for name in sorted(filtered_keywords.keys()):
660
- info = filtered_keywords[name]
661
- keyword_data = format_keyword_info_json(
662
- name, info, project_custom_keywords
663
- )
664
- keywords_data['keywords'].append(keyword_data)
665
-
666
- json_output = json.dumps(keywords_data, ensure_ascii=False, indent=2)
667
-
668
- # 确定输出文件名
669
- if output_file is None:
670
- output_file = 'keywords.json'
671
-
672
- # 写入到文件
673
- try:
674
- with open(output_file, 'w', encoding='utf-8') as f:
675
- f.write(json_output)
676
- print(f"关键字信息已保存到文件: {output_file}")
677
- print(f"共 {total_count} 个关键字")
678
- for cat, count in category_counts.items():
679
- cat_names = {
680
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
681
- 'project_custom': '项目自定义', 'remote': '远程'
682
- }
683
- print(f" {cat_names.get(cat, cat)}: {count} 个")
684
- except Exception as e:
685
- print(f"保存文件失败: {e}")
686
- # 如果写入文件失败,则回退到打印
687
- print(json_output)
688
-
689
- elif output_format == 'html':
690
- keywords_data = {
691
- 'summary': {
692
- 'total_count': total_count,
693
- 'category_counts': category_counts,
694
- 'source_counts': source_counts
695
- },
696
- 'keywords': []
697
- }
698
-
699
- for name in sorted(filtered_keywords.keys()):
700
- info = filtered_keywords[name]
701
- keyword_data = format_keyword_info_json(
702
- name, info, project_custom_keywords
703
- )
704
- keywords_data['keywords'].append(keyword_data)
705
-
706
- # 确定输出文件名
707
- if output_file is None:
708
- output_file = 'keywords.html'
709
-
710
- # 生成HTML报告
711
- try:
712
- generate_html_report(keywords_data, output_file)
713
- print(f"共 {total_count} 个关键字")
714
- for cat, count in category_counts.items():
715
- cat_names = {
716
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
717
- 'project_custom': '项目自定义', 'remote': '远程'
718
- }
719
- print(f" {cat_names.get(cat, cat)}: {count} 个")
720
- except Exception as e:
721
- print(f"生成HTML报告失败: {e}")
722
- raise
155
+ except Exception as e:
156
+ print(f"列出关键字失败: {e}")
157
+ raise
723
158
 
724
159
 
725
160
  def load_yaml_variables(args):
@@ -932,107 +367,5 @@ def main_list_keywords():
932
367
  )
933
368
 
934
369
 
935
- def scan_project_custom_keywords(project_root=None):
936
- """扫描项目中.resource文件中的自定义关键字
937
-
938
- Args:
939
- project_root: 项目根目录,默认为当前工作目录
940
-
941
- Returns:
942
- dict: 自定义关键字信息,格式为
943
- {keyword_name: {'file': file_path, 'node': ast_node}}
944
- """
945
- if project_root is None:
946
- project_root = os.getcwd()
947
-
948
- project_root = Path(project_root)
949
- custom_keywords = {}
950
-
951
- # 查找所有.resource文件
952
- resource_files = list(project_root.glob('**/*.resource'))
953
-
954
- if not resource_files:
955
- return custom_keywords
956
-
957
- lexer = get_lexer()
958
- parser = get_parser()
959
-
960
- for file_path in resource_files:
961
- try:
962
- # 读取并解析文件
963
- content = read_file(str(file_path))
964
- ast = parser.parse(content, lexer=lexer)
965
-
966
- # 查找自定义关键字定义
967
- keywords_in_file = extract_custom_keywords_from_ast(
968
- ast, str(file_path)
969
- )
970
- custom_keywords.update(keywords_in_file)
971
-
972
- except Exception as e:
973
- print(f"解析资源文件 {file_path} 时出错: {e}")
974
-
975
- return custom_keywords
976
-
977
-
978
- def extract_custom_keywords_from_ast(ast, file_path):
979
- """从AST中提取自定义关键字定义
980
-
981
- Args:
982
- ast: 抽象语法树
983
- file_path: 文件路径
984
-
985
- Returns:
986
- dict: 自定义关键字信息
987
- """
988
- custom_keywords = {}
989
-
990
- if ast.type != 'Start' or len(ast.children) < 2:
991
- return custom_keywords
992
-
993
- # 遍历语句节点
994
- statements_node = ast.children[1]
995
- if statements_node.type != 'Statements':
996
- return custom_keywords
997
-
998
- for node in statements_node.children:
999
- # 支持两种格式:CustomKeyword(旧格式)和Function(新格式)
1000
- if node.type in ['CustomKeyword', 'Function']:
1001
- keyword_name = node.value
1002
-
1003
- # 提取参数信息
1004
- params_node = node.children[0] if node.children else None
1005
- parameters = []
1006
-
1007
- if params_node:
1008
- for param in params_node:
1009
- param_name = param.value
1010
- param_default = None
1011
-
1012
- # 检查是否有默认值
1013
- if param.children and param.children[0]:
1014
- param_default = param.children[0].value
1015
-
1016
- param_info = {
1017
- 'name': param_name,
1018
- 'mapping': param_name,
1019
- 'description': f'自定义关键字参数 {param_name}'
1020
- }
1021
-
1022
- if param_default is not None:
1023
- param_info['default'] = param_default
1024
-
1025
- parameters.append(param_info)
1026
-
1027
- custom_keywords[keyword_name] = {
1028
- 'file': file_path,
1029
- 'node': node,
1030
- 'type': 'project_custom',
1031
- 'parameters': parameters
1032
- }
1033
-
1034
- return custom_keywords
1035
-
1036
-
1037
370
  if __name__ == '__main__':
1038
371
  main()