client/commands/query.py (73 lines of code) (raw):

# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import dataclasses import json import logging from pathlib import Path from typing import TextIO from .. import configuration as configuration_module, log from . import commands, server_connection, remote_logging LOG: logging.Logger = logging.getLogger(__name__) HELP_MESSAGE: str = """ Possible queries: - attributes(class_name) Returns a list of attributes, including functions, for a class. - batch(query1(arg), query2(arg)) Runs a batch of queries and returns a map of responses. List of given queries may include any combination of other valid queries except for `batch` itself. - callees(function) Calls from a given function. - callees_with_location(function) Calls from a given function, including the locations at which they are called. - defines(module_or_class_name) Returns a JSON with the signature of all defines for given module or class. - dump_call_graph() Returns a comprehensive JSON of caller -> list of callees. - inline_decorators(qualified_function_name, decorators_to_skip=[decorator1, ...]) Returns the function definition after inlining decorators. Allows skipping certain decorators when inlining. - less_or_equal(T1, T2) Returns whether T1 is a subtype of T2. - path_of_module(module) Gives an absolute path for `module`. - save_server_state('path') Saves Pyre's serialized state into `path`. - superclasses(class_name1, class_name2, ...) Returns a mapping of class_name to the list of superclasses for `class_name`. If no class name is provided, return the mapping for all classes Pyre knows about. - type(expression) Evaluates the type of `expression`. - types(path='path') or types('path1', 'path2', ...) Returns a map from each given path to a list of all types for that path. - validate_taint_models('optional path') Validates models and returns errors. Defaults to model path in configuration if no parameter is passed in. """ class InvalidQueryResponse(Exception): pass @dataclasses.dataclass(frozen=True) class Response: payload: object def _print_help_message() -> None: log.stdout.write(HELP_MESSAGE) def parse_query_response_json(response_json: object) -> Response: if ( isinstance(response_json, list) and len(response_json) > 1 and response_json[0] == "Query" ): return Response(response_json[1]) raise InvalidQueryResponse(f"Unexpected JSON response from server: {response_json}") def parse_query_response(text: str) -> Response: try: response_json = json.loads(text) return parse_query_response_json(response_json) except json.JSONDecodeError as decode_error: message = f"Cannot parse response as JSON: {decode_error}" raise InvalidQueryResponse(message) from decode_error def _send_query_request(output_channel: TextIO, query_text: str) -> None: query_message = json.dumps(["Query", query_text]) LOG.debug(f"Sending `{log.truncate(query_message, 400)}`") output_channel.write(f"{query_message}\n") def _receive_query_response(input_channel: TextIO) -> Response: query_message = input_channel.readline().strip() LOG.debug(f"Received `{log.truncate(query_message, 400)}`") return parse_query_response(query_message) def query_server(socket_path: Path, query_text: str) -> Response: with server_connection.connect_in_text_mode(socket_path) as ( input_channel, output_channel, ): _send_query_request(output_channel, query_text) return _receive_query_response(input_channel) @remote_logging.log_usage(command_name="query") def run( configuration: configuration_module.Configuration, query_text: str ) -> commands.ExitCode: socket_path = server_connection.get_default_socket_path( project_root=Path(configuration.project_root), relative_local_root=Path(configuration.relative_local_root) if configuration.relative_local_root else None, ) try: if query_text == "help": _print_help_message() return commands.ExitCode.SUCCESS response = query_server(socket_path, query_text) log.stdout.write(json.dumps(response.payload)) return commands.ExitCode.SUCCESS except server_connection.ConnectionFailure: LOG.warning( "A running Pyre server is required for queries to be responded. " "Please run `pyre` first to set up a server." ) return commands.ExitCode.SERVER_NOT_FOUND except Exception as error: raise commands.ClientException( f"Exception occurred during pyre query: {error}" ) from error