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