Add FileIO to make it possible to cache e.g. files from zip files

This commit is contained in:
Dave Halter
2019-03-25 00:48:59 +01:00
parent e77a67cd36
commit 3e2956264c
4 changed files with 58 additions and 20 deletions

View File

@@ -78,21 +78,26 @@ class _NodeCacheItem(object):
self.change_time = change_time self.change_time = change_time
def load_module(hashed_grammar, path, cache_path=None): def load_module(hashed_grammar, file_io, cache_path=None):
""" """
Returns a module or None, if it fails. Returns a module or None, if it fails.
""" """
try: try:
p_time = os.path.getmtime(path) p_time = file_io.get_last_modified()
except FileNotFoundError: except FileNotFoundError:
return None return None
try: try:
module_cache_item = parser_cache[hashed_grammar][path] module_cache_item = parser_cache[hashed_grammar][file_io.path]
if p_time <= module_cache_item.change_time: if p_time <= module_cache_item.change_time:
return module_cache_item.node return module_cache_item.node
except KeyError: except KeyError:
return _load_from_file_system(hashed_grammar, path, p_time, cache_path=cache_path) return _load_from_file_system(
hashed_grammar,
file_io.path,
p_time,
cache_path=cache_path
)
def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None): def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
@@ -123,9 +128,10 @@ def _load_from_file_system(hashed_grammar, path, p_time, cache_path=None):
return module_cache_item.node return module_cache_item.node
def save_module(hashed_grammar, path, module, lines, pickling=True, cache_path=None): def save_module(hashed_grammar, file_io, module, lines, pickling=True, cache_path=None):
path = file_io.path
try: try:
p_time = None if path is None else os.path.getmtime(path) p_time = None if path is None else file_io.get_last_modified()
except OSError: except OSError:
p_time = None p_time = None
pickling = False pickling = False

26
parso/file_io.py Normal file
View File

@@ -0,0 +1,26 @@
import os
class FileIO:
def __init__(self, path):
self.path = path
def read(self):
with open(self.path) as f:
return f.read()
def get_last_modified(self):
"""
Returns float - timestamp
Might raise FileNotFoundError
"""
return os.path.getmtime(self.path)
class KnownContentFileIO(FileIO):
def __init__(self, path, content):
super(KnownContentFileIO, self).__init__(path)
self._content = content
def read(self):
return self._content

View File

@@ -12,6 +12,7 @@ from parso.parser import BaseParser
from parso.python.parser import Parser as PythonParser from parso.python.parser import Parser as PythonParser
from parso.python.errors import ErrorFinderConfig from parso.python.errors import ErrorFinderConfig
from parso.python import pep8 from parso.python import pep8
from parso.file_io import FileIO, KnownContentFileIO
_loaded_grammars = {} _loaded_grammars = {}
@@ -77,14 +78,14 @@ class Grammar(object):
def _parse(self, code=None, error_recovery=True, path=None, def _parse(self, code=None, error_recovery=True, path=None,
start_symbol=None, cache=False, diff_cache=False, start_symbol=None, cache=False, diff_cache=False,
cache_path=None, start_pos=(1, 0)): cache_path=None, file_io=None, start_pos=(1, 0)):
""" """
Wanted python3.5 * operator and keyword only arguments. Therefore just Wanted python3.5 * operator and keyword only arguments. Therefore just
wrap it all. wrap it all.
start_pos here is just a parameter internally used. Might be public start_pos here is just a parameter internally used. Might be public
sometime in the future. sometime in the future.
""" """
if code is None and path is None: if code is None and path is None and file_io is None:
raise TypeError("Please provide either code or a path.") raise TypeError("Please provide either code or a path.")
if start_symbol is None: if start_symbol is None:
@@ -93,15 +94,18 @@ class Grammar(object):
if error_recovery and start_symbol != 'file_input': if error_recovery and start_symbol != 'file_input':
raise NotImplementedError("This is currently not implemented.") raise NotImplementedError("This is currently not implemented.")
if cache and path is not None: if file_io is None:
module_node = load_module(self._hashed, path, cache_path=cache_path) if code is None:
file_io = FileIO(path)
else:
file_io = KnownContentFileIO(path, code)
if cache and file_io.path is not None:
module_node = load_module(self._hashed, file_io, cache_path=cache_path)
if module_node is not None: if module_node is not None:
return module_node return module_node
if code is None: code = file_io.read()
with open(path, 'rb') as f:
code = f.read()
code = python_bytes_to_unicode(code) code = python_bytes_to_unicode(code)
lines = split_lines(code, keepends=True) lines = split_lines(code, keepends=True)
@@ -110,7 +114,7 @@ class Grammar(object):
raise TypeError("You have to define a diff parser to be able " raise TypeError("You have to define a diff parser to be able "
"to use this option.") "to use this option.")
try: try:
module_cache_item = parser_cache[self._hashed][path] module_cache_item = parser_cache[self._hashed][file_io.path]
except KeyError: except KeyError:
pass pass
else: else:
@@ -125,7 +129,7 @@ class Grammar(object):
old_lines=old_lines, old_lines=old_lines,
new_lines=lines new_lines=lines
) )
save_module(self._hashed, path, new_node, lines, save_module(self._hashed, file_io, new_node, lines,
# Never pickle in pypy, it's slow as hell. # Never pickle in pypy, it's slow as hell.
pickling=cache and not is_pypy, pickling=cache and not is_pypy,
cache_path=cache_path) cache_path=cache_path)
@@ -141,7 +145,7 @@ class Grammar(object):
root_node = p.parse(tokens=tokens) root_node = p.parse(tokens=tokens)
if cache or diff_cache: if cache or diff_cache:
save_module(self._hashed, path, root_node, lines, save_module(self._hashed, file_io, root_node, lines,
# Never pickle in pypy, it's slow as hell. # Never pickle in pypy, it's slow as hell.
pickling=cache and not is_pypy, pickling=cache and not is_pypy,
cache_path=cache_path) cache_path=cache_path)

View File

@@ -10,6 +10,7 @@ from parso.cache import _NodeCacheItem, save_module, load_module, \
_get_hashed_path, parser_cache, _load_from_file_system, _save_to_file_system _get_hashed_path, parser_cache, _load_from_file_system, _save_to_file_system
from parso import load_grammar from parso import load_grammar
from parso import cache from parso import cache
from parso import file_io
@pytest.fixture() @pytest.fixture()
@@ -76,12 +77,13 @@ def test_modulepickling_simulate_deleted_cache(tmpdir):
path = tmpdir.dirname + '/some_path' path = tmpdir.dirname + '/some_path'
with open(path, 'w'): with open(path, 'w'):
pass pass
io = file_io.FileIO(path)
save_module(grammar._hashed, path, module, []) save_module(grammar._hashed, io, module, lines=[])
assert load_module(grammar._hashed, path) == module assert load_module(grammar._hashed, io) == module
unlink(_get_hashed_path(grammar._hashed, path)) unlink(_get_hashed_path(grammar._hashed, path))
parser_cache.clear() parser_cache.clear()
cached2 = load_module(grammar._hashed, path) cached2 = load_module(grammar._hashed, io)
assert cached2 is None assert cached2 is None