omegaconf-pydevd 2.4.0.dev10__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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2018, Omry Yadan
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ include version.py
4
+ recursive-include pydevd_plugins *.py
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: omegaconf-pydevd
3
+ Version: 2.4.0.dev10
4
+ Summary: pydevd debugger plugin for OmegaConf
5
+ Home-page: https://github.com/omry/omegaconf
6
+ Author: Omry Yadan
7
+ Author-email: omry@yadan.net
8
+ Keywords: omegaconf pydevd debugpy debugger
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: omegaconf==2.4.0.dev10
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: keywords
27
+ Dynamic: license-file
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # OmegaConf pydevd Plugin
33
+
34
+ `omegaconf-pydevd` provides the optional `pydevd` plugin for OmegaConf objects.
35
+
36
+ This debugger integration is packaged separately from the main `omegaconf`
37
+ distribution so that the global `pydevd_plugins` namespace is only installed on
38
+ systems that explicitly opt into it.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install omegaconf-pydevd
44
+ ```
45
+
46
+ ## Debugger Demo
47
+
48
+ Use [examples/debug_demo.py](./examples/debug_demo.py) to inspect OmegaConf
49
+ objects in the debugger with `omegaconf-pydevd` installed.
50
+
51
+ Set a breakpoint on `print(cfg)` and inspect:
52
+
53
+ - `cfg`
54
+ - `cfg.greeting`
55
+ - `cfg.subprojects`
56
+ - `cfg.project`
57
+
58
+ With `omegaconf-pydevd` installed:
59
+
60
+ - missing fields render as debugger values instead of surfacing a debugger-time
61
+ exception while inspecting them
62
+ - interpolations are shown more clearly, including their resolved values
63
+
64
+ ### Example Rendering
65
+
66
+ ![OmegaConf pydevd debugger rendering](/subprojects/omegaconf-pydevd/docs/with-plugin.png "OmegaConf pydevd debugger rendering")
67
+
68
+ ## Resolver Mode
69
+
70
+ Use the `OC_PYDEVD_RESOLVER` environment variable to select which resolver to
71
+ install:
72
+
73
+ - `USER`: default behavior, useful when debugging OmegaConf objects
74
+ - `DEV`: useful when debugging OmegaConf itself and inspecting its internal
75
+ data model
76
+ - `DISABLE`: disable the OmegaConf resolver
77
+
78
+ Example:
79
+
80
+ ```bash
81
+ OC_PYDEVD_RESOLVER=DEV python your_program.py
82
+ ```
83
+
84
+ ## Development
85
+
86
+ From this repository root:
87
+
88
+ ```bash
89
+ cd subprojects/omegaconf-pydevd
90
+ pip install -r ../../requirements/dev.txt -e .
91
+ pytest
92
+ ```
@@ -0,0 +1,61 @@
1
+ # OmegaConf pydevd Plugin
2
+
3
+ `omegaconf-pydevd` provides the optional `pydevd` plugin for OmegaConf objects.
4
+
5
+ This debugger integration is packaged separately from the main `omegaconf`
6
+ distribution so that the global `pydevd_plugins` namespace is only installed on
7
+ systems that explicitly opt into it.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install omegaconf-pydevd
13
+ ```
14
+
15
+ ## Debugger Demo
16
+
17
+ Use [examples/debug_demo.py](./examples/debug_demo.py) to inspect OmegaConf
18
+ objects in the debugger with `omegaconf-pydevd` installed.
19
+
20
+ Set a breakpoint on `print(cfg)` and inspect:
21
+
22
+ - `cfg`
23
+ - `cfg.greeting`
24
+ - `cfg.subprojects`
25
+ - `cfg.project`
26
+
27
+ With `omegaconf-pydevd` installed:
28
+
29
+ - missing fields render as debugger values instead of surfacing a debugger-time
30
+ exception while inspecting them
31
+ - interpolations are shown more clearly, including their resolved values
32
+
33
+ ### Example Rendering
34
+
35
+ ![OmegaConf pydevd debugger rendering](/subprojects/omegaconf-pydevd/docs/with-plugin.png "OmegaConf pydevd debugger rendering")
36
+
37
+ ## Resolver Mode
38
+
39
+ Use the `OC_PYDEVD_RESOLVER` environment variable to select which resolver to
40
+ install:
41
+
42
+ - `USER`: default behavior, useful when debugging OmegaConf objects
43
+ - `DEV`: useful when debugging OmegaConf itself and inspecting its internal
44
+ data model
45
+ - `DISABLE`: disable the OmegaConf resolver
46
+
47
+ Example:
48
+
49
+ ```bash
50
+ OC_PYDEVD_RESOLVER=DEV python your_program.py
51
+ ```
52
+
53
+ ## Development
54
+
55
+ From this repository root:
56
+
57
+ ```bash
58
+ cd subprojects/omegaconf-pydevd
59
+ pip install -r ../../requirements/dev.txt -e .
60
+ pytest
61
+ ```
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: omegaconf-pydevd
3
+ Version: 2.4.0.dev10
4
+ Summary: pydevd debugger plugin for OmegaConf
5
+ Home-page: https://github.com/omry/omegaconf
6
+ Author: Omry Yadan
7
+ Author-email: omry@yadan.net
8
+ Keywords: omegaconf pydevd debugpy debugger
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: omegaconf==2.4.0.dev10
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: keywords
27
+ Dynamic: license-file
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # OmegaConf pydevd Plugin
33
+
34
+ `omegaconf-pydevd` provides the optional `pydevd` plugin for OmegaConf objects.
35
+
36
+ This debugger integration is packaged separately from the main `omegaconf`
37
+ distribution so that the global `pydevd_plugins` namespace is only installed on
38
+ systems that explicitly opt into it.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install omegaconf-pydevd
44
+ ```
45
+
46
+ ## Debugger Demo
47
+
48
+ Use [examples/debug_demo.py](./examples/debug_demo.py) to inspect OmegaConf
49
+ objects in the debugger with `omegaconf-pydevd` installed.
50
+
51
+ Set a breakpoint on `print(cfg)` and inspect:
52
+
53
+ - `cfg`
54
+ - `cfg.greeting`
55
+ - `cfg.subprojects`
56
+ - `cfg.project`
57
+
58
+ With `omegaconf-pydevd` installed:
59
+
60
+ - missing fields render as debugger values instead of surfacing a debugger-time
61
+ exception while inspecting them
62
+ - interpolations are shown more clearly, including their resolved values
63
+
64
+ ### Example Rendering
65
+
66
+ ![OmegaConf pydevd debugger rendering](/subprojects/omegaconf-pydevd/docs/with-plugin.png "OmegaConf pydevd debugger rendering")
67
+
68
+ ## Resolver Mode
69
+
70
+ Use the `OC_PYDEVD_RESOLVER` environment variable to select which resolver to
71
+ install:
72
+
73
+ - `USER`: default behavior, useful when debugging OmegaConf objects
74
+ - `DEV`: useful when debugging OmegaConf itself and inspecting its internal
75
+ data model
76
+ - `DISABLE`: disable the OmegaConf resolver
77
+
78
+ Example:
79
+
80
+ ```bash
81
+ OC_PYDEVD_RESOLVER=DEV python your_program.py
82
+ ```
83
+
84
+ ## Development
85
+
86
+ From this repository root:
87
+
88
+ ```bash
89
+ cd subprojects/omegaconf-pydevd
90
+ pip install -r ../../requirements/dev.txt -e .
91
+ pytest
92
+ ```
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ setup.py
5
+ version.py
6
+ omegaconf_pydevd.egg-info/PKG-INFO
7
+ omegaconf_pydevd.egg-info/SOURCES.txt
8
+ omegaconf_pydevd.egg-info/dependency_links.txt
9
+ omegaconf_pydevd.egg-info/namespace_packages.txt
10
+ omegaconf_pydevd.egg-info/requires.txt
11
+ omegaconf_pydevd.egg-info/top_level.txt
12
+ pydevd_plugins/__init__.py
13
+ pydevd_plugins/extensions/__init__.py
14
+ pydevd_plugins/extensions/pydevd_plugin_omegaconf.py
15
+ tests/test_pydev_resolver_plugin.py
16
+ tests/test_pydevd_plugin_namespace.py
@@ -0,0 +1,2 @@
1
+ pydevd_plugins
2
+ pydevd_plugins.extensions
@@ -0,0 +1 @@
1
+ omegaconf==2.4.0.dev10
@@ -0,0 +1,6 @@
1
+ try:
2
+ __import__("pkg_resources").declare_namespace(__name__)
3
+ except ImportError:
4
+ import pkgutil
5
+
6
+ __path__ = pkgutil.extend_path(__path__, __name__)
@@ -0,0 +1,6 @@
1
+ try:
2
+ __import__("pkg_resources").declare_namespace(__name__)
3
+ except ImportError:
4
+ import pkgutil
5
+
6
+ __path__ = pkgutil.extend_path(__path__, __name__)
@@ -0,0 +1,126 @@
1
+ # based on https://github.com/fabioz/PyDev.Debugger/tree/main/pydevd_plugins/extensions
2
+ import os
3
+ import sys
4
+ from typing import Any, Dict
5
+
6
+ from _pydevd_bundle.pydevd_extension_api import ( # type: ignore
7
+ StrPresentationProvider,
8
+ TypeResolveProvider,
9
+ )
10
+
11
+ DEBUG = False
12
+
13
+
14
+ def print_debug(msg: str) -> None: # pragma: no cover
15
+ if DEBUG:
16
+ print(msg)
17
+
18
+
19
+ def find_mod_attr(mod_name: str, attr: str) -> Any:
20
+ mod = sys.modules.get(mod_name)
21
+ return getattr(mod, attr, None)
22
+
23
+
24
+ class OmegaConfDeveloperResolver(object):
25
+ def can_provide(self, type_object: Any, type_name: str) -> bool:
26
+ Node = find_mod_attr("omegaconf", "Node")
27
+ return Node is not None and issubclass(type_object, Node)
28
+
29
+ def resolve(self, obj: Any, attribute: str) -> Any:
30
+ return getattr(obj, attribute)
31
+
32
+ def get_dictionary(self, obj: Any) -> Any:
33
+ return obj.__dict__
34
+
35
+
36
+ class OmegaConfUserResolver(StrPresentationProvider): # type: ignore
37
+ def __init__(self) -> None:
38
+ self.Node = find_mod_attr("omegaconf", "Node")
39
+ self.ValueNode = find_mod_attr("omegaconf", "ValueNode")
40
+ self.ListConfig = find_mod_attr("omegaconf", "ListConfig")
41
+ self.DictConfig = find_mod_attr("omegaconf", "DictConfig")
42
+ self.InterpolationResolutionError = find_mod_attr(
43
+ "omegaconf.errors", "InterpolationResolutionError"
44
+ )
45
+
46
+ def can_provide(self, type_object: Any, type_name: str) -> bool:
47
+ return self.Node is not None and issubclass(type_object, self.Node)
48
+
49
+ def resolve(self, obj: Any, attribute: Any) -> Any:
50
+ if isinstance(obj, self.ListConfig) and isinstance(attribute, str):
51
+ attribute = int(attribute)
52
+
53
+ if isinstance(obj, self.Node):
54
+ obj = obj._dereference_node()
55
+
56
+ val = obj.__dict__["_content"][attribute]
57
+
58
+ print_debug(
59
+ f"resolving {obj} ({type(obj).__name__}), {attribute} -> {val} ({type(val).__name__})"
60
+ )
61
+
62
+ return val
63
+
64
+ def _is_simple_value(self, val: Any) -> bool:
65
+ return (
66
+ isinstance(val, self.ValueNode)
67
+ and not val._is_none()
68
+ and not val._is_missing()
69
+ and not val._is_interpolation()
70
+ )
71
+
72
+ def get_dictionary(self, obj: Any) -> Dict[str, Any]:
73
+ d = self._get_dictionary(obj)
74
+ print_debug(f"get_dictionary {obj}, ({type(obj).__name__}) -> {d}")
75
+ return d
76
+
77
+ def _get_dictionary(self, obj: Any) -> Dict[str, Any]:
78
+ if isinstance(obj, self.Node):
79
+ obj = obj._maybe_dereference_node()
80
+ if obj is None or obj._is_none() or obj._is_missing():
81
+ return {}
82
+
83
+ if isinstance(obj, self.DictConfig):
84
+ d = {}
85
+ for k, v in obj.__dict__["_content"].items():
86
+ if self._is_simple_value(v):
87
+ v = v._value()
88
+ d[k] = v
89
+ elif isinstance(obj, self.ListConfig):
90
+ d = {}
91
+ for idx, v in enumerate(obj.__dict__["_content"]):
92
+ if self._is_simple_value(v):
93
+ v = v._value()
94
+ d[str(idx)] = v
95
+ else:
96
+ d = {}
97
+
98
+ return d
99
+
100
+ def get_str(self, val: Any) -> str:
101
+ if val._is_missing():
102
+ return "??? <MISSING>"
103
+ if val._is_interpolation():
104
+ try:
105
+ dr = val._dereference_node()
106
+ except self.InterpolationResolutionError as e:
107
+ dr = f"ERR: {e}"
108
+ return f"{val._value()} -> {dr}"
109
+ else:
110
+ return f"{val}"
111
+
112
+
113
+ # OC_PYDEVD_RESOLVER env can take:
114
+ # DISABLE: Do not install a pydevd resolver
115
+ # USER: Install a resolver for OmegaConf users (default)
116
+ # DEV: Install a resolver for OmegaConf developers. Shows underlying data-model in the debugger.
117
+ resolver = os.environ.get("OC_PYDEVD_RESOLVER", "USER").upper()
118
+ if resolver != "DISABLE": # pragma: no cover
119
+ if resolver == "USER":
120
+ TypeResolveProvider.register(OmegaConfUserResolver)
121
+ elif resolver == "DEV":
122
+ TypeResolveProvider.register(OmegaConfDeveloperResolver)
123
+ else:
124
+ sys.stderr.write(
125
+ f"OmegaConf pydev plugin: Not installing. Unknown mode {resolver}. Supported one of [USER, DEV, DISABLE]\n"
126
+ )
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,53 @@
1
+ # type: ignore
2
+ import pathlib
3
+ import re
4
+
5
+ import setuptools
6
+
7
+ ROOT = pathlib.Path(__file__).parent.resolve()
8
+
9
+
10
+ def find_version(*file_paths: str) -> str:
11
+ with open(ROOT / pathlib.Path(*file_paths), "r", encoding="utf-8") as fp:
12
+ version_file = fp.read()
13
+ version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
14
+ if version_match:
15
+ return version_match.group(1)
16
+ raise RuntimeError("Unable to find version string.")
17
+
18
+
19
+ VERSION = find_version("version.py")
20
+
21
+ with (ROOT / "README.md").open("r", encoding="utf-8") as fh:
22
+ LONG_DESC = fh.read()
23
+
24
+ setuptools.setup(
25
+ name="omegaconf-pydevd",
26
+ version=VERSION,
27
+ author="Omry Yadan",
28
+ author_email="omry@yadan.net",
29
+ description="pydevd debugger plugin for OmegaConf",
30
+ long_description=LONG_DESC,
31
+ long_description_content_type="text/markdown",
32
+ url="https://github.com/omry/omegaconf",
33
+ keywords="omegaconf pydevd debugpy debugger",
34
+ packages=[
35
+ "pydevd_plugins",
36
+ "pydevd_plugins.extensions",
37
+ ],
38
+ namespace_packages=[
39
+ "pydevd_plugins",
40
+ "pydevd_plugins.extensions",
41
+ ],
42
+ python_requires=">=3.10",
43
+ classifiers=[
44
+ "Programming Language :: Python :: 3.10",
45
+ "Programming Language :: Python :: 3.11",
46
+ "Programming Language :: Python :: 3.12",
47
+ "Programming Language :: Python :: 3.13",
48
+ "Programming Language :: Python :: 3.14",
49
+ "License :: OSI Approved :: BSD License",
50
+ "Operating System :: OS Independent",
51
+ ],
52
+ install_requires=[f"omegaconf=={VERSION}"],
53
+ )
@@ -0,0 +1,268 @@
1
+ import builtins
2
+ from enum import Enum
3
+ from typing import Any
4
+
5
+ from pydevd_plugins.extensions.pydevd_plugin_omegaconf import (
6
+ OmegaConfDeveloperResolver,
7
+ OmegaConfUserResolver,
8
+ )
9
+ from pytest import fixture, mark, param
10
+
11
+ from omegaconf import (
12
+ AnyNode,
13
+ BooleanNode,
14
+ BytesNode,
15
+ Container,
16
+ DictConfig,
17
+ EnumNode,
18
+ FloatNode,
19
+ IntegerNode,
20
+ ListConfig,
21
+ Node,
22
+ OmegaConf,
23
+ PathNode,
24
+ StringNode,
25
+ ValueNode,
26
+ )
27
+ from omegaconf._utils import type_str
28
+
29
+
30
+ class Color(Enum):
31
+ RED = 1
32
+
33
+
34
+ @fixture
35
+ def resolver() -> Any:
36
+ yield OmegaConfUserResolver()
37
+
38
+
39
+ @mark.parametrize(
40
+ ("obj", "expected"),
41
+ [
42
+ param(AnyNode(10), {}, id="any:10"),
43
+ param(StringNode("foo"), {}, id="str:foo"),
44
+ param(IntegerNode(10), {}, id="int:10"),
45
+ param(FloatNode(3.14), {}, id="float:3.14"),
46
+ param(BooleanNode(True), {}, id="bool:True"),
47
+ param(BytesNode(b"binary"), {}, id="bytes:binary"),
48
+ param(PathNode("hello.txt"), {}, id="path:hello.txt"),
49
+ param(EnumNode(enum_type=Color, value=Color.RED), {}, id="Color:Color.RED"),
50
+ param(AnyNode("${foo}", parent=DictConfig({"foo": 10})), {}, id="any:inter_10"),
51
+ param(DictConfig({"a": 10}), {"a": AnyNode(10)}, id="dict"),
52
+ param(
53
+ DictConfig({"a": 10, "b": "${a}"}),
54
+ {"a": AnyNode(10), "b": AnyNode("${a}")},
55
+ id="dict:interpolation_value",
56
+ ),
57
+ param(
58
+ DictConfig({"a": 10, "b": "${zzz}"}),
59
+ {"a": AnyNode(10), "b": AnyNode("${zzz}")},
60
+ id="dict:interpolation_value_error",
61
+ ),
62
+ param(
63
+ DictConfig({"a": 10, "b": "foo_${a}"}),
64
+ {"a": AnyNode(10), "b": AnyNode("foo_${a}")},
65
+ id="dict:str_interpolation_value",
66
+ ),
67
+ param(DictConfig("${zzz}"), {}, id="dict:inter_error"),
68
+ param(
69
+ ListConfig(["a", "b"]), {"0": AnyNode("a"), "1": AnyNode("b")}, id="list"
70
+ ),
71
+ param(
72
+ ListConfig(["${1}", 10]),
73
+ {"0": AnyNode("${1}"), "1": AnyNode(10)},
74
+ id="list:interpolation_value",
75
+ ),
76
+ param(ListConfig("${zzz}"), {}, id="list:inter_error"),
77
+ ],
78
+ )
79
+ def test_get_dictionary_node(resolver: Any, obj: Any, expected: Any) -> None:
80
+ res = resolver.get_dictionary(obj)
81
+ assert res == expected
82
+
83
+
84
+ @mark.parametrize(
85
+ ("obj", "attribute", "expected"),
86
+ [
87
+ param(DictConfig({"a": 10}), "a", AnyNode(10), id="dict"),
88
+ param(
89
+ DictConfig({"a": DictConfig(None)}),
90
+ "a",
91
+ DictConfig(None),
92
+ id="dict:none",
93
+ ),
94
+ param(ListConfig([10]), 0, AnyNode(10), id="list"),
95
+ param(ListConfig(["???"]), 0, AnyNode("???"), id="list:missing_item"),
96
+ ],
97
+ )
98
+ def test_resolve(
99
+ resolver: Any,
100
+ obj: Any,
101
+ attribute: str,
102
+ expected: Any,
103
+ ) -> None:
104
+ res = resolver.resolve(obj, attribute)
105
+ assert res == expected
106
+ assert type(res) is type(expected)
107
+
108
+
109
+ @mark.parametrize(
110
+ ("obj", "attribute", "expected"),
111
+ [
112
+ param(
113
+ OmegaConf.create({"a": 10, "inter": "${a}"}), "inter", {}, id="dict:inter"
114
+ ),
115
+ param(
116
+ OmegaConf.create({"missing": "???"}),
117
+ "missing",
118
+ {},
119
+ id="dict:missing_value",
120
+ ),
121
+ param(OmegaConf.create({"none": None}), "none", {}, id="dict:none_value"),
122
+ param(
123
+ OmegaConf.create({"none": DictConfig(None)}),
124
+ "none",
125
+ {},
126
+ id="dict:none_dictconfig_value",
127
+ ),
128
+ param(
129
+ OmegaConf.create({"missing": DictConfig("???")}),
130
+ "missing",
131
+ {},
132
+ id="dict:missing_dictconfig_value",
133
+ ),
134
+ param(
135
+ OmegaConf.create({"a": {"b": 10}, "b": DictConfig("${a}")}),
136
+ "b",
137
+ {"b": 10},
138
+ id="dict:interpolation_dictconfig_value",
139
+ ),
140
+ ],
141
+ )
142
+ def test_get_dictionary_dictconfig(
143
+ resolver: Any,
144
+ obj: Any,
145
+ attribute: str,
146
+ expected: Any,
147
+ ) -> None:
148
+ field = resolver.resolve(obj, attribute)
149
+ res = resolver.get_dictionary(field)
150
+ assert res == expected
151
+ assert type(res) is type(expected)
152
+
153
+
154
+ @mark.parametrize(
155
+ ("obj", "attribute", "expected"),
156
+ [
157
+ param(
158
+ ListConfig("${list}", parent=DictConfig({"list": [{"a": 10}]})),
159
+ "0",
160
+ {"a": 10},
161
+ id="inter_list:dict_element",
162
+ ),
163
+ param(
164
+ DictConfig("${dict}", parent=DictConfig({"dict": {"a": {"b": 10}}})),
165
+ "a",
166
+ {"b": 10},
167
+ id="inter_dict:dict_element",
168
+ ),
169
+ ],
170
+ )
171
+ def test_resolve_through_container_interpolation(
172
+ resolver: Any, obj: Any, attribute: str, expected: Any
173
+ ) -> None:
174
+ res = resolver.resolve(obj, attribute)
175
+ assert res == expected
176
+
177
+
178
+ @mark.parametrize(
179
+ ("obj", "attribute", "expected"),
180
+ [
181
+ param(OmegaConf.create(["${.1}", 10]), "0", {}, id="list:inter_value"),
182
+ param(
183
+ OmegaConf.create({"a": ListConfig(None)}),
184
+ "a",
185
+ {},
186
+ id="list:none_listconfig_value",
187
+ ),
188
+ param(
189
+ OmegaConf.create({"a": ListConfig("???")}),
190
+ "a",
191
+ {},
192
+ id="list:missing_listconfig_value",
193
+ ),
194
+ param(
195
+ OmegaConf.create({"a": [1, 2], "b": ListConfig("${a}")}),
196
+ "b",
197
+ {"0": 1, "1": 2},
198
+ id="list:interpolation_listconfig_value",
199
+ ),
200
+ ],
201
+ )
202
+ def test_get_dictionary_listconfig(
203
+ resolver: Any,
204
+ obj: Any,
205
+ attribute: str,
206
+ expected: Any,
207
+ ) -> None:
208
+ field = resolver.resolve(obj, attribute)
209
+ res = resolver.get_dictionary(field)
210
+ assert res == expected
211
+ assert type(res) is type(expected)
212
+
213
+
214
+ @mark.parametrize("resolver", [OmegaConfUserResolver(), OmegaConfDeveloperResolver()])
215
+ @mark.parametrize(
216
+ ("type_", "expected"),
217
+ [
218
+ (Container, True),
219
+ (DictConfig, True),
220
+ (ListConfig, True),
221
+ (Node, True),
222
+ (ValueNode, True),
223
+ (AnyNode, True),
224
+ (IntegerNode, True),
225
+ (FloatNode, True),
226
+ (StringNode, True),
227
+ (BooleanNode, True),
228
+ (BytesNode, True),
229
+ (PathNode, True),
230
+ (EnumNode, True),
231
+ (builtins.int, False),
232
+ (dict, False),
233
+ (list, False),
234
+ ],
235
+ )
236
+ def test_can_provide(resolver: Any, type_: Any, expected: bool) -> None:
237
+ assert resolver.can_provide(type_, type_str(type_)) == expected
238
+
239
+
240
+ @mark.parametrize(
241
+ ("obj", "expected"),
242
+ [
243
+ (AnyNode(10), "10"),
244
+ (AnyNode("???"), "??? <MISSING>"),
245
+ (
246
+ AnyNode("${foo}", parent=OmegaConf.create({})),
247
+ "${foo} -> ERR: Interpolation key 'foo' not found",
248
+ ),
249
+ (AnyNode("${foo}", parent=OmegaConf.create({"foo": 10})), "${foo} -> 10"),
250
+ (
251
+ DictConfig("${foo}", parent=OmegaConf.create({"foo": {"a": 10}})),
252
+ "${foo} -> {'a': 10}",
253
+ ),
254
+ (
255
+ ListConfig("${foo}", parent=OmegaConf.create({"foo": [1, 2]})),
256
+ "${foo} -> [1, 2]",
257
+ ),
258
+ ],
259
+ )
260
+ def test_get_str(resolver: Any, obj: Any, expected: str) -> None:
261
+ assert resolver.get_str(obj) == expected
262
+
263
+
264
+ def test_dev_resolver() -> None:
265
+ resolver = OmegaConfDeveloperResolver()
266
+ cfg = OmegaConf.create({"foo": 10})
267
+ assert resolver.resolve(cfg, "_metadata") is cfg.__dict__["_metadata"]
268
+ assert resolver.get_dictionary(cfg) is cfg.__dict__
@@ -0,0 +1,240 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ OLD_NAMESPACE_INIT = """import pkgutil
8
+
9
+ __path__ = pkgutil.extend_path(__path__, __name__)
10
+ """
11
+
12
+
13
+ FAKE_PKG_RESOURCES = """_namespaces = set()
14
+
15
+
16
+ def _package_paths(name):
17
+ import os
18
+ import sys
19
+
20
+ relpath = os.path.join(*name.split("."))
21
+ paths = []
22
+ for entry in sys.path:
23
+ if not entry:
24
+ continue
25
+ candidate = os.path.join(entry, relpath)
26
+ if os.path.isdir(candidate):
27
+ paths.append(candidate)
28
+ return paths
29
+
30
+
31
+ def declare_namespace(name):
32
+ import sys
33
+
34
+ _namespaces.add(name)
35
+ sys.modules[name].__path__ = _package_paths(name)
36
+
37
+
38
+ def fixup_namespace_packages(_):
39
+ import sys
40
+
41
+ for name in _namespaces:
42
+ module = sys.modules.get(name)
43
+ if module is not None:
44
+ module.__path__ = _package_paths(name)
45
+ """
46
+
47
+
48
+ DISCOVERY_SCRIPT = """import importlib
49
+ import json
50
+ import os
51
+ import pkgutil
52
+ import sys
53
+
54
+ import pydevd_plugins.extensions as extensions
55
+ import pkg_resources
56
+
57
+ plugin_root = os.environ["OMEGACONF_PYDEVD_PLUGIN_ROOT"]
58
+ sys.path.insert(0, plugin_root)
59
+ importlib.invalidate_caches()
60
+ pkg_resources.fixup_namespace_packages(plugin_root)
61
+
62
+ print(
63
+ json.dumps(
64
+ sorted(
65
+ name
66
+ for _, name, _ in pkgutil.walk_packages(
67
+ extensions.__path__, extensions.__name__ + "."
68
+ )
69
+ )
70
+ )
71
+ )
72
+ """
73
+
74
+
75
+ EDITABLE_DISCOVERY_SCRIPT = """import importlib
76
+ import json
77
+ import os
78
+ import pkgutil
79
+ import sys
80
+ from importlib.machinery import ModuleSpec, PathFinder
81
+ from importlib.util import spec_from_file_location
82
+ from pathlib import Path
83
+
84
+ import pkg_resources
85
+
86
+
87
+ class EditableFinder:
88
+ @classmethod
89
+ def find_spec(cls, fullname, path=None, target=None):
90
+ plugin_root = Path(os.environ["OMEGACONF_PYDEVD_PLUGIN_ROOT"])
91
+ mapping = {
92
+ "pydevd_plugins": plugin_root / "pydevd_plugins",
93
+ "pydevd_plugins.extensions": plugin_root / "pydevd_plugins" / "extensions",
94
+ }
95
+ candidate = mapping.get(fullname)
96
+ if candidate is not None:
97
+ return spec_from_file_location(fullname, candidate / "__init__.py")
98
+
99
+ parent, _, _ = fullname.rpartition(".")
100
+ if parent in mapping:
101
+ return PathFinder.find_spec(fullname, path=[str(mapping[parent])])
102
+ return None
103
+
104
+
105
+ def install_namespace_path(fullname, path_entry):
106
+ module = sys.modules.get(fullname)
107
+ if module is None:
108
+ module = importlib.import_module(fullname)
109
+ module_path = module.__dict__.setdefault("__path__", [])
110
+ if path_entry not in module_path:
111
+ module_path.append(path_entry)
112
+
113
+
114
+ sys.meta_path.append(EditableFinder)
115
+
116
+ if os.environ.get("OMEGACONF_PYDEVD_INSTALL_NAMESPACE_PATHS") == "1":
117
+ plugin_root = Path(os.environ["OMEGACONF_PYDEVD_PLUGIN_ROOT"])
118
+ install_namespace_path("pydevd_plugins", str(plugin_root / "pydevd_plugins"))
119
+ install_namespace_path(
120
+ "pydevd_plugins.extensions",
121
+ str(plugin_root / "pydevd_plugins" / "extensions"),
122
+ )
123
+
124
+ import pydevd_plugins.extensions as extensions
125
+ pkg_resources.fixup_namespace_packages(os.environ["OMEGACONF_PYDEVD_PLUGIN_ROOT"])
126
+
127
+ print(
128
+ json.dumps(
129
+ sorted(
130
+ name
131
+ for _, name, _ in pkgutil.walk_packages(
132
+ extensions.__path__, extensions.__name__ + "."
133
+ )
134
+ )
135
+ )
136
+ )
137
+ """
138
+
139
+
140
+ def _write_namespace_package(root: Path, init_text: str) -> None:
141
+ extensions_dir = root / "pydevd_plugins" / "extensions"
142
+ extensions_dir.mkdir(parents=True)
143
+ (root / "pydevd_plugins" / "__init__.py").write_text(init_text)
144
+ (extensions_dir / "__init__.py").write_text(init_text)
145
+
146
+
147
+ def _build_project(tmp_path: Path, init_text: str) -> tuple[Path, Path, Path]:
148
+ project_root = tmp_path / "project"
149
+ support_root = project_root / "support"
150
+ bundled_root = project_root / "bundled"
151
+ plugin_root = project_root / "plugin"
152
+
153
+ support_root.mkdir(parents=True)
154
+ (support_root / "pkg_resources.py").write_text(FAKE_PKG_RESOURCES)
155
+
156
+ _write_namespace_package(bundled_root, init_text)
157
+ _write_namespace_package(plugin_root, init_text)
158
+ (
159
+ bundled_root / "pydevd_plugins" / "extensions" / "pydevd_plugin_builtin.py"
160
+ ).write_text("X = 1\n")
161
+ (
162
+ plugin_root / "pydevd_plugins" / "extensions" / "pydevd_plugin_omegaconf.py"
163
+ ).write_text("Y = 1\n")
164
+
165
+ return project_root, support_root, bundled_root
166
+
167
+
168
+ def _discover_extensions_from_editable_install(
169
+ tmp_path: Path,
170
+ *,
171
+ install_namespace_paths: bool,
172
+ ) -> list[str]:
173
+ project_root, support_root, bundled_root = _build_project(tmp_path, OLD_NAMESPACE_INIT)
174
+ plugin_root = project_root / "plugin"
175
+ env = os.environ.copy()
176
+ env["PYTHONPATH"] = os.pathsep.join([str(support_root), str(bundled_root)])
177
+ env["OMEGACONF_PYDEVD_PLUGIN_ROOT"] = str(plugin_root)
178
+ if install_namespace_paths:
179
+ env["OMEGACONF_PYDEVD_INSTALL_NAMESPACE_PATHS"] = "1"
180
+ result = subprocess.run(
181
+ [sys.executable, "-S", "-c", EDITABLE_DISCOVERY_SCRIPT],
182
+ check=True,
183
+ capture_output=True,
184
+ cwd=project_root,
185
+ env=env,
186
+ text=True,
187
+ )
188
+ return json.loads(result.stdout)
189
+
190
+
191
+ def _discover_extensions(tmp_path: Path, init_text: str) -> list[str]:
192
+ project_root, support_root, bundled_root = _build_project(tmp_path, init_text)
193
+ plugin_root = project_root / "plugin"
194
+ env = os.environ.copy()
195
+ env["PYTHONPATH"] = os.pathsep.join([str(support_root), str(bundled_root)])
196
+ env["OMEGACONF_PYDEVD_PLUGIN_ROOT"] = str(plugin_root)
197
+ result = subprocess.run(
198
+ [sys.executable, "-S", "-c", DISCOVERY_SCRIPT],
199
+ check=True,
200
+ capture_output=True,
201
+ cwd=project_root,
202
+ env=env,
203
+ text=True,
204
+ )
205
+ return json.loads(result.stdout)
206
+
207
+
208
+ def test_current_namespace_init_supports_late_plugin_discovery(tmp_path: Path) -> None:
209
+ init_text = (
210
+ Path(__file__).resolve().parents[1] / "pydevd_plugins" / "__init__.py"
211
+ ).read_text()
212
+ discovered = _discover_extensions(tmp_path, init_text)
213
+ assert "pydevd_plugins.extensions.pydevd_plugin_omegaconf" in discovered
214
+
215
+
216
+ def test_old_namespace_init_does_not_support_late_plugin_discovery(
217
+ tmp_path: Path,
218
+ ) -> None:
219
+ discovered = _discover_extensions(tmp_path, OLD_NAMESPACE_INIT)
220
+ assert "pydevd_plugins.extensions.pydevd_plugin_omegaconf" not in discovered
221
+
222
+
223
+ def test_editable_install_without_namespace_paths_is_not_discovered(
224
+ tmp_path: Path,
225
+ ) -> None:
226
+ discovered = _discover_extensions_from_editable_install(
227
+ tmp_path,
228
+ install_namespace_paths=False,
229
+ )
230
+ assert "pydevd_plugins.extensions.pydevd_plugin_omegaconf" not in discovered
231
+
232
+
233
+ def test_editable_install_with_namespace_paths_is_discovered(
234
+ tmp_path: Path,
235
+ ) -> None:
236
+ discovered = _discover_extensions_from_editable_install(
237
+ tmp_path,
238
+ install_namespace_paths=True,
239
+ )
240
+ assert "pydevd_plugins.extensions.pydevd_plugin_omegaconf" in discovered
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0.dev10"