uiml 0.2.4__tar.gz → 0.3.0.dev2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uiml
3
- Version: 0.2.4
3
+ Version: 0.3.0.dev2
4
4
  Home-page: https://github.com/xystudio889/
5
5
  Author: xystudio
6
6
  Author-email: xystudio <173288240@qq.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "uiml"
7
- version = "0.2.4"
7
+ version = "0.3.0.dev2"
8
8
  description = ""
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  authors = [
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name = "uiml",
5
- version = "0.2.4",
5
+ version = "0.3.0.dev2",
6
6
  package_dir={"": "src"},
7
7
  packages=find_packages(where="src"),
8
- install_requires = ["PySide6>=6.10.0"],
8
+ install_requires = ["PySide6>=6.10.0", "deprecated>=1.3"],
9
9
  python_requires = ">=3.10",
10
10
  author = "xystudio",
11
11
  author_email = "173288240@qq.com",
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Dict, Optional, Tuple
3
+ from typing import Any, Dict, Optional, Tuple, Callable
4
+ from deprecated import deprecated
5
+ from warnings import warn
4
6
 
5
7
  try:
6
8
  from uiml.uilib import *
@@ -12,8 +14,77 @@ except ImportError as e:
12
14
 
13
15
  import inspect
14
16
  import re
17
+ from enum import Enum
15
18
 
16
19
  _UNICODE = re.compile(r'&#[xX]([0-9a-fA-F]+);',)
20
+ func = list[Callable]
21
+
22
+ namespace_api_enabled = False
23
+
24
+ class ParserEnum(Enum):
25
+ replacer = 1 # 参数替换器
26
+ widget_parser = 2 # 组件解析器
27
+ layout_parser = 3 # 布局解析器
28
+
29
+ removed_widget_key = 5 # 自动删除的组件项目列表
30
+ removed_layout_key = 6 # 自动删除的布局项目列表
31
+
32
+ uiml_class = 7 # 对layout渲染的扩展
33
+
34
+ class ParamEnum(Enum):
35
+ reverse_namespace = 1 # 翻转命名空间顺序
36
+ value_convert = 2 # 允许变量转化
37
+ debug_mode = 3 # 调试模式
38
+ namespace_parser = 4 # 命名空间的解析器
39
+
40
+ class ParserChain:
41
+ def __init__(self, funcs: Optional[func] = None):
42
+ self._funcs = [] if funcs is None else funcs
43
+
44
+ if funcs is not None:
45
+ self.funcs = funcs # 初始校验
46
+
47
+ def add(self, func:func):
48
+ '''添加一个函数 / 装饰器'''
49
+ self.funcs.append(func)
50
+ self.check(self.funcs)
51
+ return func
52
+
53
+ def remove(self, func:func):
54
+ '''删除一个函数'''
55
+ self.funcs.remove(func)
56
+
57
+ def set(self, funcs: func):
58
+ self.funcs = funcs
59
+
60
+ @property
61
+ def funcs(self):
62
+ return self._funcs
63
+
64
+ @funcs.setter
65
+ def funcs(self, v):
66
+ self.check(v)
67
+
68
+ def check(self, v):
69
+ if isinstance(v, list):
70
+ for func in v:
71
+ if not isinstance(func, Callable):
72
+ raise TypeError('Function list item must be function')
73
+ self._funcs = v
74
+ else:
75
+ raise TypeError('Function list must be list')
76
+
77
+ def run(self, *args, **kwargs):
78
+ '''按照列表的反向顺序执行'''
79
+ for func in reversed(self.funcs):
80
+ result = func(*args, **kwargs)
81
+
82
+ if result is not None:
83
+ return result
84
+
85
+ replacer_chain = ParserChain()
86
+ widget_parser_chain = ParserChain()
87
+ layout_parser_chain = ParserChain()
17
88
 
18
89
  skip_level=1
19
90
 
@@ -357,6 +428,23 @@ def set_style(widget, class_name: str):
357
428
  used_widget_key = ['name', 'type', 'args', 'kwargs', 'init_steps', 'signals', 'style']
358
429
  used_layout_key = ['name', 'direction', 'content', 'stretch']
359
430
 
431
+ def enable_namespace_api():
432
+ global namespace_api_enabled
433
+
434
+ namespace_api_enabled = True
435
+
436
+ set_namespace(
437
+ default_replacer, default_layout_parser, default_widget_parser,
438
+ namespace_parser_func=load_namespace, reverse=False,
439
+ enable_value_convert=False, is_debug=False
440
+ ) # 设置默认解析函数
441
+
442
+ warn(
443
+ 'Namespace API is deprecated, please use Parser API (add_parser) instead. Now Parser API is disabled.',
444
+ category=DeprecationWarning
445
+ )
446
+
447
+ @deprecated(reason="Namespace API is deprecated, please use Parser API (add_parser) instead.")
360
448
  def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parser_func=None,
361
449
  additional_used_widget_key=None, additional_used_layout_key=None,
362
450
  namespace_parser_func=None, reverse=None, enable_value_convert=None,
@@ -365,6 +453,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
365
453
  '''
366
454
  设置解析函数/变量时使用的操作。
367
455
  '''
456
+ assert namespace_api_enabled, "Please add 'uiml.enable_namespace_api()' at the beginning of the code to enable the Namespace API"
457
+
368
458
  global replacer, layout_parser, widget_parser, used_widget_key, used_layout_key, namespace_parser, default_reverse, value_convert, debug
369
459
  if value_replace_func is not None:replacer = value_replace_func
370
460
  if layout_parser_func is not None:layout_parser = layout_parser_func
@@ -375,7 +465,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
375
465
  if reverse is not None:default_reverse = reverse
376
466
  if enable_value_convert is not None:value_convert = enable_value_convert
377
467
  if is_debug is not None:debug = is_debug
378
-
468
+
469
+ @replacer_chain.add
379
470
  def default_replacer(value: str):
380
471
  '''默认替换函数,返回原值,可以自定义添加函数,如替换变量、函数调用等。'''
381
472
  unicode = re.sub(
@@ -385,6 +476,7 @@ def default_replacer(value: str):
385
476
  )
386
477
  return unicode
387
478
 
479
+ @layout_parser_chain.add
388
480
  def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
389
481
  '''默认布局解析函数,进行递归解析返回'''
390
482
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
@@ -402,7 +494,10 @@ def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=Non
402
494
 
403
495
  def reg_replacer(value):
404
496
  if isinstance(value, str):
405
- return replacer(value)
497
+ if namespace_api_enabled:
498
+ return replacer(value) # 使用 Namespace API
499
+ else:
500
+ return replacer_chain.run(value) # 使用 Parser API
406
501
  elif isinstance(value, dict):
407
502
  return default_reg_replacer_dict(value)
408
503
  elif isinstance(value, list) or isinstance(value, tuple) or isinstance(value, set):
@@ -428,6 +523,7 @@ def _pop_dict(dict_data, key):
428
523
  dict_copy.pop(k, None)
429
524
  return dict_copy
430
525
 
526
+ @widget_parser_chain.add
431
527
  def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
432
528
  '''默认控件解析函数,进行解析返回'''
433
529
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
@@ -471,12 +567,19 @@ def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]
471
567
 
472
568
  def compile_ui(ui_data, namespace=None, additional=None):
473
569
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
474
- if namespace is None:namespace = namespace_parser() # 获取命名空间
570
+ if namespace is None:
571
+ namespace = namespace_parser() # 获取命名空间
475
572
 
476
573
  if ui_data.get('direction'): # 这是layout类型
477
- return layout_parser(ui_data, namespace, additional=additional_value)
574
+ if namespace_api_enabled: # namespace API 语法
575
+ return layout_parser(ui_data, namespace, additional=additional_value)
576
+ else:
577
+ return layout_parser_chain.run(ui_data, namespace, additional=additional)
478
578
  else: # 这是widget类型
479
- return widget_parser(ui_data, namespace, additional=additional_value)
579
+ if namespace_api_enabled: # namespace API 语法
580
+ return widget_parser(ui_data, namespace, additional=additional_value)
581
+ else:
582
+ return widget_parser_chain.run(ui_data, namespace, additional=additional)
480
583
 
481
584
  def compile_ui_file(file_path: str, namespace=None, additional=None):
482
585
  '''
@@ -488,7 +591,12 @@ def compile_ui_file(file_path: str, namespace=None, additional=None):
488
591
  '''
489
592
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
490
593
  return compile_ui(xml_parse_file(file_path, namespace, additional=additional_value), namespace, additional=additional_value) # 解析 XML 字符串并编译
491
-
594
+
595
+ default_reverse = False
596
+ debug = False
597
+ value_convert = True
598
+ namespace_parser = load_namespace
599
+
492
600
  class UIMLLayout:
493
601
  def __init__(self, list):
494
602
  self.list = list
@@ -529,20 +637,7 @@ class UIMLLayout:
529
637
 
530
638
  # 最后一段
531
639
  if i == len(parts) - 1:
532
- if 'direction' in current:
533
- # 普通布局:返回 content 列表
534
- content = current.get('content')
535
- if content is None:
536
- raise ValueError(f"Layout '{part}' has no content")
537
- if not isinstance(content, list):
538
- raise TypeError(f"Layout '{part}' content is not a list")
539
- return content
540
- else:
541
- # 控件:返回 content 属性
542
- content = current.get('content')
543
- if content is None:
544
- raise ValueError(f"Widget '{part}' has no content")
545
- return content
640
+ return self.loader(current, part)
546
641
 
547
642
  # 不是最后一段,当前节点必须是布局
548
643
  if 'direction' not in current:
@@ -552,10 +647,7 @@ class UIMLLayout:
552
647
  found = None
553
648
 
554
649
  # 普通布局从 content 中查找
555
- for child in current.get('content', []):
556
- if child.get('name') == next_name:
557
- found = child
558
- break
650
+ found = self.subelement_finder(current, next_name)
559
651
 
560
652
  if found is None:
561
653
  raise KeyError(f"Child '{next_name}' not found in layout '{part}'")
@@ -564,6 +656,35 @@ class UIMLLayout:
564
656
  # 正常流程不会执行到这里
565
657
  return None
566
658
 
659
+ def loader(self, current, part):
660
+ if 'direction' in current: # layout 类型
661
+ return self.layout_loader(current, part)
662
+ else:
663
+ return self.widget_loader(current, part)
664
+
665
+ def layout_loader(self, current, part):
666
+ # 普通布局:返回 content 列表
667
+ content = current.get('content')
668
+ if content is None:
669
+ raise ValueError(f"Layout '{part}' has no content")
670
+ if not isinstance(content, list):
671
+ raise TypeError(f"Layout '{part}' content is not a list")
672
+ return content
673
+
674
+ def widget_loader(self, current, part):
675
+ # 控件:返回 content 属性
676
+ content = current.get('content')
677
+ if content is None:
678
+ raise ValueError(f"Widget '{part}' has no content")
679
+ return content
680
+
681
+ def subelement_finder(self, current, next):
682
+ # 普通布局从 content 中查找
683
+ for child in current.get('content', []):
684
+ if child.get('name') == next:
685
+ found = child
686
+ return found
687
+
567
688
  def show(self):
568
689
  '''显示布局,返回布局对象和类型字符串'''
569
690
  return self.draw_layout()[0]
@@ -623,7 +744,7 @@ class UIMLLayout:
623
744
  '''
624
745
  return (widget[0], ), {}
625
746
 
626
- def return_layout(layout):
747
+ def return_layout(self, layout):
627
748
  '''
628
749
  返回布局对象和类型字符串,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码
629
750
  参数:
@@ -646,9 +767,3 @@ class UIMLException(Exception):
646
767
  def direction_error(direction):
647
768
  '''错误方案:在widget类型中找不到direction属性时,抛出错误。可以自定义添加函数,如替换变量、函数调用等。'''
648
769
  raise UIMLException(f'Cannot load direction "{direction}" in class uiml::layout')
649
-
650
- set_namespace(
651
- default_replacer, default_layout_parser, default_widget_parser,
652
- namespace_parser_func=load_namespace, reverse=False,
653
- enable_value_convert=False, is_debug=False
654
- ) # 设置默认解析函数
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uiml
3
- Version: 0.2.4
3
+ Version: 0.3.0.dev2
4
4
  Home-page: https://github.com/xystudio889/
5
5
  Author: xystudio
6
6
  Author-email: xystudio <173288240@qq.com>
@@ -9,4 +9,6 @@ src/uiml.egg-info/SOURCES.txt
9
9
  src/uiml.egg-info/dependency_links.txt
10
10
  src/uiml.egg-info/requires.txt
11
11
  src/uiml.egg-info/top_level.txt
12
+ tests/test_error.py
13
+ tests/test_fallback.py
12
14
  tests/test_symbol.py
@@ -0,0 +1,87 @@
1
+ from typing import Optional, Callable
2
+
3
+ func = list[Callable]
4
+
5
+ class ParserChain:
6
+ def __init__(self, funcs: Optional[func] = None):
7
+ self._funcs = [] if funcs is None else funcs
8
+
9
+ if funcs is not None:
10
+ self.funcs = funcs # 初始校验
11
+
12
+ def add(self, func:func):
13
+ '''添加一个函数 / 装饰器'''
14
+ self.funcs.append(func)
15
+ self.check(func)
16
+ return func
17
+
18
+ def remove(self, func:func):
19
+ '''删除一个函数'''
20
+ self.funcs.remove(func)
21
+
22
+ def set(self, funcs: func):
23
+ self.funcs = funcs
24
+
25
+ @property
26
+ def funcs(self):
27
+ return self._funcs
28
+
29
+ @funcs.setter
30
+ def funcs(self, v):
31
+ self.check(v)
32
+
33
+ def check(self, v):
34
+ if isinstance(v, list):
35
+ for func in v:
36
+ if not isinstance(func, Callable):
37
+ raise TypeError('Function list item must be function')
38
+ self._funcs = v
39
+ else:
40
+ raise TypeError('Function list must be list')
41
+
42
+ def run(self, *args, **kwargs):
43
+ '''按照列表的反向顺序执行'''
44
+ for func in reversed(self.funcs):
45
+ result = func(*args, **kwargs)
46
+
47
+ if result is not None:
48
+ return result
49
+
50
+ def test_main():
51
+ try:
52
+ ParserChain(123) # 初始化:不是列表
53
+
54
+ assert False
55
+ except TypeError:
56
+ print('Success!')
57
+
58
+ try:
59
+ ParserChain([123]) # 初始化:列表项不是函数
60
+
61
+ assert False
62
+ except TypeError:
63
+ print('Success!')
64
+
65
+ try:
66
+ v = ParserChain([]) # 初始化:列表项不是函数
67
+ v.set(123) # 设置非法值
68
+
69
+ assert False
70
+ except TypeError:
71
+ print('Success!')
72
+
73
+ try:
74
+ v = ParserChain([]) # 初始化:列表项不是函数
75
+ v.add(123) # 添加非法值
76
+
77
+ assert False
78
+ except TypeError:
79
+ print('Success!')
80
+
81
+ try:
82
+ v = ParserChain([]) # 初始化:列表项不是函数
83
+ v.funcs = 123 # 连接非法值
84
+
85
+ assert False
86
+ except TypeError:
87
+ print('Success!')
@@ -0,0 +1,60 @@
1
+ from typing import Callable, Optional
2
+
3
+ func = list[Callable]
4
+
5
+ class ParserChain:
6
+ def __init__(self, funcs: Optional[func] = None):
7
+ self.funcs: func = [] if funcs is None else funcs
8
+
9
+ def add(self, func:func):
10
+ '''添加一个函数 / 装饰器'''
11
+ self.funcs.append(func)
12
+ return func
13
+
14
+ def remove(self, func:func):
15
+ '''删除一个函数'''
16
+ self.funcs.remove(func)
17
+
18
+ @property
19
+ def get_funcs(self):
20
+ return self.funcs
21
+
22
+ def run(self, *args, **kwargs):
23
+ '''按照列表的反向顺序执行'''
24
+ for func in reversed(self.funcs):
25
+ result = func(*args, **kwargs)
26
+
27
+ if result is not None:
28
+ return result
29
+
30
+ def test_main():
31
+ # ---------------- 示例 ----------------
32
+ chain = ParserChain()
33
+
34
+ @chain.add
35
+ def low(v): # 最先添加 → 最后执行
36
+ return f"low({v})"
37
+
38
+ @chain.add
39
+ def mid(v): # 第二个添加
40
+ return None if v < 10 else f"mid({v})"
41
+
42
+ @chain.add
43
+ def high(v): # 最后添加 → 最先执行
44
+ return None if v < 100 else f"high({v})"
45
+
46
+ @chain.add
47
+ def no_add(v):
48
+ raise ValueError('This is Added and runed!')
49
+
50
+ chain.remove(no_add)
51
+
52
+ assert chain.get_funcs == [low, mid, high]
53
+ assert chain.run(5) == "low(5)" # high→None, mid→None, low→"low(5)"
54
+ assert chain.run(50) == "mid(50)" # high→None, mid→"mid(50)"
55
+ assert chain.run(500) == "high(500)" # "high(500)"
56
+
57
+ # 用列表初始化也支持
58
+ c2 = ParserChain([lambda v: None, lambda v: f"got {v}"])
59
+ assert c2.run(1) == "got 1" # "got 1"
60
+ assert ParserChain().run(1) is None # None
File without changes
File without changes
File without changes
File without changes
File without changes