IncludeCPP 3.8.1__tar.gz → 3.8.2__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.
Files changed (60) hide show
  1. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/PKG-INFO +1 -1
  2. {includecpp-3.8.1 → includecpp-3.8.2}/PKG-INFO +1 -1
  3. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/__init__.py +1 -1
  4. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_parser.py +52 -6
  5. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_runtime.py +56 -9
  6. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/package.json +1 -1
  7. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +54 -0
  8. {includecpp-3.8.1 → includecpp-3.8.2}/pyproject.toml +1 -1
  9. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/SOURCES.txt +0 -0
  10. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  11. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/entry_points.txt +0 -0
  12. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/requires.txt +0 -0
  13. {includecpp-3.8.1 → includecpp-3.8.2}/IncludeCPP.egg-info/top_level.txt +0 -0
  14. {includecpp-3.8.1 → includecpp-3.8.2}/LICENSE +0 -0
  15. {includecpp-3.8.1 → includecpp-3.8.2}/MANIFEST.in +0 -0
  16. {includecpp-3.8.1 → includecpp-3.8.2}/README.md +0 -0
  17. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/__init__.pyi +0 -0
  18. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/__main__.py +0 -0
  19. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/cli/__init__.py +0 -0
  20. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/cli/commands.py +0 -0
  21. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/cli/config_parser.py +0 -0
  22. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/__init__.py +0 -0
  23. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/ai_integration.py +0 -0
  24. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/build_manager.py +0 -0
  25. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cpp_api.py +0 -0
  26. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cpp_api.pyi +0 -0
  27. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cppy_converter.py +0 -0
  28. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
  29. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/__init__.py +0 -0
  30. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_builtins.py +0 -0
  31. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_builtins.pyi +0 -0
  32. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_events.py +0 -0
  33. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_modules.py +0 -0
  34. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_syntax.py +0 -0
  35. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl/cssl_types.py +0 -0
  36. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl_bridge.py +0 -0
  37. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/cssl_bridge.pyi +0 -0
  38. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/error_catalog.py +0 -0
  39. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/error_formatter.py +0 -0
  40. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/exceptions.py +0 -0
  41. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/path_discovery.py +0 -0
  42. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/project_ui.py +0 -0
  43. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/core/settings_ui.py +0 -0
  44. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/generator/__init__.py +0 -0
  45. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/generator/parser.cpp +0 -0
  46. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/generator/parser.h +0 -0
  47. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/generator/type_resolver.cpp +0 -0
  48. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/generator/type_resolver.h +0 -0
  49. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/py.typed +0 -0
  50. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/templates/cpp.proj.template +0 -0
  51. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/__init__.py +0 -0
  52. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/__init__.py +0 -0
  53. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/extension.js +0 -0
  54. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/images/cssl.png +0 -0
  55. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/images/cssl_pl.png +0 -0
  56. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/language-configuration.json +0 -0
  57. {includecpp-3.8.1 → includecpp-3.8.2}/includecpp/vscode/cssl/snippets/cssl.snippets.json +0 -0
  58. {includecpp-3.8.1 → includecpp-3.8.2}/requirements.txt +0 -0
  59. {includecpp-3.8.1 → includecpp-3.8.2}/setup.cfg +0 -0
  60. {includecpp-3.8.1 → includecpp-3.8.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 3.8.1
3
+ Version: 3.8.2
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 3.8.1
3
+ Version: 3.8.2
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -2,7 +2,7 @@ from .core.cpp_api import CppApi
2
2
  from .core import cssl_bridge as CSSL
3
3
  import warnings
4
4
 
5
- __version__ = "3.8.1"
5
+ __version__ = "3.8.2"
6
6
  __all__ = ["CppApi", "CSSL"]
7
7
 
8
8
  # Module-level cache for C++ modules
@@ -1025,7 +1025,11 @@ class CSSLParser:
1025
1025
  return result
1026
1026
 
1027
1027
  def _parse_typed_variable(self) -> Optional[ASTNode]:
1028
- """Parse a typed variable declaration: type varName; or type<T> varName = value;"""
1028
+ """Parse a typed variable declaration: type varName; or type<T> *varName = value;
1029
+
1030
+ The * prefix indicates a non-nullable variable (can never be None/null).
1031
+ Example: vector<dynamic> *MyVector - can never contain None values.
1032
+ """
1029
1033
  # Get type name
1030
1034
  type_name = self._advance().value # Consume type keyword
1031
1035
 
@@ -1039,6 +1043,11 @@ class CSSLParser:
1039
1043
  element_type = self._advance().value
1040
1044
  self._expect(TokenType.COMPARE_GT)
1041
1045
 
1046
+ # Check for * prefix (non-nullable indicator)
1047
+ non_null = False
1048
+ if self._match(TokenType.ASTERISK):
1049
+ non_null = True
1050
+
1042
1051
  # Get variable name
1043
1052
  if not self._check(TokenType.IDENTIFIER):
1044
1053
  return None
@@ -1056,14 +1065,16 @@ class CSSLParser:
1056
1065
  return ASTNode('instance_declaration', value={
1057
1066
  'instance_name': element_type,
1058
1067
  'name': var_name,
1059
- 'value': value
1068
+ 'value': value,
1069
+ 'non_null': non_null
1060
1070
  })
1061
1071
 
1062
1072
  return ASTNode('typed_declaration', value={
1063
1073
  'type': type_name,
1064
1074
  'element_type': element_type,
1065
1075
  'name': var_name,
1066
- 'value': value
1076
+ 'value': value,
1077
+ 'non_null': non_null
1067
1078
  })
1068
1079
 
1069
1080
  def parse_program(self) -> ASTNode:
@@ -1361,10 +1372,18 @@ class CSSLParser:
1361
1372
  printl("Hello " + this->name);
1362
1373
  }
1363
1374
  }
1375
+
1376
+ Non-null class (all methods return non-null):
1377
+ class *MyClass { ... }
1364
1378
  """
1379
+ # Check for * prefix (non-null class - all methods return non-null)
1380
+ non_null = False
1381
+ if self._match(TokenType.ASTERISK):
1382
+ non_null = True
1383
+
1365
1384
  class_name = self._advance().value
1366
1385
 
1367
- node = ASTNode('class', value={'name': class_name}, children=[])
1386
+ node = ASTNode('class', value={'name': class_name, 'non_null': non_null}, children=[])
1368
1387
  self._expect(TokenType.BLOCK_START)
1369
1388
 
1370
1389
  while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
@@ -1400,6 +1419,17 @@ class CSSLParser:
1400
1419
  return node
1401
1420
 
1402
1421
  def _parse_define(self) -> ASTNode:
1422
+ """Parse define function declaration.
1423
+
1424
+ Syntax:
1425
+ define MyFunc(args) { }
1426
+ define *MyFunc(args) { } // Non-null: must never return None
1427
+ """
1428
+ # Check for * prefix (non-null function - must return non-null)
1429
+ non_null = False
1430
+ if self._match(TokenType.ASTERISK):
1431
+ non_null = True
1432
+
1403
1433
  name = self._advance().value
1404
1434
  params = []
1405
1435
 
@@ -1415,6 +1445,9 @@ class CSSLParser:
1415
1445
  # Handle reference operator &
1416
1446
  if self._match(TokenType.AMPERSAND):
1417
1447
  param_info['ref'] = True
1448
+ # Handle * prefix for non-null parameters
1449
+ if self._match(TokenType.ASTERISK):
1450
+ param_info['non_null'] = True
1418
1451
  # Get parameter name
1419
1452
  if self._check(TokenType.IDENTIFIER):
1420
1453
  param_name = self._advance().value
@@ -1435,7 +1468,7 @@ class CSSLParser:
1435
1468
  break
1436
1469
  self._expect(TokenType.PAREN_END)
1437
1470
 
1438
- node = ASTNode('function', value={'name': name, 'params': params}, children=[])
1471
+ node = ASTNode('function', value={'name': name, 'params': params, 'non_null': non_null}, children=[])
1439
1472
  self._expect(TokenType.BLOCK_START)
1440
1473
 
1441
1474
  while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
@@ -2610,12 +2643,24 @@ class CSSLParser:
2610
2643
  'init_values': init_values
2611
2644
  })
2612
2645
 
2613
- # Check for type-parameterized function call: OpenFind<string>(0)
2646
+ # Check for type-parameterized function call: OpenFind<string>(0) or OpenFind<dynamic, "name">
2614
2647
  if name in TYPE_PARAM_FUNCTIONS and self._check(TokenType.COMPARE_LT):
2615
2648
  self._advance() # consume <
2616
2649
  type_param = 'dynamic'
2650
+ param_name = None # Optional: named parameter search
2651
+
2617
2652
  if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2618
2653
  type_param = self._advance().value
2654
+
2655
+ # Check for second parameter: OpenFind<type, "name">
2656
+ if self._check(TokenType.COMMA):
2657
+ self._advance() # consume comma
2658
+ # Expect a string literal for the parameter name
2659
+ if self._check(TokenType.STRING):
2660
+ param_name = self._advance().value
2661
+ elif self._check(TokenType.IDENTIFIER):
2662
+ param_name = self._advance().value
2663
+
2619
2664
  self._expect(TokenType.COMPARE_GT) # consume >
2620
2665
 
2621
2666
  # Must be followed by ()
@@ -2632,6 +2677,7 @@ class CSSLParser:
2632
2677
  return ASTNode('typed_call', value={
2633
2678
  'name': name,
2634
2679
  'type_param': type_param,
2680
+ 'param_name': param_name, # Named parameter for OpenFind
2635
2681
  'args': args
2636
2682
  })
2637
2683
 
@@ -772,12 +772,16 @@ class CSSLRuntime:
772
772
  """Execute typed variable declaration: type<T> varName = value;
773
773
 
774
774
  Creates appropriate type instances for stack, vector, datastruct, etc.
775
+
776
+ The * prefix indicates a non-nullable variable (can never be None/null).
777
+ Example: vector<dynamic> *MyVector - can never contain None values.
775
778
  """
776
779
  decl = node.value
777
780
  type_name = decl.get('type')
778
781
  element_type = decl.get('element_type', 'dynamic')
779
782
  var_name = decl.get('name')
780
783
  value_node = decl.get('value')
784
+ non_null = decl.get('non_null', False)
781
785
 
782
786
  # Create the appropriate type instance
783
787
  if type_name == 'stack':
@@ -825,11 +829,32 @@ class CSSLRuntime:
825
829
  # For container types, the value might be initialization data
826
830
  init_value = self._evaluate(value_node)
827
831
  if isinstance(init_value, (list, tuple)):
832
+ # For non-null containers, filter out None values
833
+ if non_null:
834
+ init_value = [v for v in init_value if v is not None]
828
835
  instance.extend(init_value)
829
836
  elif init_value is not None:
830
837
  if hasattr(instance, 'append'):
831
838
  instance.append(init_value)
832
839
 
840
+ # Non-null enforcement: container types get wrapped to filter None on operations
841
+ if non_null:
842
+ # Mark the instance as non-null for runtime checks
843
+ if hasattr(instance, '_non_null'):
844
+ instance._non_null = True
845
+ # Track non-null variables for assignment enforcement
846
+ if not hasattr(self, '_non_null_vars'):
847
+ self._non_null_vars = set()
848
+ self._non_null_vars.add(var_name)
849
+
850
+ # Ensure initial value is not None for non-null variables
851
+ if instance is None:
852
+ raise CSSLRuntimeError(
853
+ f"Non-null variable '*{var_name}' cannot be initialized to None",
854
+ node.line,
855
+ hint="Use a default value or remove the * prefix"
856
+ )
857
+
833
858
  # Check for global modifier
834
859
  modifiers = decl.get('modifiers', [])
835
860
  is_global = 'global' in modifiers
@@ -1031,8 +1056,16 @@ class CSSLRuntime:
1031
1056
  # Check if this is an 'open' parameter - receives all args as a list
1032
1057
  if param_type == 'open' or param_name == 'Params':
1033
1058
  # 'open Params' receives all arguments as a list
1034
- new_scope.set(param_name, list(args))
1035
- new_scope.set('Params', list(args)) # Also set 'Params' for OpenFind
1059
+ # Check for non_null flag: open *Params filters out None values
1060
+ is_non_null = isinstance(param, dict) and param.get('non_null', False)
1061
+ args_list = list(args)
1062
+ if is_non_null:
1063
+ args_list = [a for a in args_list if a is not None]
1064
+ # Also filter kwargs
1065
+ kwargs = {k: v for k, v in kwargs.items() if v is not None}
1066
+ new_scope.set(param_name, args_list)
1067
+ new_scope.set('Params', args_list) # Also set 'Params' for OpenFind
1068
+ new_scope.set('_OpenKwargs', kwargs) # Store kwargs for OpenFind<type, "name">
1036
1069
  elif param_name in kwargs:
1037
1070
  # Named argument takes priority
1038
1071
  new_scope.set(param_name, kwargs[param_name])
@@ -2786,19 +2819,20 @@ class CSSLRuntime:
2786
2819
  )
2787
2820
 
2788
2821
  def _eval_typed_call(self, node: ASTNode) -> Any:
2789
- """Evaluate typed function call like OpenFind<string>(0)"""
2822
+ """Evaluate typed function call like OpenFind<string>(0) or OpenFind<dynamic, "name">"""
2790
2823
  name = node.value.get('name')
2791
2824
  type_param = node.value.get('type_param', 'dynamic')
2825
+ param_name = node.value.get('param_name') # For OpenFind<type, "name">
2792
2826
  args = [self._evaluate(a) for a in node.value.get('args', [])]
2793
2827
 
2794
- # Handle OpenFind<type>(index)
2828
+ # Handle OpenFind<type>(index) or OpenFind<type, "name">
2795
2829
  if name == 'OpenFind':
2796
2830
  # OpenFind searches for a value of the specified type
2797
2831
  # from the open parameters in scope
2798
2832
  open_params = self.scope.get('Params') or []
2799
- index = args[0] if args else 0
2833
+ open_kwargs = self.scope.get('_OpenKwargs') or {}
2800
2834
 
2801
- # Search for value of matching type at or near the index
2835
+ # Type mapping for type checking
2802
2836
  type_map = {
2803
2837
  'string': str, 'str': str,
2804
2838
  'int': int, 'integer': int,
@@ -2806,10 +2840,23 @@ class CSSLRuntime:
2806
2840
  'bool': bool, 'boolean': bool,
2807
2841
  'list': list, 'array': list,
2808
2842
  'dict': dict, 'json': dict,
2843
+ 'dynamic': None, # Accept any type
2809
2844
  }
2810
-
2811
2845
  target_type = type_map.get(type_param.lower())
2812
2846
 
2847
+ # If param_name is specified, search by name in kwargs
2848
+ # OpenFind<dynamic, "tasks"> -> searches for MyFunc(tasks="value")
2849
+ if param_name:
2850
+ if param_name in open_kwargs:
2851
+ value = open_kwargs[param_name]
2852
+ # Type check if not dynamic
2853
+ if target_type is None or isinstance(value, target_type):
2854
+ return value
2855
+ return None
2856
+
2857
+ # Otherwise, search by index in positional args
2858
+ index = args[0] if args else 0
2859
+
2813
2860
  if isinstance(open_params, (list, tuple)):
2814
2861
  # Find first matching type starting from index
2815
2862
  for i in range(index, len(open_params)):
@@ -2830,8 +2877,8 @@ class CSSLRuntime:
2830
2877
  raise CSSLRuntimeError(
2831
2878
  f"Unknown typed function: {name}<{type_param}>",
2832
2879
  node.line,
2833
- context=f"Available typed functions: OpenFind<type>",
2834
- hint="Typed functions use format: FunctionName<Type>(args)"
2880
+ context=f"Available typed functions: OpenFind<type>, OpenFind<type, \"name\">",
2881
+ hint="Use OpenFind<type>(index) for positional or OpenFind<type, \"name\"> for named params"
2835
2882
  )
2836
2883
 
2837
2884
  def _eval_new(self, node: ASTNode) -> CSSLInstance:
@@ -2,7 +2,7 @@
2
2
  "name": "cssl",
3
3
  "displayName": "CSSL Language",
4
4
  "description": "Professional syntax highlighting, snippets, and language support for CSSL scripts (.cssl, .cssl-pl, .cssl-mod)",
5
- "version": "1.3.1",
5
+ "version": "1.4.0",
6
6
  "publisher": "IncludeCPP",
7
7
  "icon": "images/cssl.png",
8
8
  "engines": {
@@ -28,9 +28,63 @@
28
28
  { "include": "#builtins" },
29
29
  { "include": "#operators" },
30
30
  { "include": "#constants" },
31
+ { "include": "#non-null-declarations" },
31
32
  { "include": "#variables" }
32
33
  ],
33
34
  "repository": {
35
+ "non-null-declarations": {
36
+ "patterns": [
37
+ {
38
+ "comment": "Non-null class: class *ClassName - light pink",
39
+ "name": "meta.class.non-null.cssl",
40
+ "match": "\\b(class)\\s+(\\*)([a-zA-Z_][a-zA-Z0-9_]*)",
41
+ "captures": {
42
+ "1": { "name": "storage.type.class.cssl" },
43
+ "2": { "name": "keyword.operator.non-null.cssl" },
44
+ "3": { "name": "entity.name.class.non-null.cssl" }
45
+ }
46
+ },
47
+ {
48
+ "comment": "Non-null function: define *FuncName - light pink",
49
+ "name": "meta.function.non-null.cssl",
50
+ "match": "\\b(define)\\s+(\\*)([a-zA-Z_][a-zA-Z0-9_]*)",
51
+ "captures": {
52
+ "1": { "name": "storage.type.function.cssl" },
53
+ "2": { "name": "keyword.operator.non-null.cssl" },
54
+ "3": { "name": "entity.name.function.non-null.cssl" }
55
+ }
56
+ },
57
+ {
58
+ "comment": "Non-null typed variable: type<T> *varName - light pink",
59
+ "name": "meta.declaration.non-null.cssl",
60
+ "match": "\\b(int|string|float|bool|dynamic|vector|stack|array|list|dict|json|datastruct|iterator)\\s*(<[^>]+>)?\\s+(\\*)([a-zA-Z_][a-zA-Z0-9_]*)",
61
+ "captures": {
62
+ "1": { "name": "storage.type.cssl" },
63
+ "2": { "name": "storage.type.generic.cssl" },
64
+ "3": { "name": "keyword.operator.non-null.cssl" },
65
+ "4": { "name": "variable.other.non-null.cssl" }
66
+ }
67
+ },
68
+ {
69
+ "comment": "Non-null open parameter: open *Params - light pink",
70
+ "name": "meta.parameter.non-null.cssl",
71
+ "match": "\\b(open)\\s+(\\*)([a-zA-Z_][a-zA-Z0-9_]*)",
72
+ "captures": {
73
+ "1": { "name": "storage.modifier.open.cssl" },
74
+ "2": { "name": "keyword.operator.non-null.cssl" },
75
+ "3": { "name": "variable.parameter.non-null.cssl" }
76
+ }
77
+ },
78
+ {
79
+ "comment": "Return type -> * (non-null return)",
80
+ "name": "meta.return-type.non-null.cssl",
81
+ "match": "->\\s*\\*",
82
+ "captures": {
83
+ "0": { "name": "keyword.operator.non-null.return.cssl" }
84
+ }
85
+ }
86
+ ]
87
+ },
34
88
  "super-functions": {
35
89
  "patterns": [
36
90
  {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "IncludeCPP"
7
- version = "3.8.1"
7
+ version = "3.8.2"
8
8
  description = "Professional C++ Python bindings with type-generic templates, pystubs and native threading"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes