opencode-pyneruntime 6.6.4__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.
Files changed (261) hide show
  1. opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
  2. opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
  3. opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
  4. opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
  5. opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
  6. opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
  7. opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
  8. pynecore/__init__.py +6 -0
  9. pynecore/cli/__init__.py +2 -0
  10. pynecore/cli/app.py +238 -0
  11. pynecore/cli/commands/__init__.py +343 -0
  12. pynecore/cli/commands/benchmark.py +186 -0
  13. pynecore/cli/commands/compile.py +198 -0
  14. pynecore/cli/commands/data.py +857 -0
  15. pynecore/cli/commands/debug.py +63 -0
  16. pynecore/cli/commands/optimize.py +956 -0
  17. pynecore/cli/commands/plugin.py +242 -0
  18. pynecore/cli/commands/run.py +2006 -0
  19. pynecore/cli/pluggable.py +132 -0
  20. pynecore/cli/utils/__init__.py +0 -0
  21. pynecore/cli/utils/api_error_handler.py +168 -0
  22. pynecore/cli/utils/broker_picker.py +330 -0
  23. pynecore/cli/utils/error_hook.py +28 -0
  24. pynecore/cli/utils/keyreader.py +178 -0
  25. pynecore/cli/utils/provider_picker.py +19 -0
  26. pynecore/cli/utils/symbol_browser.py +1149 -0
  27. pynecore/core/__init__.py +0 -0
  28. pynecore/core/aggregator.py +257 -0
  29. pynecore/core/bar_magnifier.py +168 -0
  30. pynecore/core/broker/__init__.py +64 -0
  31. pynecore/core/broker/defaults.py +113 -0
  32. pynecore/core/broker/disappearance.py +927 -0
  33. pynecore/core/broker/emulator.py +345 -0
  34. pynecore/core/broker/exceptions.py +346 -0
  35. pynecore/core/broker/idempotency.py +401 -0
  36. pynecore/core/broker/intent_builder.py +334 -0
  37. pynecore/core/broker/journal.py +1785 -0
  38. pynecore/core/broker/models.py +1600 -0
  39. pynecore/core/broker/native_failsafe_manager.py +1436 -0
  40. pynecore/core/broker/one_way_emulator.py +1128 -0
  41. pynecore/core/broker/position.py +787 -0
  42. pynecore/core/broker/run_identity.py +126 -0
  43. pynecore/core/broker/software_entry_stop_engine.py +351 -0
  44. pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
  45. pynecore/core/broker/spot_inventory.py +1327 -0
  46. pynecore/core/broker/storage.py +2655 -0
  47. pynecore/core/broker/store_helpers.py +2161 -0
  48. pynecore/core/broker/sync_engine.py +16070 -0
  49. pynecore/core/broker/validation.py +382 -0
  50. pynecore/core/class_property.py +7 -0
  51. pynecore/core/config.py +392 -0
  52. pynecore/core/csv_file.py +547 -0
  53. pynecore/core/currency.py +262 -0
  54. pynecore/core/data_converter.py +1002 -0
  55. pynecore/core/datetime.py +296 -0
  56. pynecore/core/download_info.py +71 -0
  57. pynecore/core/download_runner.py +274 -0
  58. pynecore/core/htf_aggregator.py +181 -0
  59. pynecore/core/import_hook.py +358 -0
  60. pynecore/core/instance_state.py +494 -0
  61. pynecore/core/live_ltf_collector.py +442 -0
  62. pynecore/core/live_ltf_window.py +189 -0
  63. pynecore/core/live_runner.py +1347 -0
  64. pynecore/core/module_property.py +26 -0
  65. pynecore/core/ohlcv_file.py +1888 -0
  66. pynecore/core/overload.py +371 -0
  67. pynecore/core/pine_cast.py +113 -0
  68. pynecore/core/pine_export.py +95 -0
  69. pynecore/core/pine_method.py +244 -0
  70. pynecore/core/pine_range.py +86 -0
  71. pynecore/core/pine_udt.py +69 -0
  72. pynecore/core/plugin/__init__.py +394 -0
  73. pynecore/core/plugin/broker.py +781 -0
  74. pynecore/core/plugin/cli.py +96 -0
  75. pynecore/core/plugin/live_provider.py +208 -0
  76. pynecore/core/plugin/provider.py +331 -0
  77. pynecore/core/provider_string.py +148 -0
  78. pynecore/core/random.py +40 -0
  79. pynecore/core/resampler.py +686 -0
  80. pynecore/core/safe_convert.py +64 -0
  81. pynecore/core/script.py +1011 -0
  82. pynecore/core/script_runner.py +3202 -0
  83. pynecore/core/security.py +1749 -0
  84. pynecore/core/security_process.py +1253 -0
  85. pynecore/core/security_shm.py +456 -0
  86. pynecore/core/series.py +417 -0
  87. pynecore/core/strategy_stats.py +669 -0
  88. pynecore/core/symbol_map.py +134 -0
  89. pynecore/core/syminfo.py +505 -0
  90. pynecore/core/viz.py +591 -0
  91. pynecore/lib/__init__.py +1771 -0
  92. pynecore/lib/_fixnan.py +32 -0
  93. pynecore/lib/_math_stateful.py +202 -0
  94. pynecore/lib/_timeframe_change.py +101 -0
  95. pynecore/lib/adjustment.py +6 -0
  96. pynecore/lib/alert.py +39 -0
  97. pynecore/lib/alert.pyi +14 -0
  98. pynecore/lib/array.py +1051 -0
  99. pynecore/lib/barmerge.py +60 -0
  100. pynecore/lib/barstate.py +30 -0
  101. pynecore/lib/box.py +415 -0
  102. pynecore/lib/chart.py +128 -0
  103. pynecore/lib/color.py +152 -0
  104. pynecore/lib/color.pyi +50 -0
  105. pynecore/lib/currency.py +62 -0
  106. pynecore/lib/dayofweek.py +36 -0
  107. pynecore/lib/dayofweek.pyi +18 -0
  108. pynecore/lib/display.py +8 -0
  109. pynecore/lib/dividends.py +9 -0
  110. pynecore/lib/earnings.py +11 -0
  111. pynecore/lib/extend.py +6 -0
  112. pynecore/lib/font.py +5 -0
  113. pynecore/lib/footprint.py +79 -0
  114. pynecore/lib/format.py +11 -0
  115. pynecore/lib/hline.py +67 -0
  116. pynecore/lib/hline.pyi +24 -0
  117. pynecore/lib/label.py +409 -0
  118. pynecore/lib/line.py +433 -0
  119. pynecore/lib/linefill.py +93 -0
  120. pynecore/lib/location.py +11 -0
  121. pynecore/lib/log.py +362 -0
  122. pynecore/lib/map.py +150 -0
  123. pynecore/lib/math.py +385 -0
  124. pynecore/lib/matrix.py +708 -0
  125. pynecore/lib/order.py +8 -0
  126. pynecore/lib/pivotpointtype.py +8 -0
  127. pynecore/lib/plot.py +95 -0
  128. pynecore/lib/plot.pyi +33 -0
  129. pynecore/lib/polyline.py +91 -0
  130. pynecore/lib/position.py +15 -0
  131. pynecore/lib/request.py +281 -0
  132. pynecore/lib/runtime.py +5 -0
  133. pynecore/lib/scale.py +9 -0
  134. pynecore/lib/session.py +267 -0
  135. pynecore/lib/session.pyi +12 -0
  136. pynecore/lib/shape.py +18 -0
  137. pynecore/lib/size.py +12 -0
  138. pynecore/lib/splits.py +4 -0
  139. pynecore/lib/strategy/__init__.py +4778 -0
  140. pynecore/lib/strategy/closedtrades.py +347 -0
  141. pynecore/lib/strategy/closedtrades.pyi +53 -0
  142. pynecore/lib/strategy/commission.py +9 -0
  143. pynecore/lib/strategy/direction.py +9 -0
  144. pynecore/lib/strategy/oca.py +13 -0
  145. pynecore/lib/strategy/opentrades.py +281 -0
  146. pynecore/lib/strategy/opentrades.pyi +49 -0
  147. pynecore/lib/strategy/risk.py +109 -0
  148. pynecore/lib/string.py +649 -0
  149. pynecore/lib/syminfo.py +84 -0
  150. pynecore/lib/ta.py +2230 -0
  151. pynecore/lib/table.py +290 -0
  152. pynecore/lib/text.py +17 -0
  153. pynecore/lib/ticker.py +207 -0
  154. pynecore/lib/timeframe.py +293 -0
  155. pynecore/lib/volume_row.py +67 -0
  156. pynecore/lib/xloc.py +4 -0
  157. pynecore/lib/yloc.py +5 -0
  158. pynecore/providers/__init__.py +0 -0
  159. pynecore/providers/ccxt.py +664 -0
  160. pynecore/providers/replay.py +187 -0
  161. pynecore/pynesys/__init__.py +0 -0
  162. pynecore/pynesys/api.py +498 -0
  163. pynecore/pynesys/compiler.py +112 -0
  164. pynecore/standalone.py +99 -0
  165. pynecore/testing/__init__.py +1 -0
  166. pynecore/testing/broker_lab/__init__.py +41 -0
  167. pynecore/testing/broker_lab/__main__.py +5 -0
  168. pynecore/testing/broker_lab/cli.py +87 -0
  169. pynecore/testing/broker_lab/generate.py +47 -0
  170. pynecore/testing/broker_lab/model.py +84 -0
  171. pynecore/testing/broker_lab/reference.py +645 -0
  172. pynecore/testing/broker_lab/runner.py +372 -0
  173. pynecore/testing/broker_lab/scheduler.py +50 -0
  174. pynecore/testing/broker_lab/subprocess.py +73 -0
  175. pynecore/transformers/__init__.py +0 -0
  176. pynecore/transformers/builtin_shadow.py +136 -0
  177. pynecore/transformers/closure_arguments_transformer.py +428 -0
  178. pynecore/transformers/display_rewrite.py +140 -0
  179. pynecore/transformers/dynamic_default.py +147 -0
  180. pynecore/transformers/function_isolation.py +757 -0
  181. pynecore/transformers/import_lifter.py +61 -0
  182. pynecore/transformers/import_normalizer.py +328 -0
  183. pynecore/transformers/inline_series_hoist.py +178 -0
  184. pynecore/transformers/input_transformer.py +175 -0
  185. pynecore/transformers/lib_series.py +201 -0
  186. pynecore/transformers/locations.py +70 -0
  187. pynecore/transformers/module_properties.json +3387 -0
  188. pynecore/transformers/module_property.py +221 -0
  189. pynecore/transformers/ne_guard.py +70 -0
  190. pynecore/transformers/persistent.py +320 -0
  191. pynecore/transformers/persistent_series.py +76 -0
  192. pynecore/transformers/safe_convert_transformer.py +97 -0
  193. pynecore/transformers/safe_division_transformer.py +95 -0
  194. pynecore/transformers/script_requirements.py +308 -0
  195. pynecore/transformers/security.py +752 -0
  196. pynecore/transformers/security_instantiation.py +274 -0
  197. pynecore/transformers/series.py +275 -0
  198. pynecore/transformers/slot_layout.py +381 -0
  199. pynecore/transformers/type_checking_stripper.py +25 -0
  200. pynecore/transformers/unused_series_detector.py +267 -0
  201. pynecore/types/__init__.py +21 -0
  202. pynecore/types/alert.py +5 -0
  203. pynecore/types/barmerge.py +5 -0
  204. pynecore/types/base.py +39 -0
  205. pynecore/types/box.py +37 -0
  206. pynecore/types/chart.py +17 -0
  207. pynecore/types/color.py +107 -0
  208. pynecore/types/currency.py +5 -0
  209. pynecore/types/datetime.py +6 -0
  210. pynecore/types/display.py +5 -0
  211. pynecore/types/dividends.py +5 -0
  212. pynecore/types/earnings.py +5 -0
  213. pynecore/types/extend.py +5 -0
  214. pynecore/types/font.py +5 -0
  215. pynecore/types/footprint.py +41 -0
  216. pynecore/types/format.py +5 -0
  217. pynecore/types/hline.py +24 -0
  218. pynecore/types/ib_persistent.py +8 -0
  219. pynecore/types/ib_persistent.pyi +10 -0
  220. pynecore/types/label.py +35 -0
  221. pynecore/types/line.py +32 -0
  222. pynecore/types/linefill.py +13 -0
  223. pynecore/types/location.py +5 -0
  224. pynecore/types/matrix.py +999 -0
  225. pynecore/types/na.py +237 -0
  226. pynecore/types/na.pyi +83 -0
  227. pynecore/types/ohlcv.py +12 -0
  228. pynecore/types/order.py +5 -0
  229. pynecore/types/persistent.py +8 -0
  230. pynecore/types/persistent.pyi +13 -0
  231. pynecore/types/pine_types.py +11 -0
  232. pynecore/types/pine_types.pyi +15 -0
  233. pynecore/types/pivotpointtype.py +5 -0
  234. pynecore/types/plot.py +12 -0
  235. pynecore/types/plot_meta.py +60 -0
  236. pynecore/types/polyline.py +40 -0
  237. pynecore/types/position.py +5 -0
  238. pynecore/types/scale.py +5 -0
  239. pynecore/types/script_type.py +15 -0
  240. pynecore/types/series.py +23 -0
  241. pynecore/types/series.pyi +19 -0
  242. pynecore/types/session.py +35 -0
  243. pynecore/types/shape.py +5 -0
  244. pynecore/types/size.py +5 -0
  245. pynecore/types/source.py +33 -0
  246. pynecore/types/splits.py +5 -0
  247. pynecore/types/strategy.py +45 -0
  248. pynecore/types/table.py +87 -0
  249. pynecore/types/text.py +13 -0
  250. pynecore/types/type_checker.py +7 -0
  251. pynecore/types/type_checker.pyi +48 -0
  252. pynecore/types/volume_row.py +36 -0
  253. pynecore/types/weekdays.py +11 -0
  254. pynecore/types/xloc.py +5 -0
  255. pynecore/types/yloc.py +5 -0
  256. pynecore/utils/__init__.py +0 -0
  257. pynecore/utils/file_utils.py +50 -0
  258. pynecore/utils/rich/__init__.py +0 -0
  259. pynecore/utils/rich/date_column.py +25 -0
  260. pynecore/utils/sequence_view.py +92 -0
  261. pynecore/utils/stdlib_checker.py +17 -0
@@ -0,0 +1,371 @@
1
+ """
2
+ Function overloading with per-implementation instance state (slot scheme).
3
+
4
+ The ``@overload`` decorator registers every implementation under the
5
+ function's qualified name and binds the name to a single dispatcher. The
6
+ dispatcher selects the implementation by argument types (Pine Script
7
+ compatible matching) and calls it through a per-anchor bound cache:
8
+
9
+ - Call sites reach the dispatcher on the UNIFORM route — the caller anchors
10
+ it in its own state vector with ``__bind_any__``, whose ``_bind_target``
11
+ finds the dispatcher's ``__pyne_bind__`` factory and stores a fresh
12
+ anchored dispatcher in the anchor slot.
13
+ - One anchor holds one bound callable PER IMPLEMENTATION: a call site where
14
+ different argument types win on different bars keeps a separate persistent
15
+ instance for every implementation, while one implementation's state
16
+ persists across the bars it wins on.
17
+ - State-carrying implementations are bound through
18
+ ``instance_state._bind_target`` (their ``__pyne_layout__`` comes from the
19
+ ``@__attach_layout__`` decorator the slot transform inserts below
20
+ ``@overload``); stateless implementations are called raw.
21
+ - Calling the dispatcher directly (no anchor — module level, non-transformed
22
+ code, function values passed to builtins) falls back to the dispatcher's
23
+ own module-lifetime bound cache: one shared instance per implementation,
24
+ the same semantics the legacy module-global scope gave such calls.
25
+
26
+ Implementation matching skips the hidden state parameter the slot transform
27
+ injects (``__state__`` or the scope-qualified ``__state·{scope}__`` form):
28
+ signatures and parameter types are computed from the VISIBLE parameters
29
+ only, and the state argument is prepended by the bound partial, never by
30
+ the caller.
31
+ """
32
+ from typing import (TypeVar, Callable, get_type_hints, overload as typing_overload,
33
+ Any, Type, Union, get_args, get_origin, cast)
34
+ from functools import wraps, partial
35
+ from inspect import signature
36
+ from collections import defaultdict
37
+ from types import FunctionType, UnionType
38
+
39
+ from .instance_state import _bind_target, _make_state, register_shared_cache
40
+ from ..types.base import StrLiteral
41
+ from ..types.na import NA
42
+
43
+ __all__ = ['overload']
44
+
45
+ T = TypeVar('T')
46
+
47
+
48
+ def _is_state_param(name: str) -> bool:
49
+ """Whether a parameter is the hidden state parameter injected by the
50
+ slot-layout transform.
51
+
52
+ :param name: Parameter name.
53
+ :return: True for ``__state__`` and the scope-qualified form.
54
+ """
55
+ return name == '__state__' or (name.startswith('__state·') and name.endswith('__'))
56
+
57
+
58
+ class Implementation:
59
+ __slots__ = ('func', 'sig', 'type_hints', 'param_types')
60
+ func: FunctionType
61
+ sig: Any # Signature object of the VISIBLE parameters
62
+ type_hints: dict
63
+ param_types: tuple # Cached visible parameter types for quick checking
64
+
65
+ def __init__(self, func: FunctionType):
66
+ self.update(func)
67
+
68
+ def update(self, func: FunctionType) -> None:
69
+ """(Re)bind to the implementation function and cache its matching
70
+ metadata. Re-running a module re-decorates the same source lines —
71
+ the dispatcher and the Implementation objects survive, only the
72
+ function objects are swapped.
73
+
74
+ :param func: The (possibly re-created) implementation function.
75
+ """
76
+ if getattr(self, 'func', None) is not None and func.__code__ is self.func.__code__:
77
+ # The same source line re-executed (library mains and nested
78
+ # scopes re-run every bar): only the closure cells and default
79
+ # values are new, the matching metadata is unchanged — skip the
80
+ # expensive signature()/get_type_hints() recompute
81
+ self.func = func
82
+ return
83
+ sig = signature(func)
84
+ params = list(sig.parameters.values())
85
+ if params and _is_state_param(params[0].name):
86
+ # Hide the injected state parameter from matching: arity and
87
+ # types are checked against what the call site passes, the
88
+ # state argument comes from the bound partial
89
+ params = params[1:]
90
+ hints = get_type_hints(func)
91
+ self.func = func
92
+ self.sig = sig.replace(parameters=params)
93
+ self.type_hints = hints
94
+ self.param_types = tuple((p.name, hints.get(p.name, Any)) for p in params)
95
+
96
+
97
+ _registry: dict[str, list[Implementation]] = defaultdict(list)
98
+ _implementations: dict[str, Implementation] = {} # Store implementations separately
99
+ _dispatchers: dict[str, Callable] = {} # Store dispatchers separately
100
+
101
+
102
+ def _check_type(value: Any, expected_type: Type) -> bool:
103
+ """Cached type checking for better performance with Pine Script compatibility"""
104
+ # ``Any`` matches every value. Parameters without a type hint default to ``Any``
105
+ # (see ``param_types`` below), and the compiler threads a closure variable in as a
106
+ # leading, unannotated parameter -- both surface here as ``Any`` and must accept any
107
+ # argument, like an unconstrained Pine parameter. isinstance() rejects ``Any``.
108
+ if expected_type is Any:
109
+ return True
110
+
111
+ # Parameterized containers (list[T], dict[K, V], ...): isinstance() rejects
112
+ # parameterized generics. Match on the container type, then discriminate on a
113
+ # sample element -- overloads can differ only in their element types
114
+ # (map<string, string> vs map<string, float>)
115
+ _origin = get_origin(expected_type)
116
+ if isinstance(_origin, type) and _origin is not UnionType:
117
+ if isinstance(value, _origin):
118
+ _args = get_args(expected_type)
119
+ if _args and isinstance(value, dict):
120
+ if value:
121
+ _key, _val = next(iter(value.items()))
122
+ return _check_type(_key, _args[0]) and _check_type(_val, _args[1])
123
+ elif _args and isinstance(value, (list, tuple)) and value:
124
+ return _check_type(value[0], _args[0])
125
+ return True
126
+ expected_type = cast(Type, _origin)
127
+
128
+ # Direct type match
129
+ if isinstance(value, expected_type):
130
+ return True
131
+
132
+ # Pine Script-like int to float conversion
133
+ if expected_type is float and isinstance(value, int):
134
+ return True
135
+
136
+ # Pine Script allows plain str where StrLiteral subtypes are expected (e.g. size, xloc)
137
+ if isinstance(value, str) and isinstance(expected_type, type) and issubclass(expected_type, StrLiteral):
138
+ return True
139
+
140
+ # Handle NA values - Pine Script allows NA for any basic type
141
+ if isinstance(value, NA):
142
+ # Check if expected_type is a Pine Script basic type
143
+ if expected_type in (int, float, str, bool):
144
+ return True
145
+
146
+ # For Union types containing basic types, NA is also acceptable
147
+ origin = get_origin(expected_type)
148
+ if origin in (Union, type(None) | type):
149
+ args = get_args(expected_type)
150
+ # If any of the Union members is a basic type, accept NA
151
+ if any(arg in (int, float, str, bool) for arg in args):
152
+ return True
153
+
154
+ # For non-basic types, check if NA's type matches
155
+ na_type = value.type
156
+ # A typeless `na` is assignable to anything, like in Pine
157
+ if na_type is None:
158
+ return True
159
+ # Handle the case when na_type is an actual instance and not a type
160
+ if not isinstance(na_type, type):
161
+ na_type = type(na_type)
162
+ return na_type is expected_type
163
+
164
+ # Handle Union types
165
+ origin = get_origin(expected_type)
166
+ if origin in (Union, type(None) | type):
167
+ return any(_check_type(value, t) for t in get_args(expected_type))
168
+
169
+ if hasattr(expected_type, '__instancecheck__'):
170
+ return expected_type.__instancecheck__(value)
171
+
172
+ return False
173
+
174
+
175
+ def _select(impls: list[Implementation], args: tuple, kwargs: dict) -> Implementation | None:
176
+ """Select the implementation matching a call's arguments.
177
+
178
+ :param impls: Registered implementations (registration order).
179
+ :param args: Positional arguments of the call.
180
+ :param kwargs: Keyword arguments of the call.
181
+ :return: The first matching implementation, or None.
182
+ """
183
+ # Quick path: try direct positional args match first
184
+ if not kwargs:
185
+ for impl in impls:
186
+ if len(args) == len(impl.param_types):
187
+ if all(_check_type(arg, type_)
188
+ for arg, (_, type_) in zip(args, impl.param_types)):
189
+ return impl
190
+
191
+ # Slower path: handle mixed args/kwargs and defaults
192
+ for impl in impls:
193
+ try:
194
+ bound = impl.sig.bind(*args, **kwargs)
195
+ bound.apply_defaults()
196
+
197
+ if all(_check_type(value, impl.type_hints[name])
198
+ for name, value in bound.arguments.items()
199
+ if name in impl.type_hints):
200
+ return impl
201
+ except TypeError:
202
+ continue
203
+ return None
204
+
205
+
206
+ def _type_token(value: Any) -> Any:
207
+ """Hashable token capturing exactly the properties ``_check_type``
208
+ discriminates on, so that two arguments with equal tokens are
209
+ interchangeable for implementation selection.
210
+
211
+ Scalars map to their type; NA carries its ``type`` marker; parameterized
212
+ containers sample one element, mirroring ``_check_type``'s element probe.
213
+ The token is conservative: distinct tokens never merge arguments that
214
+ ``_check_type`` could treat differently.
215
+
216
+ :param value: A call argument.
217
+ :return: A hashable selection token.
218
+ """
219
+ t = type(value)
220
+ if t is int or t is float or t is str or t is bool:
221
+ return t
222
+ if isinstance(value, NA):
223
+ return NA, value.type
224
+ if t is dict:
225
+ if value:
226
+ _k, _v = next(iter(value.items()))
227
+ return dict, type(_k), type(_v)
228
+ return (dict,)
229
+ if t is list or t is tuple:
230
+ if value:
231
+ return t, type(value[0])
232
+ return (t,)
233
+ return t
234
+
235
+
236
+ def _canonical_kwarg_renames(impls: list[Implementation],
237
+ names: tuple[str, ...]) -> tuple[tuple[str, str], ...]:
238
+ """Compute the keyword renames onto canonically renamed parameters.
239
+
240
+ An untyped call site emits a keyword argument under its original Pine
241
+ spelling while the library ``def`` declares ``name + '__ren__'``
242
+ (PyneComp's canonical rename; the same contract as
243
+ ``pine_method._adapt_exported_kwargs``, which cannot see through the
244
+ dispatcher). A keyword is renamed only when NO implementation declares
245
+ the raw name and at least one declares the suffixed image — a correct
246
+ call is never altered. Overloads of one export come from one compilation
247
+ unit, so a name's rename decision is identical across implementations.
248
+
249
+ :param impls: Registered implementations of the overload group.
250
+ :param names: Keyword argument names as emitted at the call site.
251
+ :return: ``(raw, canonical)`` pairs to apply; empty when nothing renames.
252
+ """
253
+ declared = {name for impl in impls for name, _ in impl.param_types}
254
+ return tuple((k, k + '__ren__') for k in names
255
+ if k not in declared and k + '__ren__' in declared)
256
+
257
+
258
+ def _anchored(impls: list[Implementation], qualname: str,
259
+ cache: dict[Implementation, tuple[Callable, list | None, Callable]] | None = None
260
+ ) -> Callable:
261
+ """Create an anchored dispatch entry with its own per-implementation
262
+ bound cache. ``__pyne_bind__`` hands these out, one per anchor; the
263
+ dispatcher itself is one too (the shared, anchorless fallback, whose
264
+ cache is registered for clearing on ``instance_state.reset()`` — anchor
265
+ caches die with their anchor, the dispatcher's would outlive the run).
266
+
267
+ :param impls: The registry list of the overload group (shared, live).
268
+ :param qualname: Qualified name for error messages.
269
+ :param cache: Externally held cache dict (the dispatcher's registered
270
+ one); per-anchor entries create their own.
271
+ :return: The dispatch callable.
272
+ """
273
+ _cache: dict[Implementation, tuple[Callable, list | None, Callable]] = \
274
+ {} if cache is None else cache
275
+ # Per-anchor selection memo: a call site invokes the dispatcher with the
276
+ # same argument shape every bar, so the matching implementation is cached
277
+ # by call shape and the full _select (with inspect binding) runs once.
278
+ # Implementation objects are module-lifetime stable (re-runs swap only
279
+ # impl.func, handled below), so this never goes stale across resets.
280
+ _select_cache: dict[tuple, Implementation] = {}
281
+ # Keyword-name adaptation memo (see _canonical_kwarg_renames): keyed by
282
+ # the call's keyword-name tuple, stable for the same reason _select_cache
283
+ # is — implementation signatures only change with a recompile.
284
+ _kw_rename_cache: dict[tuple[str, ...], tuple[tuple[str, str], ...]] = {}
285
+
286
+ def dispatch(*args: Any, **kwargs: Any) -> Any:
287
+ if kwargs:
288
+ names = tuple(kwargs)
289
+ renames = _kw_rename_cache.get(names)
290
+ if renames is None:
291
+ renames = _kw_rename_cache[names] = _canonical_kwarg_renames(impls, names)
292
+ for raw, canonical in renames:
293
+ kwargs[canonical] = kwargs.pop(raw)
294
+ # Selection key, inlined (this is per-bar hot code): a uniform hashable
295
+ # ``(positional_tokens, keyword_tokens)`` pair so the no-kwargs and
296
+ # with-kwargs forms can never collide. map() over _type_token beats a
297
+ # generator expression here. Equal keys guarantee the same impl.
298
+ pos = tuple(map(_type_token, args))
299
+ key = (pos, ()) if not kwargs else \
300
+ (pos, tuple((k, _type_token(v)) for k, v in kwargs.items()))
301
+ impl = _select_cache.get(key)
302
+ if impl is None:
303
+ impl = _select(impls, args, kwargs)
304
+ if impl is None:
305
+ raise TypeError(f"No matching implementation found for {qualname}: {args}, {kwargs}")
306
+ _select_cache[key] = impl
307
+ entry = _cache.get(impl)
308
+ if entry is None or entry[0] is not impl.func:
309
+ # First win at this anchor, or the implementation function was
310
+ # re-created by a re-execution of its defining scope (library
311
+ # mains re-run every bar). Keep the existing instance state and
312
+ # take the closure from the new function object — the same
313
+ # split pine_method._bound_method does
314
+ func = impl.func
315
+ layout: dict[str, Any] | None = getattr(func, '__pyne_layout__', None)
316
+ if layout is not None:
317
+ state = entry[1] if entry is not None and entry[1] is not None \
318
+ else _make_state(layout)
319
+ entry = _cache[impl] = (func, state, partial(func, state))
320
+ else:
321
+ entry = _cache[impl] = (func, None, _bind_target(func))
322
+ return entry[2](*args, **kwargs)
323
+
324
+ return dispatch
325
+
326
+
327
+ def overload(func: Callable[..., T]) -> Callable[..., T]:
328
+ """
329
+ Function overloading decorator with:
330
+ - Type checking cache
331
+ - Pre-calculated signatures and type hints (hidden state parameter excluded)
332
+ - Quick parameter matching
333
+ - Per-anchor instance state through ``__pyne_bind__``
334
+ - IDE type checking support via typing.overload
335
+ """
336
+ _func = cast(FunctionType, func)
337
+ qualname = _func.__module__ + '.' + _func.__qualname__
338
+ qualname_with_line = f"{qualname}:{_func.__code__.co_firstlineno}"
339
+
340
+ # Re-executed module: same dispatcher, rebind the implementation
341
+ _dispatcher = _dispatchers.get(qualname)
342
+ if _dispatcher is not None:
343
+ impl = _implementations.get(qualname_with_line)
344
+ if impl is not None:
345
+ impl.update(_func)
346
+ return _dispatcher
347
+
348
+ # Register with typing.overload for IDE support
349
+ typing_overload(func)
350
+
351
+ impl = Implementation(_func)
352
+ _implementations[qualname_with_line] = impl
353
+ _registry[qualname].append(impl)
354
+
355
+ if _dispatcher is None:
356
+ # The dispatcher must carry the implementation's metadata (__name__ in
357
+ # particular): for exported library functions the @export decorator sits
358
+ # above @overload and looks up the module-level Exported proxy by the
359
+ # wrapped callable's __name__.
360
+ _dispatcher = wraps(func)(_anchored(_registry[qualname], qualname,
361
+ register_shared_cache({})))
362
+ # @wraps copies the implementation's __dict__ too — including the
363
+ # __pyne_layout__ the slot transform attached. The dispatcher must
364
+ # not look state-carrying to the call-site classifier or to
365
+ # _bind_target.
366
+ _dispatcher.__dict__.pop('__pyne_layout__', None)
367
+ setattr(_dispatcher, '__pyne_bind__',
368
+ lambda: _anchored(_registry[qualname], qualname))
369
+ _dispatchers[qualname] = _dispatcher
370
+
371
+ return _dispatcher
@@ -0,0 +1,113 @@
1
+ from ..types.na import NA, na_float
2
+
3
+ from ..types.color import Color
4
+ from ..types.label import Label
5
+ from ..types.table import Table
6
+ from ..types.box import Box
7
+ from ..types.line import Line
8
+ from ..types.linefill import LineFill
9
+
10
+
11
+ def cast_color(x: Color | NA) -> Color:
12
+ """
13
+ Casts `na` to Color
14
+ :param x: The value to convert
15
+ :return: The casted value
16
+ """
17
+ return NA(Color) if isinstance(x, NA) else x
18
+
19
+
20
+ def cast_label(x: Label | NA) -> Label:
21
+ """
22
+ Casts `na` to Label
23
+
24
+ :param x: The value to convert
25
+ :return: The casted value
26
+ """
27
+ return NA(Label) if isinstance(x, NA) else x
28
+
29
+
30
+ def cast_table(x: Table | NA) -> Table:
31
+ """
32
+ Casts `na` to Table
33
+
34
+ :param x: The value to convert
35
+ :return: The casted value
36
+ """
37
+ return NA(Table) if isinstance(x, NA) else x
38
+
39
+
40
+ def cast_bool(x: bool | int | float | NA) -> bool:
41
+ """
42
+ Converts the x value to a bool value
43
+
44
+ :param x: The value to convert
45
+ :return: The casted value
46
+ """
47
+ if isinstance(x, NA) or x != x: # NA object or native nan
48
+ return False
49
+ return not not x
50
+
51
+
52
+ def cast_box(x: Box | NA) -> Box:
53
+ """
54
+ Casts `na` to Box
55
+
56
+ :param x: The value to convert
57
+ :return: The casted value
58
+ """
59
+ return NA(Box) if isinstance(x, NA) else x
60
+
61
+
62
+ def cast_int(x: int | float | NA) -> int:
63
+ """
64
+ Casts na or truncates float value to int
65
+
66
+ :param x: The value to convert
67
+ :return: The casted value
68
+ """
69
+ if isinstance(x, NA) or x != x: # NA object or native nan
70
+ return NA(int)
71
+ return int(x)
72
+
73
+
74
+ def cast_line(x: Line | NA) -> Line:
75
+ """
76
+ Casts `na` to Line
77
+
78
+ :param x: The value to convert
79
+ :return: The casted value
80
+ """
81
+ return NA(Line) if isinstance(x, NA) else x
82
+
83
+
84
+ def cast_float(x: float | int | NA) -> float:
85
+ """
86
+ Casts `na` to float
87
+
88
+ :param x: The value to convert
89
+ :return: The casted value
90
+ """
91
+ if isinstance(x, NA) or x != x: # NA object or native nan
92
+ return na_float
93
+ return float(x)
94
+
95
+
96
+ def cast_string(x: str | NA) -> str:
97
+ """
98
+ Casts `na` to string
99
+
100
+ :param x: The value to convert
101
+ :return: The casted value
102
+ """
103
+ return NA(str) if isinstance(x, NA) else x
104
+
105
+
106
+ def cast_linefill(x: LineFill | NA) -> LineFill:
107
+ """
108
+ Casts `na` to LineFill
109
+
110
+ :param x: The value to convert
111
+ :return: The casted value
112
+ """
113
+ return NA(LineFill) if isinstance(x, NA) else x
@@ -0,0 +1,95 @@
1
+ from typing import Callable, TypeVar, Generic, Optional, Any, Union, overload
2
+ import sys
3
+
4
+ __all__ = ['Exported', 'export']
5
+
6
+ F = TypeVar('F', bound=Callable[..., Any]) # Function type
7
+
8
+
9
+ class Exported(Generic[F]):
10
+ """
11
+ Function closure proxy with flexible type annotation support
12
+
13
+ Supports:
14
+ - Protocol with named parameters: Exported[MyProtocol]
15
+ - Callable types: Exported[Callable[[int, str], bool]]
16
+ - No annotation: Exported (falls back to Any)
17
+ """
18
+ __fn__: Optional[F] = None
19
+ __name__: str
20
+
21
+ def set(self, client: F):
22
+ """Set the client function"""
23
+ self.__fn__ = client
24
+ # Expose the client's name so callers that inspect the callable
25
+ # (e.g. method_call's builtin-method name check) see the real one
26
+ name = getattr(client, '__name__', None)
27
+ if name is not None:
28
+ self.__name__ = name
29
+
30
+ def __call__(self, *args, **kwargs) -> Any:
31
+ if self.__fn__ is None:
32
+ raise ValueError("Function has not been set yet")
33
+ return self.__fn__(*args, **kwargs)
34
+
35
+
36
+ @overload
37
+ def export(func: Callable) -> Callable:
38
+ ...
39
+
40
+
41
+ @overload
42
+ def export(*, func_globals: dict[str, Any]) -> Callable:
43
+ ...
44
+
45
+
46
+ def export(
47
+ func: Optional[Callable] = None,
48
+ *,
49
+ func_globals: Optional[dict[str, Any]] = None
50
+ ) -> Union[Callable, Callable[[Callable], Callable]]:
51
+ """
52
+ Export decorator that can work with or without parameters.
53
+ It is exporting the function closure to the global scope of the module.
54
+
55
+ Usage:
56
+ @export
57
+ def my_func(): pass
58
+
59
+ @export(func_globals=some_globals)
60
+ def my_func(): pass
61
+ """
62
+ # Get caller's globals once at decorator definition time
63
+ if func_globals is None:
64
+ func_globals = sys._getframe(1).f_globals
65
+
66
+ def decorator(f: Callable) -> Callable:
67
+ func_name = f.__name__
68
+ assert func_globals is not None
69
+
70
+ # Check if there's already something with the same name in globals
71
+ if func_name in func_globals:
72
+ existing = func_globals[func_name]
73
+ if isinstance(existing, Exported):
74
+ # Set the function in the existing proxy
75
+ existing.set(f)
76
+ return existing
77
+ elif callable(existing):
78
+ # Function already exists in global scope, just return it unchanged (decorator as decoration)
79
+ return f
80
+
81
+ # No proxy found, throw error explaining what's needed
82
+ raise ValueError(
83
+ f"No Exported proxy found for function '{func_name}' in global scope. "
84
+ f"You must create an Exported proxy first:\n"
85
+ f" {func_name} = Exported()\n"
86
+ f" @export\n"
87
+ f" def {func_name}(): ..."
88
+ )
89
+
90
+ if func is not None:
91
+ # Called without parentheses: @export
92
+ return decorator(func)
93
+ else:
94
+ # Called with parentheses: @export() or @export(func_globals=...)
95
+ return decorator