uiml 0.2.3__tar.gz → 0.3.0.dev1__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.3
3
+ Version: 0.3.0.dev1
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.3"
7
+ version = "0.3.0.dev1"
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.3",
5
+ version = "0.3.0.dev1",
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,6 +14,77 @@ except ImportError as e:
12
14
 
13
15
  import inspect
14
16
  import re
17
+ from enum import Enum
18
+
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(func)
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()
15
88
 
16
89
  skip_level=1
17
90
 
@@ -355,6 +428,23 @@ def set_style(widget, class_name: str):
355
428
  used_widget_key = ['name', 'type', 'args', 'kwargs', 'init_steps', 'signals', 'style']
356
429
  used_layout_key = ['name', 'direction', 'content', 'stretch']
357
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.")
358
448
  def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parser_func=None,
359
449
  additional_used_widget_key=None, additional_used_layout_key=None,
360
450
  namespace_parser_func=None, reverse=None, enable_value_convert=None,
@@ -363,6 +453,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
363
453
  '''
364
454
  设置解析函数/变量时使用的操作。
365
455
  '''
456
+ assert namespace_api_enabled, "Please add 'uiml.enable_namespace_api()' at the beginning of the code to enable the Namespace API"
457
+
366
458
  global replacer, layout_parser, widget_parser, used_widget_key, used_layout_key, namespace_parser, default_reverse, value_convert, debug
367
459
  if value_replace_func is not None:replacer = value_replace_func
368
460
  if layout_parser_func is not None:layout_parser = layout_parser_func
@@ -373,11 +465,18 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
373
465
  if reverse is not None:default_reverse = reverse
374
466
  if enable_value_convert is not None:value_convert = enable_value_convert
375
467
  if is_debug is not None:debug = is_debug
376
-
468
+
469
+ @replacer_chain.add
377
470
  def default_replacer(value: str):
378
471
  '''默认替换函数,返回原值,可以自定义添加函数,如替换变量、函数调用等。'''
379
- return value
380
-
472
+ unicode = re.sub(
473
+ _UNICODE,
474
+ lambda m: chr(int(m.group(1), 16)),
475
+ value
476
+ )
477
+ return unicode
478
+
479
+ @layout_parser_chain.add
381
480
  def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
382
481
  '''默认布局解析函数,进行递归解析返回'''
383
482
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
@@ -395,7 +494,10 @@ def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=Non
395
494
 
396
495
  def reg_replacer(value):
397
496
  if isinstance(value, str):
398
- return replacer(value)
497
+ if namespace_api_enabled:
498
+ return replacer(value) # 使用 Namespace API
499
+ else:
500
+ return replacer_chain.run(value) # 使用 Parser API
399
501
  elif isinstance(value, dict):
400
502
  return default_reg_replacer_dict(value)
401
503
  elif isinstance(value, list) or isinstance(value, tuple) or isinstance(value, set):
@@ -421,6 +523,7 @@ def _pop_dict(dict_data, key):
421
523
  dict_copy.pop(k, None)
422
524
  return dict_copy
423
525
 
526
+ @widget_parser_chain.add
424
527
  def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
425
528
  '''默认控件解析函数,进行解析返回'''
426
529
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
@@ -464,12 +567,19 @@ def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]
464
567
 
465
568
  def compile_ui(ui_data, namespace=None, additional=None):
466
569
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
467
- if namespace is None:namespace = namespace_parser() # 获取命名空间
570
+ if namespace is None:
571
+ namespace = namespace_parser() # 获取命名空间
468
572
 
469
573
  if ui_data.get('direction'): # 这是layout类型
470
- 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)
471
578
  else: # 这是widget类型
472
- 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)
473
583
 
474
584
  def compile_ui_file(file_path: str, namespace=None, additional=None):
475
585
  '''
@@ -481,7 +591,12 @@ def compile_ui_file(file_path: str, namespace=None, additional=None):
481
591
  '''
482
592
  additional_value = {} if additional is None else additional # 默认添加额外的空字典
483
593
  return compile_ui(xml_parse_file(file_path, namespace, additional=additional_value), namespace, additional=additional_value) # 解析 XML 字符串并编译
484
-
594
+
595
+ default_reverse = False
596
+ debug = False
597
+ value_convert = True
598
+ namespace_parser = load_namespace
599
+
485
600
  class UIMLLayout:
486
601
  def __init__(self, list):
487
602
  self.list = list
@@ -522,20 +637,7 @@ class UIMLLayout:
522
637
 
523
638
  # 最后一段
524
639
  if i == len(parts) - 1:
525
- if 'direction' in current:
526
- # 普通布局:返回 content 列表
527
- content = current.get('content')
528
- if content is None:
529
- raise ValueError(f"Layout '{part}' has no content")
530
- if not isinstance(content, list):
531
- raise TypeError(f"Layout '{part}' content is not a list")
532
- return content
533
- else:
534
- # 控件:返回 content 属性
535
- content = current.get('content')
536
- if content is None:
537
- raise ValueError(f"Widget '{part}' has no content")
538
- return content
640
+ return self.loader(current, part)
539
641
 
540
642
  # 不是最后一段,当前节点必须是布局
541
643
  if 'direction' not in current:
@@ -545,10 +647,7 @@ class UIMLLayout:
545
647
  found = None
546
648
 
547
649
  # 普通布局从 content 中查找
548
- for child in current.get('content', []):
549
- if child.get('name') == next_name:
550
- found = child
551
- break
650
+ found = self.subelement_finder(current, next_name)
552
651
 
553
652
  if found is None:
554
653
  raise KeyError(f"Child '{next_name}' not found in layout '{part}'")
@@ -557,6 +656,35 @@ class UIMLLayout:
557
656
  # 正常流程不会执行到这里
558
657
  return None
559
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
+
560
688
  def show(self):
561
689
  '''显示布局,返回布局对象和类型字符串'''
562
690
  return self.draw_layout()[0]
@@ -616,7 +744,7 @@ class UIMLLayout:
616
744
  '''
617
745
  return (widget[0], ), {}
618
746
 
619
- def return_layout(layout):
747
+ def return_layout(self, layout):
620
748
  '''
621
749
  返回布局对象和类型字符串,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码
622
750
  参数:
@@ -628,20 +756,14 @@ class UIMLLayout:
628
756
 
629
757
  def extend_layout(self, list_info):
630
758
  '''扩展布局,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码'''
631
- WidgetError.direction_error() # 默认这里抛出错误,因为widget类型没有direction属性
759
+ UIMLException.direction_error(list_info['direction'].lower()) # 默认这里抛出错误,因为widget类型没有direction属性
632
760
 
633
761
  def extend_widget(self, widget_info):
634
762
  '''扩展控件,可以在不复制draw_layout()函数的情况下,直接在原控件上添加加载时代码'''
635
763
  return widget_info['content'], 'widget'
636
764
 
637
- class WidgetError:
765
+ class UIMLException(Exception):
638
766
  @staticmethod
639
- def direction_error():
767
+ def direction_error(direction):
640
768
  '''错误方案:在widget类型中找不到direction属性时,抛出错误。可以自定义添加函数,如替换变量、函数调用等。'''
641
- raise ValueError('Direction error: cannot find direction attribute in widget type')
642
-
643
- set_namespace(
644
- default_replacer, default_layout_parser, default_widget_parser,
645
- namespace_parser_func=load_namespace, reverse=False,
646
- enable_value_convert=False, is_debug=False
647
- ) # 设置默认解析函数
769
+ raise UIMLException(f'Cannot load direction "{direction}" in class uiml::layout')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uiml
3
- Version: 0.2.3
3
+ Version: 0.3.0.dev1
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