Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major changes to interpolations and resolvers #445

Merged
merged 77 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
03fa89a
Major changes to interpolations and resolvers
odelalleau Aug 16, 2020
4e375dc
Revert changes to docs notebook
odelalleau Nov 29, 2020
4ea5439
Simpler example for env default value in doc
odelalleau Nov 29, 2020
088df9f
Minor doc code style improvement
odelalleau Nov 29, 2020
2e14cd2
Properly identify changes to `register_resolver()` as API change in news
odelalleau Nov 29, 2020
071d225
Make `_get_value()` work with containers that are missing or interpol…
odelalleau Nov 29, 2020
6563efe
Simplify `get_value_kind()`
odelalleau Nov 29, 2020
8cd57e1
Minor reformatting
odelalleau Nov 29, 2020
3b63e67
More readable error message formatting
odelalleau Nov 29, 2020
d97d15c
Fix typo in comment
odelalleau Nov 29, 2020
459c845
Simplify grammar by removing the `toplevel` context
odelalleau Nov 29, 2020
4c361d0
Refactoring only: move method to preserve alphabetical order
odelalleau Nov 29, 2020
2d4917e
Refactoring: move _make_hashable() to _utils
odelalleau Nov 29, 2020
642bb47
Use monkeypatch to set/del env variables in tests
odelalleau Nov 29, 2020
e58bfa9
Restore new notebook so that tests can pass
odelalleau Nov 29, 2020
c8acfa2
Fix coverage to reach 100% again
odelalleau Nov 29, 2020
88583ca
Minor fix to notebook text (now matches the doc)
odelalleau Dec 7, 2020
4b503d0
Add missing test for @ character in unquoted strings
odelalleau Dec 6, 2020
84537e6
Allow most characters in node interpolations' keys
odelalleau Dec 6, 2020
8c53fc9
Dictionary keys can now be almost any primitive (except with ':')
odelalleau Dec 10, 2020
c069aa3
Improvements to tutorial notebook
odelalleau Dec 14, 2020
c953ffd
Add doc about non-leaf node interpolation
odelalleau Dec 15, 2020
3a932bc
Update news items
odelalleau Dec 15, 2020
1c064c0
Restore assert to be sure equality check returns a bool
odelalleau Dec 15, 2020
1d53141
Update doc to match recent notebook changes
odelalleau Dec 15, 2020
5da0747
Do not allow interpolations as dictionary keys
odelalleau Dec 20, 2020
4b7c4de
Refactoring only: move method in file
odelalleau Dec 20, 2020
bc71899
Minor doc reformatting
odelalleau Feb 5, 2021
7ea0599
Resolver output is now validated and converted for typed nodes
odelalleau Feb 5, 2021
95f3acb
Replace yaml configs with dicts in doc for consistency
odelalleau Feb 5, 2021
0d1925b
Minor code reformatting
odelalleau Feb 5, 2021
ea38786
Clarify comment
odelalleau Feb 5, 2021
0e0a0a1
Rename interpolation methods for clarity
odelalleau Feb 5, 2021
36b14b4
Small optimization
odelalleau Feb 5, 2021
9e661b4
Fix a bunch of trivial errors from the rebase
odelalleau Feb 5, 2021
d53ffd4
Small optimization
odelalleau Feb 5, 2021
483656c
Fix a couple of tests
odelalleau Feb 5, 2021
ce6670d
Restore proper error for interpolations without parent
odelalleau Feb 5, 2021
0fc1ce0
All tests now pass again
odelalleau Feb 5, 2021
fe2c389
Fix nox benchmark session
odelalleau Feb 5, 2021
c47cdba
Remove doc (now included in another PR)
odelalleau Feb 6, 2021
272cc7a
More concise cache example in doc
odelalleau Feb 6, 2021
01ade3e
Rename 445.api_change.1 --> 426.api_change
odelalleau Feb 6, 2021
b470c8d
Update news message associated to issue 426
odelalleau Feb 6, 2021
48368d8
Rename local_install -> editable_install
odelalleau Feb 6, 2021
9ba5a0e
Remove leftover comment
odelalleau Feb 6, 2021
5b9ca54
Update doc regarding type validation for interpolations (also fix err…
odelalleau Feb 6, 2021
dd2acf0
Revert "Update doc regarding type validation for interpolations (also…
odelalleau Feb 8, 2021
fcb8567
Revert "Resolver output is now validated and converted for typed nodes"
odelalleau Feb 8, 2021
77b8761
Remove now duplicated news item
odelalleau Feb 8, 2021
35fdcfe
Remove DeepCode statement for now
odelalleau Feb 8, 2021
f9b7fd6
Use monkeypatch to set env variables in tests
odelalleau Feb 8, 2021
210c435
Split and parameterize the test validating characters in key names
odelalleau Feb 8, 2021
b8803bf
Refactor interpolation grammar tests
odelalleau Feb 9, 2021
57934b0
Add comment to justify using "${" to detect interpolations
odelalleau Feb 9, 2021
e78142a
Fix coverage
odelalleau Feb 9, 2021
7c0ab04
Test to see if DeepCode is happier
odelalleau Feb 9, 2021
977f2ee
Revert "Test to see if DeepCode is happier"
odelalleau Feb 9, 2021
7bcd572
Rename new_register_resolver() -> register_new_resolver()
odelalleau Feb 10, 2021
7b47c52
Disable deprecation warning for register_resolver()
odelalleau Feb 10, 2021
262b05b
Fix a few minor issues after rebase on top of current master
odelalleau Feb 10, 2021
7c53ed5
Fix coverage
odelalleau Feb 10, 2021
9540b47
Add test & news for handling of `null` as default value to the env re…
odelalleau Feb 10, 2021
0d00b72
Use f-string instead of `.format()`
odelalleau Feb 10, 2021
5335ff5
Run more tests with `register_resolver()`
odelalleau Feb 10, 2021
0083a38
Remove unused function in tests
odelalleau Feb 10, 2021
3c45c9c
Safer handling of $ signs in error messages
odelalleau Feb 10, 2021
7d1907b
Detect invalid interpolation syntax on assignment
odelalleau Feb 10, 2021
e7147b6
Move grammar tests to their own file
odelalleau Feb 10, 2021
1cc2d27
Cleaner implementation for interpolation syntax check on assignment
odelalleau Feb 11, 2021
d19d078
Implement a cache for grammar objects
odelalleau Feb 11, 2021
bbe0d1f
Optimize the detection of basic interpolation patterns
odelalleau Feb 11, 2021
dcf8952
Minor (no functional change)
odelalleau Feb 11, 2021
ed80d13
Add test of escaped interpolations with simple regex matching
odelalleau Feb 11, 2021
5cb75f6
Minor news formatting improvement
odelalleau Feb 11, 2021
3993195
Minor change to fix a DeepCode warning
odelalleau Feb 12, 2021
d22e372
Disable spurious DeepCode warning
odelalleau Feb 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 29 additions & 35 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,41 @@
version: 2.1

jobs:
py36_linux:
docker:
- image: circleci/python:3.6
steps:
- checkout
- run: echo 'export NOX_PYTHON_VERSIONS=3.6' >> $BASH_ENV
- run: sudo pip install nox
- run: nox --add-timestamp

py37_linux:
docker:
- image: circleci/python:3.7
commands:
linux:
description: "Commands run on Linux"
parameters:
py_version:
type: string
steps:
- checkout
- run: echo 'export NOX_PYTHON_VERSIONS=3.7' >> $BASH_ENV
- run: sudo pip install nox
- run: nox --add-timestamp
- run:
name: "Preparing environment"
command: |
sudo apt-get update
sudo apt-get install -y openjdk-11-jre
sudo pip install nox
- run:
name: "Testing OmegaConf"
command: |
export NOX_PYTHON_VERSIONS=<< parameters.py_version >>
nox --add-timestamp

py38_linux:
docker:
- image: circleci/python:3.8
steps:
- checkout
- run: echo 'export NOX_PYTHON_VERSIONS=3.8' >> $BASH_ENV
- run: sudo pip install nox
- run: nox --add-timestamp

py39_linux:
jobs:
test_linux:
parameters:
py_version:
type: string
docker:
- image: circleci/python:3.9
- image: circleci/python:<< parameters.py_version >>
steps:
- checkout
- run: echo 'export NOX_PYTHON_VERSIONS=3.9' >> $BASH_ENV
- run: sudo pip install nox
- run: nox --add-timestamp
- linux:
py_version: << parameters.py_version >>

workflows:
version: 2
build:
jobs:
- py36_linux
- py37_linux
- py38_linux
- py39_linux
- test_linux:
matrix:
parameters:
py_version: ["3.6", "3.7", "3.8", "3.9"]
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ omit =
.nox/*
*tests*
docs/*
omegaconf/grammar/gen/*
omegaconf/version.py
.stubs

Expand All @@ -15,7 +16,7 @@ exclude_lines =
assert False
@abstractmethod
\.\.\.

if TYPE_CHECKING:

[html]
directory = docs/build/coverage
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
exclude = .git,.nox,.tox
exclude = .git,.nox,.tox,omegaconf/grammar/gen
max-line-length = 119
select = E,F,W,C
ignore=W503,E203
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.jar binary
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ TODO
.coverage
.eggs
.mypy_cache
/omegaconf/grammar/gen
/pip-wheel-metadata
/.pyre
.dmypy.json
.python-version
.vscode
1 change: 1 addition & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ line_length=88
ensure_newline_before_comments=True
known_third_party=attr,pytest
known_first_party=omegaconf
skip=.eggs,.nox,omegaconf/grammar/gen
21 changes: 21 additions & 0 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pytest import fixture, mark, param

from omegaconf import OmegaConf
from omegaconf._utils import ValueKind, get_value_kind


def build_dict(
Expand Down Expand Up @@ -119,3 +120,23 @@ def iterate(seq: Any) -> None:
pass

benchmark(iterate, lst)


@mark.parametrize(
"strict_interpolation_validation",
[True, False],
)
@mark.parametrize(
("value", "expected"),
[
("simple", ValueKind.VALUE),
("${a}", ValueKind.INTERPOLATION),
("${a:b,c,d}", ValueKind.INTERPOLATION),
("${${b}}", ValueKind.INTERPOLATION),
("${a:${b}}", ValueKind.INTERPOLATION),
],
)
def test_get_value_kind(
strict_interpolation_validation: bool, value: Any, expected: Any, benchmark: Any
) -> None:
assert benchmark(get_value_kind, value, strict_interpolation_validation) == expected
3 changes: 3 additions & 0 deletions build_helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Order of imports is important (see warning otherwise when running tests)
import setuptools # isort:skip # noqa
import distutils # isort:skip # noqa
Binary file added build_helpers/bin/antlr-4.8-complete.jar
Binary file not shown.
195 changes: 195 additions & 0 deletions build_helpers/build_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import codecs
import distutils.log
import errno
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Optional

from setuptools import Command
from setuptools.command import build_py, develop, sdist # type: ignore


class ANTLRCommand(Command): # type: ignore # pragma: no cover
"""Generate parsers using ANTLR."""

description = "Run ANTLR"
user_options: List[str] = []

def run(self) -> None:
"""Run command."""
build_dir = Path(__file__).parent.absolute()
project_root = build_dir.parent
for grammar in [
"OmegaConfGrammarLexer.g4",
"OmegaConfGrammarParser.g4",
]:
command = [
"java",
"-jar",
str(build_dir / "bin" / "antlr-4.8-complete.jar"),
"-Dlanguage=Python3",
"-o",
str(project_root / "omegaconf" / "grammar" / "gen"),
"-Xexact-output-dir",
"-visitor",
str(project_root / "omegaconf" / "grammar" / grammar),
]

self.announce(
f"Generating parser for Python3: {command}",
level=distutils.log.INFO,
)

subprocess.check_call(command)

def initialize_options(self) -> None:
pass

def finalize_options(self) -> None:
pass


class BuildPyCommand(build_py.build_py): # type: ignore # pragma: no cover
def run(self) -> None:
if not self.dry_run:
self.run_command("clean")
run_antlr(self)
build_py.build_py.run(self)


class CleanCommand(Command): # type: ignore # pragma: no cover
"""
Our custom command to clean out junk files.
"""

description = "Cleans out generated and junk files we don't want in the repo"
dry_run: bool
user_options: List[str] = []

def run(self) -> None:
root = Path(__file__).parent.parent.absolute()
files = find(
root=root,
include_files=["^omegaconf/grammar/gen/.*"],
include_dirs=[
"^omegaconf\\.egg-info$",
"\\.eggs$",
"^\\.mypy_cache$",
"^\\.nox$",
"^\\.pytest_cache$",
".*/__pycache__$",
"^__pycache__$",
"^build$",
"^dist$",
],
scan_exclude=["^.git$", "^.nox/.*$"],
excludes=[".*\\.gitignore$", ".*/__init__.py"],
)

if self.dry_run:
print("Dry run! Would clean up the following files and dirs:")
print("\n".join(sorted(map(str, files))))
else:
for f in files:
if f.exists():
if f.is_dir():
shutil.rmtree(f, ignore_errors=True)
else:
f.unlink()

def initialize_options(self) -> None:
pass

def finalize_options(self) -> None:
pass


class DevelopCommand(develop.develop): # type: ignore # pragma: no cover
def run(self) -> None:
if not self.dry_run:
run_antlr(self)
develop.develop.run(self)


class SDistCommand(sdist.sdist): # type: ignore # pragma: no cover
def run(self) -> None:
if not self.dry_run:
self.run_command("clean")
run_antlr(self)
sdist.sdist.run(self)


def find(
root: Path,
include_files: List[str],
include_dirs: List[str],
excludes: List[str],
rbase: Optional[Path] = None,
scan_exclude: Optional[List[str]] = None,
) -> List[Path]:
if rbase is None:
rbase = Path()
if scan_exclude is None:
scan_exclude = []
files = []
scan_root = root / rbase
for entry in scan_root.iterdir():
path = rbase / entry.name
if matches(scan_exclude, path):
continue

if entry.is_dir():
if matches(include_dirs, path):
if not matches(excludes, path):
files.append(path)
else:
ret = find(
root=root,
include_files=include_files,
include_dirs=include_dirs,
excludes=excludes,
rbase=path,
scan_exclude=scan_exclude,
)
files.extend(ret)
else:
if matches(include_files, path) and not matches(excludes, path):
files.append(path)

return files


def find_version(*file_paths: str) -> str:
root = Path(__file__).parent.parent.absolute()
with codecs.open(root / Path(*file_paths), "r") as fp: # type: ignore
version_file = fp.read()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.") # pragma: no cover


def matches(patterns: List[str], path: Path) -> bool:
string = str(path).replace(os.sep, "/") # for Windows
for pattern in patterns:
if re.match(pattern, string):
return True
return False


def run_antlr(cmd: Command) -> None: # pragma: no cover
try:
cmd.announce("Generating parsers with antlr4", level=distutils.log.INFO)
cmd.run_command("antlr")
except OSError as e:
if e.errno == errno.ENOENT:
msg = f"| Unable to generate parsers: {e} |"
msg = "=" * len(msg) + "\n" + msg + "\n" + "=" * len(msg)
cmd.announce(f"{msg}", level=distutils.log.FATAL)
sys.exit(1)
else:
raise
1 change: 1 addition & 0 deletions build_helpers/test_files/a/b/bad_dir/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Intentionally left empty for git to keep the directory
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions build_helpers/test_files/c/bad_dir/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Intentionally left empty for git to keep the directory
Empty file.
Empty file.
Empty file.
Loading