commit | author | age
|
337960
|
1 |
from code import interact |
990fb0
|
2 |
from contextlib import contextmanager |
5ad647
|
3 |
import argparse |
f3a567
|
4 |
import os |
337960
|
5 |
import sys |
d58614
|
6 |
import textwrap |
cb9202
|
7 |
import pkg_resources |
337960
|
8 |
|
f3a567
|
9 |
from pyramid.compat import exec_ |
337960
|
10 |
from pyramid.util import DottedNameResolver |
990fb0
|
11 |
from pyramid.util import make_contextmanager |
337960
|
12 |
from pyramid.paster import bootstrap |
160865
|
13 |
|
208e7b
|
14 |
from pyramid.settings import aslist |
MM |
15 |
|
e1d1af
|
16 |
from pyramid.scripts.common import get_config_loader |
49fb77
|
17 |
from pyramid.scripts.common import parse_vars |
JA |
18 |
|
0c29cf
|
19 |
|
d29151
|
20 |
def main(argv=sys.argv, quiet=False): |
CM |
21 |
command = PShellCommand(argv, quiet) |
d58614
|
22 |
return command.run() |
cb9202
|
23 |
|
337960
|
24 |
|
b7350e
|
25 |
def python_shell_runner(env, help, interact=interact): |
MM |
26 |
cprt = 'Type "help" for more information.' |
|
27 |
banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) |
|
28 |
banner += '\n\n' + help + '\n' |
|
29 |
interact(banner, local=env) |
|
30 |
|
|
31 |
|
337960
|
32 |
class PShellCommand(object): |
d58614
|
33 |
description = """\ |
CM |
34 |
Open an interactive shell with a Pyramid app loaded. This command |
|
35 |
accepts one positional argument named "config_uri" which specifies the |
|
36 |
PasteDeploy config file to use for the interactive shell. The format is |
|
37 |
"inifile#name". If the name is left off, the Pyramid default application |
0a3a20
|
38 |
will be assumed. Example: "pshell myapp.ini#main". |
337960
|
39 |
|
d58614
|
40 |
If you do not point the loader directly at the section of the ini file |
CM |
41 |
containing your Pyramid application, the command will attempt to |
|
42 |
find the app for you. If you are loading a pipeline that contains more |
|
43 |
than one Pyramid application within it, the loader will use the |
|
44 |
last one. |
337960
|
45 |
""" |
e1d1af
|
46 |
bootstrap = staticmethod(bootstrap) # for testing |
MM |
47 |
get_config_loader = staticmethod(get_config_loader) # for testing |
cb9202
|
48 |
pkg_resources = pkg_resources # for testing |
337960
|
49 |
|
5ad647
|
50 |
parser = argparse.ArgumentParser( |
df57ec
|
51 |
description=textwrap.dedent(description), |
c9b2fa
|
52 |
formatter_class=argparse.RawDescriptionHelpFormatter, |
0c29cf
|
53 |
) |
MM |
54 |
parser.add_argument( |
|
55 |
'-p', |
|
56 |
'--python-shell', |
|
57 |
action='store', |
|
58 |
dest='python_shell', |
|
59 |
default='', |
|
60 |
help=( |
|
61 |
'Select the shell to use. A list of possible ' |
|
62 |
'shells is available using the --list-shells ' |
|
63 |
'option.' |
|
64 |
), |
|
65 |
) |
|
66 |
parser.add_argument( |
|
67 |
'-l', |
|
68 |
'--list-shells', |
|
69 |
dest='list', |
|
70 |
action='store_true', |
|
71 |
help='List all available shells.', |
|
72 |
) |
|
73 |
parser.add_argument( |
|
74 |
'--setup', |
|
75 |
dest='setup', |
|
76 |
help=( |
|
77 |
"A callable that will be passed the environment " |
|
78 |
"before it is made available to the shell. This " |
|
79 |
"option will override the 'setup' key in the " |
|
80 |
"[pshell] ini section." |
|
81 |
), |
|
82 |
) |
|
83 |
parser.add_argument( |
|
84 |
'config_uri', |
|
85 |
nargs='?', |
|
86 |
default=None, |
|
87 |
help='The URI to the configuration file.', |
|
88 |
) |
3c4310
|
89 |
parser.add_argument( |
SP |
90 |
'config_vars', |
|
91 |
nargs='*', |
|
92 |
default=(), |
|
93 |
help="Variables required by the config file. For example, " |
0c29cf
|
94 |
"`http_port=%%(http_port)s` would expect `http_port=8080` to be " |
MM |
95 |
"passed here.", |
|
96 |
) |
337960
|
97 |
|
0c29cf
|
98 |
default_runner = python_shell_runner # testing |
337960
|
99 |
|
CM |
100 |
loaded_objects = {} |
|
101 |
object_help = {} |
208e7b
|
102 |
preferred_shells = [] |
337960
|
103 |
setup = None |
823ac4
|
104 |
pystartup = os.environ.get('PYTHONSTARTUP') |
990fb0
|
105 |
resolver = DottedNameResolver(None) |
337960
|
106 |
|
d29151
|
107 |
def __init__(self, argv, quiet=False): |
CM |
108 |
self.quiet = quiet |
5ad647
|
109 |
self.args = self.parser.parse_args(argv[1:]) |
337960
|
110 |
|
e1d1af
|
111 |
def pshell_file_config(self, loader, defaults): |
MM |
112 |
settings = loader.get_settings('pshell', defaults) |
337960
|
113 |
self.loaded_objects = {} |
CM |
114 |
self.object_help = {} |
|
115 |
self.setup = None |
e1d1af
|
116 |
for k, v in settings.items(): |
337960
|
117 |
if k == 'setup': |
CM |
118 |
self.setup = v |
208e7b
|
119 |
elif k == 'default_shell': |
MM |
120 |
self.preferred_shells = [x.lower() for x in aslist(v)] |
337960
|
121 |
else: |
990fb0
|
122 |
self.loaded_objects[k] = self.resolver.maybe_resolve(v) |
337960
|
123 |
self.object_help[k] = v |
CM |
124 |
|
0c29cf
|
125 |
def out(self, msg): # pragma: no cover |
d29151
|
126 |
if not self.quiet: |
CM |
127 |
print(msg) |
|
128 |
|
337960
|
129 |
def run(self, shell=None): |
1fc1b8
|
130 |
if self.args.list: |
208e7b
|
131 |
return self.show_shells() |
1fc1b8
|
132 |
if not self.args.config_uri: |
d29151
|
133 |
self.out('Requires a config file argument') |
d58614
|
134 |
return 2 |
990fb0
|
135 |
|
1fc1b8
|
136 |
config_uri = self.args.config_uri |
e1d1af
|
137 |
config_vars = parse_vars(self.args.config_vars) |
MM |
138 |
loader = self.get_config_loader(config_uri) |
|
139 |
loader.setup_logging(config_vars) |
|
140 |
self.pshell_file_config(loader, config_vars) |
337960
|
141 |
|
990fb0
|
142 |
self.env = self.bootstrap(config_uri, options=config_vars) |
337960
|
143 |
|
CM |
144 |
# remove the closer from the env |
990fb0
|
145 |
self.closer = self.env.pop('closer') |
337960
|
146 |
|
990fb0
|
147 |
try: |
MM |
148 |
if shell is None: |
|
149 |
try: |
|
150 |
shell = self.make_shell() |
|
151 |
except ValueError as e: |
|
152 |
self.out(str(e)) |
|
153 |
return 1 |
|
154 |
|
|
155 |
with self.setup_env(): |
|
156 |
shell(self.env, self.help) |
|
157 |
|
|
158 |
finally: |
|
159 |
self.closer() |
|
160 |
|
|
161 |
@contextmanager |
|
162 |
def setup_env(self): |
337960
|
163 |
# setup help text for default environment |
990fb0
|
164 |
env = self.env |
337960
|
165 |
env_help = dict(env) |
CM |
166 |
env_help['app'] = 'The WSGI application.' |
|
167 |
env_help['root'] = 'Root of the default resource tree.' |
|
168 |
env_help['registry'] = 'Active Pyramid registry.' |
|
169 |
env_help['request'] = 'Active request object.' |
0c29cf
|
170 |
env_help[ |
MM |
171 |
'root_factory' |
|
172 |
] = 'Default root factory used to create `root`.' |
337960
|
173 |
|
CM |
174 |
# load the pshell section of the ini file |
|
175 |
env.update(self.loaded_objects) |
|
176 |
|
|
177 |
# eliminate duplicates from env, allowing custom vars to override |
|
178 |
for k in self.loaded_objects: |
|
179 |
if k in env_help: |
|
180 |
del env_help[k] |
|
181 |
|
990fb0
|
182 |
# override use_script with command-line options |
MM |
183 |
if self.args.setup: |
|
184 |
self.setup = self.args.setup |
337960
|
185 |
|
990fb0
|
186 |
if self.setup: |
MM |
187 |
# call the setup callable |
|
188 |
self.setup = self.resolver.maybe_resolve(self.setup) |
337960
|
189 |
|
990fb0
|
190 |
# store the env before muddling it with the script |
MM |
191 |
orig_env = env.copy() |
|
192 |
setup_manager = make_contextmanager(self.setup) |
|
193 |
with setup_manager(env): |
|
194 |
# remove any objects from default help that were overidden |
|
195 |
for k, v in env.items(): |
|
196 |
if k not in orig_env or v != orig_env[k]: |
|
197 |
if getattr(v, '__doc__', False): |
|
198 |
env_help[k] = v.__doc__.replace("\n", " ") |
|
199 |
else: |
|
200 |
env_help[k] = v |
|
201 |
del orig_env |
337960
|
202 |
|
990fb0
|
203 |
# generate help text |
MM |
204 |
help = '' |
|
205 |
if env_help: |
|
206 |
help += 'Environment:' |
|
207 |
for var in sorted(env_help.keys()): |
|
208 |
help += '\n %-12s %s' % (var, env_help[var]) |
f3a567
|
209 |
|
990fb0
|
210 |
if self.object_help: |
MM |
211 |
help += '\n\nCustom Variables:' |
|
212 |
for var in sorted(self.object_help.keys()): |
|
213 |
help += '\n %-12s %s' % (var, self.object_help[var]) |
|
214 |
|
|
215 |
if self.pystartup and os.path.isfile(self.pystartup): |
|
216 |
with open(self.pystartup, 'rb') as fp: |
|
217 |
exec_(fp.read().decode('utf-8'), env) |
|
218 |
if '__builtins__' in env: |
|
219 |
del env['__builtins__'] |
|
220 |
|
|
221 |
self.help = help.strip() |
|
222 |
yield |
3808f7
|
223 |
|
208e7b
|
224 |
def show_shells(self): |
MM |
225 |
shells = self.find_all_shells() |
b7350e
|
226 |
sorted_names = sorted(shells.keys(), key=lambda x: x.lower()) |
cb9202
|
227 |
|
208e7b
|
228 |
self.out('Available shells:') |
b7350e
|
229 |
for name in sorted_names: |
MM |
230 |
self.out(' %s' % (name,)) |
208e7b
|
231 |
return 0 |
MM |
232 |
|
|
233 |
def find_all_shells(self): |
b7350e
|
234 |
pkg_resources = self.pkg_resources |
MM |
235 |
|
208e7b
|
236 |
shells = {} |
b7350e
|
237 |
for ep in pkg_resources.iter_entry_points('pyramid.pshell_runner'): |
cb9202
|
238 |
name = ep.name |
208e7b
|
239 |
shell_factory = ep.load() |
MM |
240 |
shells[name] = shell_factory |
|
241 |
return shells |
|
242 |
|
|
243 |
def make_shell(self): |
|
244 |
shells = self.find_all_shells() |
cb9202
|
245 |
|
3808f7
|
246 |
shell = None |
5ad647
|
247 |
user_shell = self.args.python_shell.lower() |
cb9202
|
248 |
|
3808f7
|
249 |
if not user_shell: |
208e7b
|
250 |
preferred_shells = self.preferred_shells |
MM |
251 |
if not preferred_shells: |
|
252 |
# by default prioritize all shells above python |
|
253 |
preferred_shells = [k for k in shells.keys() if k != 'python'] |
|
254 |
max_weight = len(preferred_shells) |
0c29cf
|
255 |
|
208e7b
|
256 |
def order(x): |
MM |
257 |
# invert weight to reverse sort the list |
|
258 |
# (closer to the front is higher priority) |
|
259 |
try: |
|
260 |
return preferred_shells.index(x[0].lower()) - max_weight |
|
261 |
except ValueError: |
|
262 |
return 1 |
0c29cf
|
263 |
|
208e7b
|
264 |
sorted_shells = sorted(shells.items(), key=order) |
3808f7
|
265 |
|
b7350e
|
266 |
if len(sorted_shells) > 0: |
MM |
267 |
shell = sorted_shells[0][1] |
|
268 |
|
cb9202
|
269 |
else: |
b7350e
|
270 |
runner = shells.get(user_shell) |
3808f7
|
271 |
|
b7350e
|
272 |
if runner is not None: |
MM |
273 |
shell = runner |
208e7b
|
274 |
|
MM |
275 |
if shell is None: |
b932a4
|
276 |
raise ValueError( |
JA |
277 |
'could not find a shell named "%s"' % user_shell |
|
278 |
) |
3808f7
|
279 |
|
MM |
280 |
if shell is None: |
b7350e
|
281 |
# should never happen, but just incase entry points are borked |
MM |
282 |
shell = self.default_runner |
3808f7
|
283 |
|
b74535
|
284 |
return shell |
MM |
285 |
|
337960
|
286 |
|
0c29cf
|
287 |
if __name__ == '__main__': # pragma: no cover |
40d54e
|
288 |
sys.exit(main() or 0) |