Skip to content

Commit 499de6b

Browse files
authored
Major changes to interpolations and resolvers (omry#445)
* Add a grammar to parse interpolations * Add support for nested interpolations * Deprecate register_resolver() and introduce new_register_resolver() (that allows resolvers to use non-string arguments, and to decide whether or not to use the cache) * The `env` resolver now parses environment variables in a way that is consistent with the new interpolation grammar Fixes omry#100 omry#230 omry#266 omry#318
1 parent 2cf4e7c commit 499de6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2751
-436
lines changed

.circleci/config.yml

+29-35
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,41 @@
11
version: 2.1
22

3-
jobs:
4-
py36_linux:
5-
docker:
6-
- image: circleci/python:3.6
7-
steps:
8-
- checkout
9-
- run: echo 'export NOX_PYTHON_VERSIONS=3.6' >> $BASH_ENV
10-
- run: sudo pip install nox
11-
- run: nox --add-timestamp
12-
13-
py37_linux:
14-
docker:
15-
- image: circleci/python:3.7
3+
commands:
4+
linux:
5+
description: "Commands run on Linux"
6+
parameters:
7+
py_version:
8+
type: string
169
steps:
1710
- checkout
18-
- run: echo 'export NOX_PYTHON_VERSIONS=3.7' >> $BASH_ENV
19-
- run: sudo pip install nox
20-
- run: nox --add-timestamp
11+
- run:
12+
name: "Preparing environment"
13+
command: |
14+
sudo apt-get update
15+
sudo apt-get install -y openjdk-11-jre
16+
sudo pip install nox
17+
- run:
18+
name: "Testing OmegaConf"
19+
command: |
20+
export NOX_PYTHON_VERSIONS=<< parameters.py_version >>
21+
nox --add-timestamp
2122
22-
py38_linux:
23-
docker:
24-
- image: circleci/python:3.8
25-
steps:
26-
- checkout
27-
- run: echo 'export NOX_PYTHON_VERSIONS=3.8' >> $BASH_ENV
28-
- run: sudo pip install nox
29-
- run: nox --add-timestamp
30-
31-
py39_linux:
23+
jobs:
24+
test_linux:
25+
parameters:
26+
py_version:
27+
type: string
3228
docker:
33-
- image: circleci/python:3.9
29+
- image: circleci/python:<< parameters.py_version >>
3430
steps:
35-
- checkout
36-
- run: echo 'export NOX_PYTHON_VERSIONS=3.9' >> $BASH_ENV
37-
- run: sudo pip install nox
38-
- run: nox --add-timestamp
31+
- linux:
32+
py_version: << parameters.py_version >>
3933

4034
workflows:
4135
version: 2
4236
build:
4337
jobs:
44-
- py36_linux
45-
- py37_linux
46-
- py38_linux
47-
- py39_linux
38+
- test_linux:
39+
matrix:
40+
parameters:
41+
py_version: ["3.6", "3.7", "3.8", "3.9"]

.coveragerc

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ omit =
33
.nox/*
44
*tests*
55
docs/*
6+
omegaconf/grammar/gen/*
67
omegaconf/version.py
78
.stubs
89

@@ -15,7 +16,7 @@ exclude_lines =
1516
assert False
1617
@abstractmethod
1718
\.\.\.
18-
19+
if TYPE_CHECKING:
1920

2021
[html]
2122
directory = docs/build/coverage

.flake8

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[flake8]
2-
exclude = .git,.nox,.tox
2+
exclude = .git,.nox,.tox,omegaconf/grammar/gen
33
max-line-length = 119
44
select = E,F,W,C
55
ignore=W503,E203

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.jar binary

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ TODO
1616
.coverage
1717
.eggs
1818
.mypy_cache
19+
/omegaconf/grammar/gen
1920
/pip-wheel-metadata
2021
/.pyre
2122
.dmypy.json
2223
.python-version
24+
.vscode

.isort.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ line_length=88
77
ensure_newline_before_comments=True
88
known_third_party=attr,pytest
99
known_first_party=omegaconf
10+
skip=.eggs,.nox,omegaconf/grammar/gen

benchmark/benchmark.py

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pytest import fixture, mark, param
55

66
from omegaconf import OmegaConf
7+
from omegaconf._utils import ValueKind, get_value_kind
78

89

910
def build_dict(
@@ -119,3 +120,23 @@ def iterate(seq: Any) -> None:
119120
pass
120121

121122
benchmark(iterate, lst)
123+
124+
125+
@mark.parametrize(
126+
"strict_interpolation_validation",
127+
[True, False],
128+
)
129+
@mark.parametrize(
130+
("value", "expected"),
131+
[
132+
("simple", ValueKind.VALUE),
133+
("${a}", ValueKind.INTERPOLATION),
134+
("${a:b,c,d}", ValueKind.INTERPOLATION),
135+
("${${b}}", ValueKind.INTERPOLATION),
136+
("${a:${b}}", ValueKind.INTERPOLATION),
137+
],
138+
)
139+
def test_get_value_kind(
140+
strict_interpolation_validation: bool, value: Any, expected: Any, benchmark: Any
141+
) -> None:
142+
assert benchmark(get_value_kind, value, strict_interpolation_validation) == expected

build_helpers/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Order of imports is important (see warning otherwise when running tests)
2+
import setuptools # isort:skip # noqa
3+
import distutils # isort:skip # noqa
1.99 MB
Binary file not shown.

build_helpers/build_helpers.py

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import codecs
2+
import distutils.log
3+
import errno
4+
import os
5+
import re
6+
import shutil
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
from typing import List, Optional
11+
12+
from setuptools import Command
13+
from setuptools.command import build_py, develop, sdist # type: ignore
14+
15+
16+
class ANTLRCommand(Command): # type: ignore # pragma: no cover
17+
"""Generate parsers using ANTLR."""
18+
19+
description = "Run ANTLR"
20+
user_options: List[str] = []
21+
22+
def run(self) -> None:
23+
"""Run command."""
24+
build_dir = Path(__file__).parent.absolute()
25+
project_root = build_dir.parent
26+
for grammar in [
27+
"OmegaConfGrammarLexer.g4",
28+
"OmegaConfGrammarParser.g4",
29+
]:
30+
command = [
31+
"java",
32+
"-jar",
33+
str(build_dir / "bin" / "antlr-4.8-complete.jar"),
34+
"-Dlanguage=Python3",
35+
"-o",
36+
str(project_root / "omegaconf" / "grammar" / "gen"),
37+
"-Xexact-output-dir",
38+
"-visitor",
39+
str(project_root / "omegaconf" / "grammar" / grammar),
40+
]
41+
42+
self.announce(
43+
f"Generating parser for Python3: {command}",
44+
level=distutils.log.INFO,
45+
)
46+
47+
subprocess.check_call(command)
48+
49+
def initialize_options(self) -> None:
50+
pass
51+
52+
def finalize_options(self) -> None:
53+
pass
54+
55+
56+
class BuildPyCommand(build_py.build_py): # type: ignore # pragma: no cover
57+
def run(self) -> None:
58+
if not self.dry_run:
59+
self.run_command("clean")
60+
run_antlr(self)
61+
build_py.build_py.run(self)
62+
63+
64+
class CleanCommand(Command): # type: ignore # pragma: no cover
65+
"""
66+
Our custom command to clean out junk files.
67+
"""
68+
69+
description = "Cleans out generated and junk files we don't want in the repo"
70+
dry_run: bool
71+
user_options: List[str] = []
72+
73+
def run(self) -> None:
74+
root = Path(__file__).parent.parent.absolute()
75+
files = find(
76+
root=root,
77+
include_files=["^omegaconf/grammar/gen/.*"],
78+
include_dirs=[
79+
"^omegaconf\\.egg-info$",
80+
"\\.eggs$",
81+
"^\\.mypy_cache$",
82+
"^\\.nox$",
83+
"^\\.pytest_cache$",
84+
".*/__pycache__$",
85+
"^__pycache__$",
86+
"^build$",
87+
"^dist$",
88+
],
89+
scan_exclude=["^.git$", "^.nox/.*$"],
90+
excludes=[".*\\.gitignore$", ".*/__init__.py"],
91+
)
92+
93+
if self.dry_run:
94+
print("Dry run! Would clean up the following files and dirs:")
95+
print("\n".join(sorted(map(str, files))))
96+
else:
97+
for f in files:
98+
if f.exists():
99+
if f.is_dir():
100+
shutil.rmtree(f, ignore_errors=True)
101+
else:
102+
f.unlink()
103+
104+
def initialize_options(self) -> None:
105+
pass
106+
107+
def finalize_options(self) -> None:
108+
pass
109+
110+
111+
class DevelopCommand(develop.develop): # type: ignore # pragma: no cover
112+
def run(self) -> None:
113+
if not self.dry_run:
114+
run_antlr(self)
115+
develop.develop.run(self)
116+
117+
118+
class SDistCommand(sdist.sdist): # type: ignore # pragma: no cover
119+
def run(self) -> None:
120+
if not self.dry_run:
121+
self.run_command("clean")
122+
run_antlr(self)
123+
sdist.sdist.run(self)
124+
125+
126+
def find(
127+
root: Path,
128+
include_files: List[str],
129+
include_dirs: List[str],
130+
excludes: List[str],
131+
rbase: Optional[Path] = None,
132+
scan_exclude: Optional[List[str]] = None,
133+
) -> List[Path]:
134+
if rbase is None:
135+
rbase = Path()
136+
if scan_exclude is None:
137+
scan_exclude = []
138+
files = []
139+
scan_root = root / rbase
140+
for entry in scan_root.iterdir():
141+
path = rbase / entry.name
142+
if matches(scan_exclude, path):
143+
continue
144+
145+
if entry.is_dir():
146+
if matches(include_dirs, path):
147+
if not matches(excludes, path):
148+
files.append(path)
149+
else:
150+
ret = find(
151+
root=root,
152+
include_files=include_files,
153+
include_dirs=include_dirs,
154+
excludes=excludes,
155+
rbase=path,
156+
scan_exclude=scan_exclude,
157+
)
158+
files.extend(ret)
159+
else:
160+
if matches(include_files, path) and not matches(excludes, path):
161+
files.append(path)
162+
163+
return files
164+
165+
166+
def find_version(*file_paths: str) -> str:
167+
root = Path(__file__).parent.parent.absolute()
168+
with codecs.open(root / Path(*file_paths), "r") as fp: # type: ignore
169+
version_file = fp.read()
170+
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
171+
if version_match:
172+
return version_match.group(1)
173+
raise RuntimeError("Unable to find version string.") # pragma: no cover
174+
175+
176+
def matches(patterns: List[str], path: Path) -> bool:
177+
string = str(path).replace(os.sep, "/") # for Windows
178+
for pattern in patterns:
179+
if re.match(pattern, string):
180+
return True
181+
return False
182+
183+
184+
def run_antlr(cmd: Command) -> None: # pragma: no cover
185+
try:
186+
cmd.announce("Generating parsers with antlr4", level=distutils.log.INFO)
187+
cmd.run_command("antlr")
188+
except OSError as e:
189+
if e.errno == errno.ENOENT:
190+
msg = f"| Unable to generate parsers: {e} |"
191+
msg = "=" * len(msg) + "\n" + msg + "\n" + "=" * len(msg)
192+
cmd.announce(f"{msg}", level=distutils.log.FATAL)
193+
sys.exit(1)
194+
else:
195+
raise
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Intentionally left empty for git to keep the directory

build_helpers/test_files/a/b/file1.txt

Whitespace-only changes.

build_helpers/test_files/a/b/file2.txt

Whitespace-only changes.

build_helpers/test_files/a/b/junk.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Intentionally left empty for git to keep the directory

build_helpers/test_files/c/file1.txt

Whitespace-only changes.

build_helpers/test_files/c/file2.txt

Whitespace-only changes.

build_helpers/test_files/c/junk.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)