mirror of
https://github.com/davidhalter/parso.git
synced 2025-12-30 08:04:40 +08:00
Change Parso to use cPickle instead of pickle when reading/writing the cache, which speeds up the cache significantly. In Python 2, cPickle is up to 1000 times faster than pickle. (In Python 3, if you "import pickle", you are actually getting cPickle.) As is the convention, the code tries to import cPickle, and if that fails, it falls back to pickle. This has a big impact for users of jedi-vim, since in many cases Vim uses Python 2.
163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
import time
|
|
import os
|
|
import sys
|
|
import hashlib
|
|
import gc
|
|
import shutil
|
|
import platform
|
|
import errno
|
|
import logging
|
|
|
|
try:
|
|
import cPickle as pickle
|
|
except:
|
|
import pickle
|
|
|
|
from parso._compatibility import FileNotFoundError
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
_PICKLE_VERSION = 30
|
|
"""
|
|
Version number (integer) for file system cache.
|
|
|
|
Increment this number when there are any incompatible changes in
|
|
the parser tree classes. For example, the following changes
|
|
are regarded as incompatible.
|
|
|
|
- A class name is changed.
|
|
- A class is moved to another module.
|
|
- A __slot__ of a class is changed.
|
|
"""
|
|
|
|
_VERSION_TAG = '%s-%s%s-%s' % (
|
|
platform.python_implementation(),
|
|
sys.version_info[0],
|
|
sys.version_info[1],
|
|
_PICKLE_VERSION
|
|
)
|
|
"""
|
|
Short name for distinguish Python implementations and versions.
|
|
|
|
It's like `sys.implementation.cache_tag` but for Python < 3.3
|
|
we generate something similar. See:
|
|
http://docs.python.org/3/library/sys.html#sys.implementation
|
|
"""
|
|
|
|
def _get_default_cache_path():
|
|
if platform.system().lower() == 'windows':
|
|
dir_ = os.path.join(os.getenv('LOCALAPPDATA') or '~', 'Parso', 'Parso')
|
|
elif platform.system().lower() == 'darwin':
|
|
dir_ = os.path.join('~', 'Library', 'Caches', 'Parso')
|
|
else:
|
|
dir_ = os.path.join(os.getenv('XDG_CACHE_HOME') or '~/.cache', 'parso')
|
|
return os.path.expanduser(dir_)
|
|
|
|
_default_cache_path = _get_default_cache_path()
|
|
"""
|
|
The path where the cache is stored.
|
|
|
|
On Linux, this defaults to ``~/.cache/parso/``, on OS X to
|
|
``~/Library/Caches/Parso/`` and on Windows to ``%LOCALAPPDATA%\\Parso\\Parso\\``.
|
|
On Linux, if environment variable ``$XDG_CACHE_HOME`` is set,
|
|
``$XDG_CACHE_HOME/parso`` is used instead of the default one.
|
|
"""
|
|
|
|
parser_cache = {}
|
|
|
|
|
|
class _NodeCacheItem(object):
|
|
def __init__(self, node, lines, change_time=None):
|
|
self.node = node
|
|
self.lines = lines
|
|
if change_time is None:
|
|
change_time = time.time()
|
|
self.change_time = change_time
|
|
|
|
|
|
def load_module(hashed_grammar, path, cache_path=None):
|
|
"""
|
|
Returns a module or None, if it fails.
|
|
"""
|
|
try:
|
|
p_time = os.path.getmtime(path)
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
try:
|
|
module_cache_item = parser_cache[hashed_grammar][path]
|
|
if p_time <= module_cache_item.change_time:
|
|
return module_cache_item.node
|
|
except KeyError:
|
|
return _load_from_file_system(hashed_grammar, path, p_time, cache_path=cache_path)
|
|
|
|
|
|
def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
|
|
cache_path = _get_hashed_path(hashed_grammar, path, cache_path=cache_path)
|
|
try:
|
|
try:
|
|
if p_time > os.path.getmtime(cache_path):
|
|
# Cache is outdated
|
|
return None
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
# In Python 2 instead of an IOError here we get an OSError.
|
|
raise FileNotFoundError
|
|
else:
|
|
raise
|
|
|
|
with open(cache_path, 'rb') as f:
|
|
gc.disable()
|
|
try:
|
|
module_cache_item = pickle.load(f)
|
|
finally:
|
|
gc.enable()
|
|
except FileNotFoundError:
|
|
return None
|
|
else:
|
|
parser_cache.setdefault(hashed_grammar, {})[path] = module_cache_item
|
|
LOG.debug('pickle loaded: %s', path)
|
|
return module_cache_item.node
|
|
|
|
|
|
def save_module(hashed_grammar, path, module, lines, pickling=True, cache_path=None):
|
|
try:
|
|
p_time = None if path is None else os.path.getmtime(path)
|
|
except OSError:
|
|
p_time = None
|
|
pickling = False
|
|
|
|
item = _NodeCacheItem(module, lines, p_time)
|
|
parser_cache.setdefault(hashed_grammar, {})[path] = item
|
|
if pickling and path is not None:
|
|
_save_to_file_system(hashed_grammar, path, item, cache_path=cache_path)
|
|
|
|
|
|
def _save_to_file_system(hashed_grammar, path, item, cache_path=None):
|
|
with open(_get_hashed_path(hashed_grammar, path, cache_path=cache_path), 'wb') as f:
|
|
pickle.dump(item, f, pickle.HIGHEST_PROTOCOL)
|
|
|
|
|
|
def clear_cache(cache_path=None):
|
|
if cache_path is None:
|
|
cache_path = _default_cache_path
|
|
shutil.rmtree(cache_path)
|
|
parser_cache.clear()
|
|
|
|
|
|
def _get_hashed_path(hashed_grammar, path, cache_path=None):
|
|
directory = _get_cache_directory_path(cache_path=cache_path)
|
|
|
|
file_hash = hashlib.sha256(path.encode("utf-8")).hexdigest()
|
|
return os.path.join(directory, '%s-%s.pkl' % (hashed_grammar, file_hash))
|
|
|
|
|
|
def _get_cache_directory_path(cache_path=None):
|
|
if cache_path is None:
|
|
cache_path = _default_cache_path
|
|
directory = os.path.join(cache_path, _VERSION_TAG)
|
|
if not os.path.exists(directory):
|
|
os.makedirs(directory)
|
|
return directory
|