Skip to content

Commit c1b8eb8

Browse files
miss-islingtonbenjaminp
authored andcommitted
bpo-24960: use pkgutil.get_data in lib2to3 to read pickled grammar files (GH-4977) (#4979)
This is more complicated than it should be because we need to preserve the useful mtime-based regeneration feature that lib2to3.pgen2.driver.load_grammar has. We only look for the pickled grammar file with pkgutil.get_data and only if the source file does not exist. (cherry picked from commit 8a58771)
1 parent 2e1ef00 commit c1b8eb8

File tree

5 files changed

+45
-2
lines changed

5 files changed

+45
-2
lines changed

Lib/lib2to3/pgen2/driver.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io
2121
import os
2222
import logging
23+
import pkgutil
2324
import sys
2425

2526
# Pgen imports
@@ -143,6 +144,26 @@ def _newer(a, b):
143144
return os.path.getmtime(a) >= os.path.getmtime(b)
144145

145146

147+
def load_packaged_grammar(package, grammar_source):
148+
"""Normally, loads a pickled grammar by doing
149+
pkgutil.get_data(package, pickled_grammar)
150+
where *pickled_grammar* is computed from *grammar_source* by adding the
151+
Python version and using a ``.pickle`` extension.
152+
153+
However, if *grammar_source* is an extant file, load_grammar(grammar_source)
154+
is called instead. This facilities using a packaged grammar file when needed
155+
but preserves load_grammar's automatic regeneration behavior when possible.
156+
157+
"""
158+
if os.path.isfile(grammar_source):
159+
return load_grammar(grammar_source)
160+
pickled_name = _generate_pickle_name(os.path.basename(grammar_source))
161+
data = pkgutil.get_data(package, pickled_name)
162+
g = grammar.Grammar()
163+
g.loads(data)
164+
return g
165+
166+
146167
def main(*args):
147168
"""Main program, when run as a script: produce grammar pickle files.
148169

Lib/lib2to3/pgen2/grammar.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ def load(self, filename):
108108
d = pickle.load(f)
109109
self.__dict__.update(d)
110110

111+
def loads(self, pkl):
112+
"""Load the grammar tables from a pickle bytes object."""
113+
self.__dict__.update(pickle.loads(pkl))
114+
111115
def copy(self):
112116
"""
113117
Copy the grammar.

Lib/lib2to3/pygram.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ def __init__(self, grammar):
2929
setattr(self, name, symbol)
3030

3131

32-
python_grammar = driver.load_grammar(_GRAMMAR_FILE)
32+
python_grammar = driver.load_packaged_grammar("lib2to3", _GRAMMAR_FILE)
3333

3434
python_symbols = Symbols(python_grammar)
3535

3636
python_grammar_no_print_statement = python_grammar.copy()
3737
del python_grammar_no_print_statement.keywords["print"]
3838

39-
pattern_grammar = driver.load_grammar(_PATTERN_GRAMMAR_FILE)
39+
pattern_grammar = driver.load_packaged_grammar("lib2to3", _PATTERN_GRAMMAR_FILE)
4040
pattern_symbols = Symbols(pattern_grammar)

Lib/lib2to3/tests/test_parser.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
from test.support import verbose
1313

1414
# Python imports
15+
import importlib
16+
import operator
1517
import os
18+
import pickle
1619
import shutil
1720
import subprocess
1821
import sys
@@ -99,6 +102,18 @@ def test_load_grammar_from_subprocess(self):
99102
finally:
100103
shutil.rmtree(tmpdir)
101104

105+
def test_load_packaged_grammar(self):
106+
modname = __name__ + '.load_test'
107+
class MyLoader:
108+
def get_data(self, where):
109+
return pickle.dumps({'elephant': 19})
110+
class MyModule:
111+
__file__ = 'parsertestmodule'
112+
__spec__ = importlib.util.spec_from_loader(modname, MyLoader())
113+
sys.modules[modname] = MyModule()
114+
self.addCleanup(operator.delitem, sys.modules, modname)
115+
g = pgen2_driver.load_packaged_grammar(modname, 'Grammar.txt')
116+
self.assertEqual(g.elephant, 19)
102117

103118

104119
class GrammarTest(support.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2to3 and lib2to3 can now read pickled grammar files using pkgutil.get_data()
2+
rather than probing the filesystem. This lets 2to3 and lib2to3 work when run
3+
from a zipfile.

0 commit comments

Comments
 (0)