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.
- opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
- opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
- opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
- opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
- opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
- pynecore/__init__.py +6 -0
- pynecore/cli/__init__.py +2 -0
- pynecore/cli/app.py +238 -0
- pynecore/cli/commands/__init__.py +343 -0
- pynecore/cli/commands/benchmark.py +186 -0
- pynecore/cli/commands/compile.py +198 -0
- pynecore/cli/commands/data.py +857 -0
- pynecore/cli/commands/debug.py +63 -0
- pynecore/cli/commands/optimize.py +956 -0
- pynecore/cli/commands/plugin.py +242 -0
- pynecore/cli/commands/run.py +2006 -0
- pynecore/cli/pluggable.py +132 -0
- pynecore/cli/utils/__init__.py +0 -0
- pynecore/cli/utils/api_error_handler.py +168 -0
- pynecore/cli/utils/broker_picker.py +330 -0
- pynecore/cli/utils/error_hook.py +28 -0
- pynecore/cli/utils/keyreader.py +178 -0
- pynecore/cli/utils/provider_picker.py +19 -0
- pynecore/cli/utils/symbol_browser.py +1149 -0
- pynecore/core/__init__.py +0 -0
- pynecore/core/aggregator.py +257 -0
- pynecore/core/bar_magnifier.py +168 -0
- pynecore/core/broker/__init__.py +64 -0
- pynecore/core/broker/defaults.py +113 -0
- pynecore/core/broker/disappearance.py +927 -0
- pynecore/core/broker/emulator.py +345 -0
- pynecore/core/broker/exceptions.py +346 -0
- pynecore/core/broker/idempotency.py +401 -0
- pynecore/core/broker/intent_builder.py +334 -0
- pynecore/core/broker/journal.py +1785 -0
- pynecore/core/broker/models.py +1600 -0
- pynecore/core/broker/native_failsafe_manager.py +1436 -0
- pynecore/core/broker/one_way_emulator.py +1128 -0
- pynecore/core/broker/position.py +787 -0
- pynecore/core/broker/run_identity.py +126 -0
- pynecore/core/broker/software_entry_stop_engine.py +351 -0
- pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
- pynecore/core/broker/spot_inventory.py +1327 -0
- pynecore/core/broker/storage.py +2655 -0
- pynecore/core/broker/store_helpers.py +2161 -0
- pynecore/core/broker/sync_engine.py +16070 -0
- pynecore/core/broker/validation.py +382 -0
- pynecore/core/class_property.py +7 -0
- pynecore/core/config.py +392 -0
- pynecore/core/csv_file.py +547 -0
- pynecore/core/currency.py +262 -0
- pynecore/core/data_converter.py +1002 -0
- pynecore/core/datetime.py +296 -0
- pynecore/core/download_info.py +71 -0
- pynecore/core/download_runner.py +274 -0
- pynecore/core/htf_aggregator.py +181 -0
- pynecore/core/import_hook.py +358 -0
- pynecore/core/instance_state.py +494 -0
- pynecore/core/live_ltf_collector.py +442 -0
- pynecore/core/live_ltf_window.py +189 -0
- pynecore/core/live_runner.py +1347 -0
- pynecore/core/module_property.py +26 -0
- pynecore/core/ohlcv_file.py +1888 -0
- pynecore/core/overload.py +371 -0
- pynecore/core/pine_cast.py +113 -0
- pynecore/core/pine_export.py +95 -0
- pynecore/core/pine_method.py +244 -0
- pynecore/core/pine_range.py +86 -0
- pynecore/core/pine_udt.py +69 -0
- pynecore/core/plugin/__init__.py +394 -0
- pynecore/core/plugin/broker.py +781 -0
- pynecore/core/plugin/cli.py +96 -0
- pynecore/core/plugin/live_provider.py +208 -0
- pynecore/core/plugin/provider.py +331 -0
- pynecore/core/provider_string.py +148 -0
- pynecore/core/random.py +40 -0
- pynecore/core/resampler.py +686 -0
- pynecore/core/safe_convert.py +64 -0
- pynecore/core/script.py +1011 -0
- pynecore/core/script_runner.py +3202 -0
- pynecore/core/security.py +1749 -0
- pynecore/core/security_process.py +1253 -0
- pynecore/core/security_shm.py +456 -0
- pynecore/core/series.py +417 -0
- pynecore/core/strategy_stats.py +669 -0
- pynecore/core/symbol_map.py +134 -0
- pynecore/core/syminfo.py +505 -0
- pynecore/core/viz.py +591 -0
- pynecore/lib/__init__.py +1771 -0
- pynecore/lib/_fixnan.py +32 -0
- pynecore/lib/_math_stateful.py +202 -0
- pynecore/lib/_timeframe_change.py +101 -0
- pynecore/lib/adjustment.py +6 -0
- pynecore/lib/alert.py +39 -0
- pynecore/lib/alert.pyi +14 -0
- pynecore/lib/array.py +1051 -0
- pynecore/lib/barmerge.py +60 -0
- pynecore/lib/barstate.py +30 -0
- pynecore/lib/box.py +415 -0
- pynecore/lib/chart.py +128 -0
- pynecore/lib/color.py +152 -0
- pynecore/lib/color.pyi +50 -0
- pynecore/lib/currency.py +62 -0
- pynecore/lib/dayofweek.py +36 -0
- pynecore/lib/dayofweek.pyi +18 -0
- pynecore/lib/display.py +8 -0
- pynecore/lib/dividends.py +9 -0
- pynecore/lib/earnings.py +11 -0
- pynecore/lib/extend.py +6 -0
- pynecore/lib/font.py +5 -0
- pynecore/lib/footprint.py +79 -0
- pynecore/lib/format.py +11 -0
- pynecore/lib/hline.py +67 -0
- pynecore/lib/hline.pyi +24 -0
- pynecore/lib/label.py +409 -0
- pynecore/lib/line.py +433 -0
- pynecore/lib/linefill.py +93 -0
- pynecore/lib/location.py +11 -0
- pynecore/lib/log.py +362 -0
- pynecore/lib/map.py +150 -0
- pynecore/lib/math.py +385 -0
- pynecore/lib/matrix.py +708 -0
- pynecore/lib/order.py +8 -0
- pynecore/lib/pivotpointtype.py +8 -0
- pynecore/lib/plot.py +95 -0
- pynecore/lib/plot.pyi +33 -0
- pynecore/lib/polyline.py +91 -0
- pynecore/lib/position.py +15 -0
- pynecore/lib/request.py +281 -0
- pynecore/lib/runtime.py +5 -0
- pynecore/lib/scale.py +9 -0
- pynecore/lib/session.py +267 -0
- pynecore/lib/session.pyi +12 -0
- pynecore/lib/shape.py +18 -0
- pynecore/lib/size.py +12 -0
- pynecore/lib/splits.py +4 -0
- pynecore/lib/strategy/__init__.py +4778 -0
- pynecore/lib/strategy/closedtrades.py +347 -0
- pynecore/lib/strategy/closedtrades.pyi +53 -0
- pynecore/lib/strategy/commission.py +9 -0
- pynecore/lib/strategy/direction.py +9 -0
- pynecore/lib/strategy/oca.py +13 -0
- pynecore/lib/strategy/opentrades.py +281 -0
- pynecore/lib/strategy/opentrades.pyi +49 -0
- pynecore/lib/strategy/risk.py +109 -0
- pynecore/lib/string.py +649 -0
- pynecore/lib/syminfo.py +84 -0
- pynecore/lib/ta.py +2230 -0
- pynecore/lib/table.py +290 -0
- pynecore/lib/text.py +17 -0
- pynecore/lib/ticker.py +207 -0
- pynecore/lib/timeframe.py +293 -0
- pynecore/lib/volume_row.py +67 -0
- pynecore/lib/xloc.py +4 -0
- pynecore/lib/yloc.py +5 -0
- pynecore/providers/__init__.py +0 -0
- pynecore/providers/ccxt.py +664 -0
- pynecore/providers/replay.py +187 -0
- pynecore/pynesys/__init__.py +0 -0
- pynecore/pynesys/api.py +498 -0
- pynecore/pynesys/compiler.py +112 -0
- pynecore/standalone.py +99 -0
- pynecore/testing/__init__.py +1 -0
- pynecore/testing/broker_lab/__init__.py +41 -0
- pynecore/testing/broker_lab/__main__.py +5 -0
- pynecore/testing/broker_lab/cli.py +87 -0
- pynecore/testing/broker_lab/generate.py +47 -0
- pynecore/testing/broker_lab/model.py +84 -0
- pynecore/testing/broker_lab/reference.py +645 -0
- pynecore/testing/broker_lab/runner.py +372 -0
- pynecore/testing/broker_lab/scheduler.py +50 -0
- pynecore/testing/broker_lab/subprocess.py +73 -0
- pynecore/transformers/__init__.py +0 -0
- pynecore/transformers/builtin_shadow.py +136 -0
- pynecore/transformers/closure_arguments_transformer.py +428 -0
- pynecore/transformers/display_rewrite.py +140 -0
- pynecore/transformers/dynamic_default.py +147 -0
- pynecore/transformers/function_isolation.py +757 -0
- pynecore/transformers/import_lifter.py +61 -0
- pynecore/transformers/import_normalizer.py +328 -0
- pynecore/transformers/inline_series_hoist.py +178 -0
- pynecore/transformers/input_transformer.py +175 -0
- pynecore/transformers/lib_series.py +201 -0
- pynecore/transformers/locations.py +70 -0
- pynecore/transformers/module_properties.json +3387 -0
- pynecore/transformers/module_property.py +221 -0
- pynecore/transformers/ne_guard.py +70 -0
- pynecore/transformers/persistent.py +320 -0
- pynecore/transformers/persistent_series.py +76 -0
- pynecore/transformers/safe_convert_transformer.py +97 -0
- pynecore/transformers/safe_division_transformer.py +95 -0
- pynecore/transformers/script_requirements.py +308 -0
- pynecore/transformers/security.py +752 -0
- pynecore/transformers/security_instantiation.py +274 -0
- pynecore/transformers/series.py +275 -0
- pynecore/transformers/slot_layout.py +381 -0
- pynecore/transformers/type_checking_stripper.py +25 -0
- pynecore/transformers/unused_series_detector.py +267 -0
- pynecore/types/__init__.py +21 -0
- pynecore/types/alert.py +5 -0
- pynecore/types/barmerge.py +5 -0
- pynecore/types/base.py +39 -0
- pynecore/types/box.py +37 -0
- pynecore/types/chart.py +17 -0
- pynecore/types/color.py +107 -0
- pynecore/types/currency.py +5 -0
- pynecore/types/datetime.py +6 -0
- pynecore/types/display.py +5 -0
- pynecore/types/dividends.py +5 -0
- pynecore/types/earnings.py +5 -0
- pynecore/types/extend.py +5 -0
- pynecore/types/font.py +5 -0
- pynecore/types/footprint.py +41 -0
- pynecore/types/format.py +5 -0
- pynecore/types/hline.py +24 -0
- pynecore/types/ib_persistent.py +8 -0
- pynecore/types/ib_persistent.pyi +10 -0
- pynecore/types/label.py +35 -0
- pynecore/types/line.py +32 -0
- pynecore/types/linefill.py +13 -0
- pynecore/types/location.py +5 -0
- pynecore/types/matrix.py +999 -0
- pynecore/types/na.py +237 -0
- pynecore/types/na.pyi +83 -0
- pynecore/types/ohlcv.py +12 -0
- pynecore/types/order.py +5 -0
- pynecore/types/persistent.py +8 -0
- pynecore/types/persistent.pyi +13 -0
- pynecore/types/pine_types.py +11 -0
- pynecore/types/pine_types.pyi +15 -0
- pynecore/types/pivotpointtype.py +5 -0
- pynecore/types/plot.py +12 -0
- pynecore/types/plot_meta.py +60 -0
- pynecore/types/polyline.py +40 -0
- pynecore/types/position.py +5 -0
- pynecore/types/scale.py +5 -0
- pynecore/types/script_type.py +15 -0
- pynecore/types/series.py +23 -0
- pynecore/types/series.pyi +19 -0
- pynecore/types/session.py +35 -0
- pynecore/types/shape.py +5 -0
- pynecore/types/size.py +5 -0
- pynecore/types/source.py +33 -0
- pynecore/types/splits.py +5 -0
- pynecore/types/strategy.py +45 -0
- pynecore/types/table.py +87 -0
- pynecore/types/text.py +13 -0
- pynecore/types/type_checker.py +7 -0
- pynecore/types/type_checker.pyi +48 -0
- pynecore/types/volume_row.py +36 -0
- pynecore/types/weekdays.py +11 -0
- pynecore/types/xloc.py +5 -0
- pynecore/types/yloc.py +5 -0
- pynecore/utils/__init__.py +0 -0
- pynecore/utils/file_utils.py +50 -0
- pynecore/utils/rich/__init__.py +0 -0
- pynecore/utils/rich/date_column.py +25 -0
- pynecore/utils/sequence_view.py +92 -0
- 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
|