Skip to content

Commit

Permalink
Merge pull request #1595 from google/google_sync
Browse files Browse the repository at this point in the history
Google sync
  • Loading branch information
rchen152 authored Mar 4, 2024
2 parents 5201c54 + a4872f6 commit 95cb0b7
Show file tree
Hide file tree
Showing 46 changed files with 930 additions and 544 deletions.
11 changes: 5 additions & 6 deletions pytype/abstract/_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import logging
from typing import Any, Dict, List, Optional, Tuple, Union

import attrs

from pytype import datatypes
from pytype.abstract import _base
from pytype.abstract import _instance_base
Expand Down Expand Up @@ -435,7 +433,7 @@ def _init_attr_metadata_from_pytd(self, decorator):
params = []
for p in init.signatures[0].params[1:]:
if p.name in protected:
params.append(attrs.evolve(p, name=protected[p.name]))
params.append(p.Replace(name=protected[p.name]))
else:
params.append(p)
with self.ctx.allow_recursive_convert():
Expand Down Expand Up @@ -684,7 +682,8 @@ def __hash__(self):
for name, val in self.formal_type_parameters.items():
# The 'is not True' check is to prevent us from incorrectly caching
# the hash when val.resolved == LateAnnotation._RESOLVING.
if val.is_late_annotation() and val.resolved is not True: # pylint: disable=g-bool-id-comparison
if (val.is_late_annotation() and
val.resolved is not True): # pylint: disable=g-bool-id-comparison # pytype: disable=attribute-error
cache = False
items.append((name, val.full_name))
hashval = hash((self.base_cls, tuple(items)))
Expand Down Expand Up @@ -716,9 +715,9 @@ def members(self):
return self.base_cls.members

@property
def formal_type_parameters(self):
def formal_type_parameters(self) -> Dict[Union[str, int], _base.BaseValue]:
self._load_formal_type_parameters()
return self._formal_type_parameters
return self._formal_type_parameters # pytype: disable=bad-return-type

def _load_formal_type_parameters(self):
if self._formal_type_parameters_loaded:
Expand Down
11 changes: 7 additions & 4 deletions pytype/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,13 @@ def _get_attribute(self, node, obj, cls, name, valself):
if attr:
# If the attribute is a method, then we allow it to take precedence over
# the possible unknown instance attribute, since otherwise method lookup
# on classes with _HAS_DYNAMIC_ATTRIBUTES would always return Any.
if (is_unknown_instance_attribute and
any(isinstance(v, abstract.FUNCTION_TYPES) for v in attr.data)):
is_unknown_instance_attribute = False
# on classes with _HAS_DYNAMIC_ATTRIBUTES would always return Any. We
# look up the attribute a second time, using a lookup method that leaves
# properties as methods, so that properties are not replaced with Any.
if is_unknown_instance_attribute:
attr2 = self._lookup_from_mro(node, cls, name, valself, ())
if any(isinstance(v, abstract.FUNCTION_TYPES) for v in attr2.data):
is_unknown_instance_attribute = False
elif not is_unknown_instance_attribute:
# Fall back to __getattr__ if the attribute doesn't otherwise exist.
node, attr = self._get_attribute_computed(
Expand Down
19 changes: 9 additions & 10 deletions pytype/imports/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ py_library(
pytype.pytd.pytd
)

py_library(
NAME
serde_utils_api
SRCS
pickle_utils.py
DEPS
pytype.pytd.pytd
)

py_test(
NAME
builtin_stubs_test
Expand All @@ -91,16 +100,6 @@ py_test(
pytype.tests.test_base
)

py_test(
NAME
pickle_utils_test
SRCS
pickle_utils_test.py
DEPS
.pickle_utils
pytype.tests.test_base
)

py_test(
NAME
typeshed_test
Expand Down
2 changes: 1 addition & 1 deletion pytype/imports/module_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _load_pyi(self, mod_info: base.ModuleInfo):

def _load_pickle(self, mod_info: base.ModuleInfo):
"""Load and unpickle a serialized pytd AST."""
return pickle_utils.LoadPickle(
return pickle_utils.LoadAst(
mod_info.filename, open_function=self.options.open_function)

def load_ast(self, mod_info: base.ModuleInfo):
Expand Down
210 changes: 152 additions & 58 deletions pytype/imports/pickle_utils.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,178 @@
"""Pickle file loading and saving."""
"""Pickle file loading and saving.
This file is tested by serialize_ast_test.py.
"""

# We now use msgspec for serialization, which prompted a rewrite of this file's
# API. In the new API, only serialize_ast.SerializableAst and
# serialize_ast.ModuleBundle may be directly serialized and deserialized.
# There are methods for handling pytd.TypeDeclUnit objects, which will be
# turned into SerializableAst objects before serialization, and will be
# deserialized as SerializableAst objects.
# - Turn an object into binary data: (i.e. in-memory serialization.)
# - Old: SavePickle(filename=None), or StoreAst(filename=None) for
# pytd.TypeDeclUnit.
# - New: Encode(), or Serialize() for pytd.TypeDeclUnit.
# - Turn binary data into an object:
# - Old: LoadAst()
# - New: DecodeAst() for SerializableAst, DecodeBuiltins() for ModuleBundle.
# - Turn an object into binary data and save it to a file:
# - Old: SavePickle(filename=str), StoreAst(filename=str) for
# pytd.TypeDeclUnit.
# - New: Save() for SerializableAst and ModuleBundle, SerializeAndSave() for
# pytd.TypeDeclUnit.
# - Load binary data from a file and turn it into an object:
# - Old: LoadPickle()
# - New: LoadAst() for SerializeAst, LoadBuiltins() for ModuleBundle.
# - There is also PrepareModuleBundle, which takes an iterable of (typically
# builtin) modules to be encoded in one file.

import gzip
import pickle
import os
import sys
from typing import Iterable, Optional, Tuple, TypeVar, Union

from pytype.pytd import serialize_ast


_PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
_PICKLE_RECURSION_LIMIT_AST = 40000
import msgspec

from pytype.pytd import pytd
from pytype.pytd import serialize_ast

def LoadAst(data):
"""Load data that has been read from a pickled file."""
# This exists to consolidate all uses of pickle into one module.
return pickle.loads(data)
# In Python 3.8 and below, os.PathLike isn't subscriptable.
if sys.version_info[:2] >= (3, 9):
_StrPathLike = os.PathLike[str]
else:
_StrPathLike = os.PathLike
Path = Union[str, _StrPathLike]


class LoadPickleError(Exception):
"""Errors when loading a pickled pytd file."""

def __init__(self, filename):
self.filename = filename
msg = f"Error loading pickle file: {filename}"
def __init__(self, filename: Path):
self.filename = os.fspath(filename)
msg = f"Error loading pickle file: {self.filename}"
super().__init__(msg)

Encoder = msgspec.msgpack.Encoder()
AstDecoder = msgspec.msgpack.Decoder(type=serialize_ast.SerializableAst)
BuiltinsDecoder = msgspec.msgpack.Decoder(type=serialize_ast.ModuleBundle)

_DecT = TypeVar(
"_DecT", serialize_ast.SerializableAst, serialize_ast.ModuleBundle
)
_Dec = msgspec.msgpack.Decoder
_Serializable = Union[serialize_ast.SerializableAst, serialize_ast.ModuleBundle]


def _Load(
dec: "_Dec[_DecT]", filename: Path, compress: bool = False,
open_function=open,
) -> _DecT:
"""Loads a serialized file.
Args:
dec: The msgspec.Decoder to use.
filename: The file to read.
compress: if True, the file will be opened using gzip.
open_function: The function to open the file with.
Returns:
The decoded object.
def _LoadPickle(f, filename):
"""Load a pickle file, raising a custom exception on failure."""
Raises:
LoadPickleError, if there is an OSError, gzip error, or msgspec error.
"""
try:
return pickle.load(f)
except Exception as e: # pylint: disable=broad-except
with open_function(filename, "rb") as fi:
if compress:
with gzip.GzipFile(fileobj=fi) as zfi:
data = zfi.read()
else:
data = fi.read()
return dec.decode(data)
except (
OSError,
gzip.BadGzipFile,
msgspec.DecodeError,
msgspec.ValidationError,
) as e:
raise LoadPickleError(filename) from e


def LoadPickle(filename, compress=False, open_function=open):
with open_function(filename, "rb") as fi:
if compress:
with gzip.GzipFile(fileobj=fi) as zfi:
return _LoadPickle(zfi, filename)
else:
return _LoadPickle(fi, filename)
def DecodeAst(data: bytes) -> serialize_ast.SerializableAst:
return AstDecoder.decode(data)


def SavePickle(data, filename=None, compress=False, open_function=open):
"""Pickle the data."""
recursion_limit = sys.getrecursionlimit()
sys.setrecursionlimit(_PICKLE_RECURSION_LIMIT_AST)
assert not compress or filename, "gzip only supported with a filename"
try:
if compress:
with open_function(filename, mode="wb") as fi:
# We blank the filename and set the mtime explicitly to produce
# deterministic gzip files.
with gzip.GzipFile(filename="", mode="wb",
fileobj=fi, mtime=1.0) as zfi:
pickle.dump(data, zfi, _PICKLE_PROTOCOL)
elif filename is not None:
with open_function(filename, "wb") as fi:
pickle.dump(data, fi, _PICKLE_PROTOCOL)
else:
return pickle.dumps(data, _PICKLE_PROTOCOL)
finally:
sys.setrecursionlimit(recursion_limit)
def LoadAst(
filename: Path, compress: bool = False, open_function=open
) -> serialize_ast.SerializableAst:
return _Load(AstDecoder, filename, compress, open_function)


def StoreAst(
ast, filename=None, open_function=open, src_path=None, metadata=None):
"""Loads and stores an ast to disk.
def DecodeBuiltins(data: bytes) -> serialize_ast.ModuleBundle:
return BuiltinsDecoder.decode(data)

Args:
ast: The pytd.TypeDeclUnit to save to disk.
filename: The filename for the pickled output. If this is None, this
function instead returns the pickled string.
open_function: A custom file opening function.
src_path: Optionally, the filepath of the original source file.
metadata: A list of arbitrary string-encoded metadata.

Returns:
The pickled string, if no filename was given. (None otherwise.)
def LoadBuiltins(
filename: Path, compress: bool = False, open_function=open
) -> serialize_ast.ModuleBundle:
return _Load(BuiltinsDecoder, filename, compress, open_function)


def Encode(obj: _Serializable) -> bytes:
return Encoder.encode(obj)


def Save(
obj: _Serializable,
filename: Path,
compress: bool = False,
open_function=open,
) -> None:
"""Saves a serializable object to a file.
Args:
obj: The object to serialize.
filename: filename to write to.
compress: if True, the data will be compressed using gzip. The given
filename will be used, unaltered.
open_function: The function to use to open files. Defaults to the builtin
open() function.
"""
with open_function(filename, "wb") as fi:
if compress:
# We blank the filename and set the mtime explicitly to produce
# deterministic gzip files.
with gzip.GzipFile(filename="", mode="wb", fileobj=fi, mtime=1.0) as zfi:
zfi.write(Encode(obj))
else:
fi.write(Encode(obj))


def Serialize(
ast: pytd.TypeDeclUnit, src_path: Optional[str] = None, metadata=None
) -> bytes:
out = serialize_ast.SerializeAst(ast, src_path, metadata)
return Encode(out)


def SerializeAndSave(
ast: pytd.TypeDeclUnit,
filename: Path,
*,
compress: bool = False,
open_function=open,
src_path: Optional[str] = None,
metadata=None,
) -> None:
out = serialize_ast.SerializeAst(ast, src_path, metadata)
return SavePickle(out, filename, open_function=open_function)
Save(out, filename, compress, open_function)


def PrepareModuleBundle(
modules: Iterable[Tuple[str, str, pytd.TypeDeclUnit]]
) -> serialize_ast.ModuleBundle:
raw = lambda ast, filename: msgspec.Raw(Serialize(ast, src_path=filename))
return tuple(
((name, raw(module, filename)) for name, filename, module in modules)
)
29 changes: 0 additions & 29 deletions pytype/imports/pickle_utils_test.py

This file was deleted.

Loading

0 comments on commit 95cb0b7

Please sign in to comment.