Skip to content

Commit

Permalink
Resolve conflits
Browse files Browse the repository at this point in the history
  • Loading branch information
WangGithubUser committed Jan 12, 2025
1 parent 28a2ec9 commit ac0ac79
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 24 deletions.
6 changes: 6 additions & 0 deletions .pyre_configuration
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
{
"site-package": "tabulate"
},
{
"site-package": "tomli"
},
{
"site-package": "tomli_w"
},
{
"is_toplevel_module": true,
"site-package": "typing_extensions"
Expand Down
49 changes: 43 additions & 6 deletions client/commands/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@
from pathlib import Path
from typing import Any, Dict, Optional, Tuple, Union

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

import tomli_w

from .. import log
from ..find_directories import (
BINARY_NAME,
CONFIGURATION_FILE,
JSON_CONFIGURATION_FILE,
TOML_CONFIGURATION_FILE,
find_global_root,
find_parent_directory_containing_file,
find_taint_models_directories,
Expand Down Expand Up @@ -55,7 +63,7 @@ def _create_source_directory_element(source: str) -> Union[str, Dict[str, str]]:
return source


def _check_configuration_file_location(
def _check_json_configuration_file_location(
configuration_path: Path, current_directory: Path, global_root: Optional[Path]
) -> None:
if os.path.isfile(configuration_path):
Expand All @@ -78,6 +86,18 @@ def _check_configuration_file_location(
+ f"`{str(local_configuration_path)}`."
)

def _check_toml_configuration_file_content(
configuration_path: Path, global_root: Optional[Path]
) -> None:
if configuration_path.is_file() and not global_root:
toml_configurations = tomllib.loads(configuration_path.read_text())
try:
toml_configurations["tool"]["pyre"]
except KeyError:
return
raise InitializationException(
f"A Pyre configuration already exists at {str(configuration_path)}"
)

def _get_local_configuration(
current_directory: Path, buck_root: Optional[Path]
Expand Down Expand Up @@ -218,15 +238,27 @@ def get_configuration_and_path(
Path("."), ".buckconfig"
)
current_directory: Path = Path(os.getcwd())
configuration_path = current_directory / CONFIGURATION_FILE
_check_configuration_file_location(
configuration_path, current_directory, global_root
configuration_path_json = current_directory / JSON_CONFIGURATION_FILE
configuration_path_toml = current_directory / TOML_CONFIGURATION_FILE
_check_json_configuration_file_location(
configuration_path_json, current_directory, global_root
)
_check_toml_configuration_file_content(configuration_path_toml, global_root)
local_configuration_path = current_directory / LOCAL_CONFIGURATION_FILE
if global_root:
configuration_path = local_configuration_path
configuration = _get_local_configuration(current_directory, buck_root)
else:
where_to_store = log.get_input(
f"Should pyre store configuration at `{JSON_CONFIGURATION_FILE}` or `{TOML_CONFIGURATION_FILE}`?",
suffix="Type `JSON` or `TOML` to answer(lower case work as well).Default is `JSON`",
).upper()
if where_to_store in ("JSON", ""):
configuration_path = configuration_path_json
elif where_to_store == "TOML":
configuration_path = configuration_path_toml
else:
raise InitializationException("Please answer `JSON` or `TOML`.")
configuration = _get_configuration(taint_models_directory_required)
return configuration, configuration_path

Expand All @@ -235,7 +267,12 @@ def write_configuration(
configuration: Dict[str, Any], configuration_path: Path
) -> None:
with open(configuration_path, "w+") as configuration_file:
json.dump(configuration, configuration_file, sort_keys=True, indent=2)
if configuration_path.suffix.lower() == ".toml":
configuration = {"tool": {"pyre": configuration}} # [tool.pyre] section
tomli_w.dump(configuration, configuration_file)
else:
# assume to JSON
json.dump(configuration, configuration_file, sort_keys=True, indent=2)
configuration_file.write("\n")


Expand Down
2 changes: 1 addition & 1 deletion client/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def get_full_path(root: str, relative: str) -> str:
return str(full_path)

# TODO(T137504540) update critical files for overridden configs as well
configuration_name = find_directories.CONFIGURATION_FILE
configuration_name = find_directories.JSON_CONFIGURATION_FILE
local_root = configuration.get_local_root()
return [
CriticalFile(
Expand Down
46 changes: 34 additions & 12 deletions client/configuration/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,16 @@

import psutil

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

from .. import command_arguments, dataclasses_merge, find_directories, identifiers
from ..filesystem import expand_global_root, expand_relative_path
from ..find_directories import (
CONFIGURATION_FILE,
JSON_CONFIGURATION_FILE,
TOML_CONFIGURATION_FILE,
get_relative_local_root,
LOCAL_CONFIGURATION_FILE,
)
Expand Down Expand Up @@ -250,7 +256,7 @@ def from_command_arguments(
)

@staticmethod
def from_string(contents: str) -> "PartialConfiguration":
def from_string(contents: str, is_pyproject_dot_toml: bool = False) -> "PartialConfiguration":
def is_list_of_string(elements: object) -> bool:
return isinstance(elements, list) and all(
isinstance(element, str) for element in elements
Expand Down Expand Up @@ -337,7 +343,13 @@ def create_search_paths(
return search_path

try:
configuration_json = json.loads(contents)
if is_pyproject_dot_toml:
# For usual, we write pyproject.toml as
# [tool.pyre]
# ...<configurations>
configuration_json = tomllib.loads(contents)["tool"]["pyre"]
else:
configuration_json = json.loads(contents)

dot_pyre_directory = ensure_option_type(
configuration_json, "dot_pyre_directory", str
Expand Down Expand Up @@ -500,14 +512,16 @@ def create_search_paths(
LOG.warning(f"Unrecognized configuration item: {unrecognized_key}")

return partial_configuration
except json.JSONDecodeError as error:
raise exceptions.InvalidConfiguration("Invalid JSON file") from error
except (json.JSONDecodeError, tomllib.TOMLDecodeError) as error:
raise exceptions.InvalidConfiguration(
f'Invalid {"TOML" if is_pyproject_dot_toml else "JSON"} file'
) from error

@staticmethod
def from_file(path: Path) -> "PartialConfiguration":
def from_file(path: Path, is_pyproject_dot_toml: bool = False) -> "PartialConfiguration":
try:
contents = path.read_text(encoding="utf-8")
return PartialConfiguration.from_string(contents)
return PartialConfiguration.from_string(contents, is_pyproject_dot_toml)
except OSError as error:
raise exceptions.InvalidConfiguration(
f"Error when reading {path}"
Expand Down Expand Up @@ -948,8 +962,8 @@ def create_configuration(
if local_root_argument is not None:
if found_root is None:
raise exceptions.InvalidConfiguration(
"A local configuration path was explicitly specified, but no"
+ f" {CONFIGURATION_FILE} file was found in {search_base}"
"A local configuration path was explicitly specified, but neither"
+ f" {JSON_CONFIGURATION_FILE} nor {TOML_CONFIGURATION_FILE} file was found in {search_base}"
+ " or its parents."
)
elif found_root.local_root is None:
Expand All @@ -969,9 +983,17 @@ def create_configuration(
else:
project_root = found_root.global_root
relative_local_root = None
partial_configuration = PartialConfiguration.from_file(
project_root / CONFIGURATION_FILE
).expand_relative_paths(str(project_root))
if (project_root / JSON_CONFIGURATION_FILE).is_file():
partial_configuration = PartialConfiguration.from_file(
project_root / JSON_CONFIGURATION_FILE
).expand_relative_paths(str(project_root))
else:
LOG.debug(
"Could not find `.pyre_configuration` in the project root.Searching for `pyproject.toml`..."
)
partial_configuration = PartialConfiguration.from_file(
project_root / TOML_CONFIGURATION_FILE, True
).expand_relative_paths(str(project_root))
local_root = found_root.local_root
if local_root is not None:
relative_local_root = get_relative_local_root(project_root, local_root)
Expand Down
55 changes: 55 additions & 0 deletions client/configuration/tests/configuration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import testslide

from ... import command_arguments
from ...find_directories import TOML_CONFIGURATION_FILE
from ...commands.initialize import write_configuration
from ...tests.setup import (
ensure_directories_exists,
ensure_files_exist,
Expand Down Expand Up @@ -1165,3 +1167,56 @@ def test_source_directories_glob(self) -> None:
SimpleElement(str(root_path / "b")),
],
)
def test_pyproject_dot_toml_configuration_from_string_and_file(self) -> None:
pyproject_config: str = """[tool.pyre]
exclude = ["sample/exclude1", "sample/exclude2"]
strict = true
"""
config = PartialConfiguration.from_string(pyproject_config, True)
self.assertEqual(config.excludes, ["sample/exclude1", "sample/exclude2"])
self.assertEqual(config.strict, True)
with tempfile.TemporaryDirectory() as root:
root_path = Path(root)
with open(
root_path / TOML_CONFIGURATION_FILE, "w", encoding="UTF-8"
) as configuration_file:
configuration_file.write(pyproject_config)
config = PartialConfiguration.from_file(
root_path / TOML_CONFIGURATION_FILE, True
)
self.assertEqual(config.excludes, ["sample/exclude1", "sample/exclude2"])
self.assertEqual(config.strict, True)

def test_pyproject_dot_toml_with_no_json_configuration_file(self) -> None:
pyproject_config: str = """[tool.pyre]
exclude = ["another/sample/exclude1", "another/sample/exclude2"]
strict = false
"""
with tempfile.TemporaryDirectory() as root:
root_path = Path(root)
with open(
root_path / TOML_CONFIGURATION_FILE, "w", encoding="UTF-8"
) as configuration_file:
configuration_file.write(pyproject_config)
config = create_configuration(
command_arguments.CommandArguments(), root_path
)
self.assertEqual(
config.excludes, ["another/sample/exclude1", "another/sample/exclude2"]
)
self.assertEqual(config.strict, False)
def test_write_to_pyproject_dot_toml(self) -> None:
with tempfile.TemporaryDirectory() as root:
root_path = Path(root)
config = {"excludes": ["sample/exclude1", "sample/exclude2"], "strict": True}
write_configuration(config, root_path / TOML_CONFIGURATION_FILE)
with open(
root_path / TOML_CONFIGURATION_FILE, "r", encoding="UTF-8"
) as configuration_file:
self.assertEqual(
configuration_file.read(),
"""[tool.pyre]
exclude = ["sample/exclude1", "sample/exclude2"]
strict = true
""",
)
9 changes: 6 additions & 3 deletions client/find_directories.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from pathlib import Path
from typing import Callable, List, NamedTuple, Optional

CONFIGURATION_FILE: str = ".pyre_configuration"
JSON_CONFIGURATION_FILE: str = ".pyre_configuration"
TOML_CONFIGURATION_FILE: str = "pyproject.toml"
LOCAL_CONFIGURATION_FILE: str = ".pyre_configuration.local"
BINARY_NAME: str = "pyre.bin"
CLIENT_NAME: str = "pyre-client"
Expand Down Expand Up @@ -122,7 +123,7 @@ def find_outermost_directory_containing_file(
def find_global_root(base: Path) -> Optional[Path]:
"""Pyre always runs from the directory containing the nearest .pyre_configuration,
if one exists."""
return find_parent_directory_containing_file(base, CONFIGURATION_FILE)
return find_parent_directory_containing_file(base, JSON_CONFIGURATION_FILE) or find_parent_directory_containing_file(base, TOML_CONFIGURATION_FILE)


def get_relative_local_root(
Expand Down Expand Up @@ -152,7 +153,9 @@ def find_global_and_local_root(base: Path) -> Optional[FoundRoot]:
return the path to the global configuration.
If both global and local exist, return them as a pair.
"""
found_global_root = find_parent_directory_containing_file(base, CONFIGURATION_FILE)
found_global_root = find_parent_directory_containing_file(
base, JSON_CONFIGURATION_FILE
) or find_parent_directory_containing_file(base, TOML_CONFIGURATION_FILE)
if found_global_root is None:
return None

Expand Down
4 changes: 2 additions & 2 deletions client/tests/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

from pyre_extensions import ParameterSpecification

from ..find_directories import CONFIGURATION_FILE, LOCAL_CONFIGURATION_FILE
from ..find_directories import JSON_CONFIGURATION_FILE, LOCAL_CONFIGURATION_FILE

TParams = ParameterSpecification("TParams")
T = TypeVar("T")
Expand All @@ -59,7 +59,7 @@ def write_configuration_file(
content: Mapping[str, Any],
relative: Optional[str] = None,
) -> None:
configuration_file = CONFIGURATION_FILE
configuration_file = JSON_CONFIGURATION_FILE
if relative is None:
(root / configuration_file).write_text(json.dumps(content))
else:
Expand Down
32 changes: 32 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,35 @@ excludes = [
"stubs/",
"pyre2/",
]

[tool.pyre]
exclude = [
".*/documentation/.*",
".*/generate_taint_models/.*",
".*/scripts/.*",
".*/source/.*",
".*/pyre-check/stubs/.*",
]
ignore_all_errors = [
"client/tests/configuration_test.py",
"pyre_extensions/safe_json.py",
]
search_path = [
{ site-package = "click" },
{ site-package = "dataclasses_json" },
{ site-package = "flask" },
{ site-package = "flask_cors" },
{ site-package = "graphql" },
{ site-package = "libcst" },
{ site-package = "marshmallow" },
{ site-package = "psutil" },
{ site-package = "testslide" },
{ site-package = "toml" },
{ site-package = "tabulate" },
{ site-package = "tomli" },
{ site-package = "tomli_w" },
{ is_toplevel_module = true, site-package = "typing_extensions" },
{ is_toplevel_module = true, site-package = "typing_inspect" },
]
source_directories = ["."]
strict = true
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ tabulate
testslide>=2.7.0
typing_extensions
typing_inspect
tomli
tomli-w

0 comments on commit ac0ac79

Please sign in to comment.