Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/alembic/config.py : 24%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from argparse import ArgumentParser
2import inspect
3import os
4import sys
6from . import command
7from . import util
8from .util import compat
9from .util.compat import SafeConfigParser
12class Config(object):
14 r"""Represent an Alembic configuration.
16 Within an ``env.py`` script, this is available
17 via the :attr:`.EnvironmentContext.config` attribute,
18 which in turn is available at ``alembic.context``::
20 from alembic import context
22 some_param = context.config.get_main_option("my option")
24 When invoking Alembic programatically, a new
25 :class:`.Config` can be created by passing
26 the name of an .ini file to the constructor::
28 from alembic.config import Config
29 alembic_cfg = Config("/path/to/yourapp/alembic.ini")
31 With a :class:`.Config` object, you can then
32 run Alembic commands programmatically using the directives
33 in :mod:`alembic.command`.
35 The :class:`.Config` object can also be constructed without
36 a filename. Values can be set programmatically, and
37 new sections will be created as needed::
39 from alembic.config import Config
40 alembic_cfg = Config()
41 alembic_cfg.set_main_option("script_location", "myapp:migrations")
42 alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar")
43 alembic_cfg.set_section_option("mysection", "foo", "bar")
45 .. warning::
47 When using programmatic configuration, make sure the
48 ``env.py`` file in use is compatible with the target configuration;
49 including that the call to Python ``logging.fileConfig()`` is
50 omitted if the programmatic configuration doesn't actually include
51 logging directives.
53 For passing non-string values to environments, such as connections and
54 engines, use the :attr:`.Config.attributes` dictionary::
56 with engine.begin() as connection:
57 alembic_cfg.attributes['connection'] = connection
58 command.upgrade(alembic_cfg, "head")
60 :param file\_: name of the .ini file to open.
61 :param ini_section: name of the main Alembic section within the
62 .ini file
63 :param output_buffer: optional file-like input buffer which
64 will be passed to the :class:`.MigrationContext` - used to redirect
65 the output of "offline generation" when using Alembic programmatically.
66 :param stdout: buffer where the "print" output of commands will be sent.
67 Defaults to ``sys.stdout``.
69 .. versionadded:: 0.4
71 :param config_args: A dictionary of keys and values that will be used
72 for substitution in the alembic config file. The dictionary as given
73 is **copied** to a new one, stored locally as the attribute
74 ``.config_args``. When the :attr:`.Config.file_config` attribute is
75 first invoked, the replacement variable ``here`` will be added to this
76 dictionary before the dictionary is passed to ``SafeConfigParser()``
77 to parse the .ini file.
79 .. versionadded:: 0.7.0
81 :param attributes: optional dictionary of arbitrary Python keys/values,
82 which will be populated into the :attr:`.Config.attributes` dictionary.
84 .. versionadded:: 0.7.5
86 .. seealso::
88 :ref:`connection_sharing`
90 """
92 def __init__(
93 self,
94 file_=None,
95 ini_section="alembic",
96 output_buffer=None,
97 stdout=sys.stdout,
98 cmd_opts=None,
99 config_args=util.immutabledict(),
100 attributes=None,
101 ):
102 """Construct a new :class:`.Config`
104 """
105 self.config_file_name = file_
106 self.config_ini_section = ini_section
107 self.output_buffer = output_buffer
108 self.stdout = stdout
109 self.cmd_opts = cmd_opts
110 self.config_args = dict(config_args)
111 if attributes:
112 self.attributes.update(attributes)
114 cmd_opts = None
115 """The command-line options passed to the ``alembic`` script.
117 Within an ``env.py`` script this can be accessed via the
118 :attr:`.EnvironmentContext.config` attribute.
120 .. versionadded:: 0.6.0
122 .. seealso::
124 :meth:`.EnvironmentContext.get_x_argument`
126 """
128 config_file_name = None
129 """Filesystem path to the .ini file in use."""
131 config_ini_section = None
132 """Name of the config file section to read basic configuration
133 from. Defaults to ``alembic``, that is the ``[alembic]`` section
134 of the .ini file. This value is modified using the ``-n/--name``
135 option to the Alembic runnier.
137 """
139 @util.memoized_property
140 def attributes(self):
141 """A Python dictionary for storage of additional state.
144 This is a utility dictionary which can include not just strings but
145 engines, connections, schema objects, or anything else.
146 Use this to pass objects into an env.py script, such as passing
147 a :class:`sqlalchemy.engine.base.Connection` when calling
148 commands from :mod:`alembic.command` programmatically.
150 .. versionadded:: 0.7.5
152 .. seealso::
154 :ref:`connection_sharing`
156 :paramref:`.Config.attributes`
158 """
159 return {}
161 def print_stdout(self, text, *arg):
162 """Render a message to standard out.
164 When :meth:`.Config.print_stdout` is called with additional args
165 those arguments will formatted against the provided text,
166 otherwise we simply output the provided text verbatim.
168 e.g.::
170 >>> config.print_stdout('Some text %s', 'arg')
171 Some Text arg
173 """
175 if arg:
176 output = compat.text_type(text) % arg
177 else:
178 output = compat.text_type(text)
180 util.write_outstream(self.stdout, output, "\n")
182 @util.memoized_property
183 def file_config(self):
184 """Return the underlying ``ConfigParser`` object.
186 Direct access to the .ini file is available here,
187 though the :meth:`.Config.get_section` and
188 :meth:`.Config.get_main_option`
189 methods provide a possibly simpler interface.
191 """
193 if self.config_file_name:
194 here = os.path.abspath(os.path.dirname(self.config_file_name))
195 else:
196 here = ""
197 self.config_args["here"] = here
198 file_config = SafeConfigParser(self.config_args)
199 if self.config_file_name:
200 file_config.read([self.config_file_name])
201 else:
202 file_config.add_section(self.config_ini_section)
203 return file_config
205 def get_template_directory(self):
206 """Return the directory where Alembic setup templates are found.
208 This method is used by the alembic ``init`` and ``list_templates``
209 commands.
211 """
212 import alembic
214 package_dir = os.path.abspath(os.path.dirname(alembic.__file__))
215 return os.path.join(package_dir, "templates")
217 def get_section(self, name, default=None):
218 """Return all the configuration options from a given .ini file section
219 as a dictionary.
221 """
222 if not self.file_config.has_section(name):
223 return default
225 return dict(self.file_config.items(name))
227 def set_main_option(self, name, value):
228 """Set an option programmatically within the 'main' section.
230 This overrides whatever was in the .ini file.
232 :param name: name of the value
234 :param value: the value. Note that this value is passed to
235 ``ConfigParser.set``, which supports variable interpolation using
236 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of
237 an interpolation symbol must therefore be escaped, e.g. ``%%``.
238 The given value may refer to another value already in the file
239 using the interpolation format.
241 """
242 self.set_section_option(self.config_ini_section, name, value)
244 def remove_main_option(self, name):
245 self.file_config.remove_option(self.config_ini_section, name)
247 def set_section_option(self, section, name, value):
248 """Set an option programmatically within the given section.
250 The section is created if it doesn't exist already.
251 The value here will override whatever was in the .ini
252 file.
254 :param section: name of the section
256 :param name: name of the value
258 :param value: the value. Note that this value is passed to
259 ``ConfigParser.set``, which supports variable interpolation using
260 pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of
261 an interpolation symbol must therefore be escaped, e.g. ``%%``.
262 The given value may refer to another value already in the file
263 using the interpolation format.
265 """
267 if not self.file_config.has_section(section):
268 self.file_config.add_section(section)
269 self.file_config.set(section, name, value)
271 def get_section_option(self, section, name, default=None):
272 """Return an option from the given section of the .ini file.
274 """
275 if not self.file_config.has_section(section):
276 raise util.CommandError(
277 "No config file %r found, or file has no "
278 "'[%s]' section" % (self.config_file_name, section)
279 )
280 if self.file_config.has_option(section, name):
281 return self.file_config.get(section, name)
282 else:
283 return default
285 def get_main_option(self, name, default=None):
286 """Return an option from the 'main' section of the .ini file.
288 This defaults to being a key from the ``[alembic]``
289 section, unless the ``-n/--name`` flag were used to
290 indicate a different section.
292 """
293 return self.get_section_option(self.config_ini_section, name, default)
296class CommandLine(object):
297 def __init__(self, prog=None):
298 self._generate_args(prog)
300 def _generate_args(self, prog):
301 def add_options(fn, parser, positional, kwargs):
302 kwargs_opts = {
303 "template": (
304 "-t",
305 "--template",
306 dict(
307 default="generic",
308 type=str,
309 help="Setup template for use with 'init'",
310 ),
311 ),
312 "message": (
313 "-m",
314 "--message",
315 dict(
316 type=str, help="Message string to use with 'revision'"
317 ),
318 ),
319 "sql": (
320 "--sql",
321 dict(
322 action="store_true",
323 help="Don't emit SQL to database - dump to "
324 "standard output/file instead. See docs on "
325 "offline mode.",
326 ),
327 ),
328 "tag": (
329 "--tag",
330 dict(
331 type=str,
332 help="Arbitrary 'tag' name - can be used by "
333 "custom env.py scripts.",
334 ),
335 ),
336 "head": (
337 "--head",
338 dict(
339 type=str,
340 help="Specify head revision or <branchname>@head "
341 "to base new revision on.",
342 ),
343 ),
344 "splice": (
345 "--splice",
346 dict(
347 action="store_true",
348 help="Allow a non-head revision as the "
349 "'head' to splice onto",
350 ),
351 ),
352 "depends_on": (
353 "--depends-on",
354 dict(
355 action="append",
356 help="Specify one or more revision identifiers "
357 "which this revision should depend on.",
358 ),
359 ),
360 "rev_id": (
361 "--rev-id",
362 dict(
363 type=str,
364 help="Specify a hardcoded revision id instead of "
365 "generating one",
366 ),
367 ),
368 "version_path": (
369 "--version-path",
370 dict(
371 type=str,
372 help="Specify specific path from config for "
373 "version file",
374 ),
375 ),
376 "branch_label": (
377 "--branch-label",
378 dict(
379 type=str,
380 help="Specify a branch label to apply to the "
381 "new revision",
382 ),
383 ),
384 "verbose": (
385 "-v",
386 "--verbose",
387 dict(action="store_true", help="Use more verbose output"),
388 ),
389 "resolve_dependencies": (
390 "--resolve-dependencies",
391 dict(
392 action="store_true",
393 help="Treat dependency versions as down revisions",
394 ),
395 ),
396 "autogenerate": (
397 "--autogenerate",
398 dict(
399 action="store_true",
400 help="Populate revision script with candidate "
401 "migration operations, based on comparison "
402 "of database to model.",
403 ),
404 ),
405 "head_only": (
406 "--head-only",
407 dict(
408 action="store_true",
409 help="Deprecated. Use --verbose for "
410 "additional output",
411 ),
412 ),
413 "rev_range": (
414 "-r",
415 "--rev-range",
416 dict(
417 action="store",
418 help="Specify a revision range; "
419 "format is [start]:[end]",
420 ),
421 ),
422 "indicate_current": (
423 "-i",
424 "--indicate-current",
425 dict(
426 action="store_true",
427 help="Indicate the current revision",
428 ),
429 ),
430 "purge": (
431 "--purge",
432 dict(
433 action="store_true",
434 help="Unconditionally erase the version table "
435 "before stamping",
436 ),
437 ),
438 "package": (
439 "--package",
440 dict(
441 action="store_true",
442 help="Write empty __init__.py files to the "
443 "environment and version locations",
444 ),
445 ),
446 }
447 positional_help = {
448 "directory": "location of scripts directory",
449 "revision": "revision identifier",
450 "revisions": "one or more revisions, or 'heads' for all heads",
451 }
452 for arg in kwargs:
453 if arg in kwargs_opts:
454 args = kwargs_opts[arg]
455 args, kw = args[0:-1], args[-1]
456 parser.add_argument(*args, **kw)
458 for arg in positional:
459 if (
460 arg == "revisions"
461 or fn in positional_translations
462 and positional_translations[fn][arg] == "revisions"
463 ):
464 subparser.add_argument(
465 "revisions",
466 nargs="+",
467 help=positional_help.get("revisions"),
468 )
469 else:
470 subparser.add_argument(arg, help=positional_help.get(arg))
472 parser = ArgumentParser(prog=prog)
474 parser.add_argument(
475 "-c",
476 "--config",
477 type=str,
478 default=os.environ.get("ALEMBIC_CONFIG", "alembic.ini"),
479 help="Alternate config file; defaults to value of "
480 'ALEMBIC_CONFIG environment variable, or "alembic.ini"',
481 )
482 parser.add_argument(
483 "-n",
484 "--name",
485 type=str,
486 default="alembic",
487 help="Name of section in .ini file to " "use for Alembic config",
488 )
489 parser.add_argument(
490 "-x",
491 action="append",
492 help="Additional arguments consumed by "
493 "custom env.py scripts, e.g. -x "
494 "setting1=somesetting -x setting2=somesetting",
495 )
496 parser.add_argument(
497 "--raiseerr",
498 action="store_true",
499 help="Raise a full stack trace on error",
500 )
501 subparsers = parser.add_subparsers()
503 positional_translations = {command.stamp: {"revision": "revisions"}}
505 for fn in [getattr(command, n) for n in dir(command)]:
506 if (
507 inspect.isfunction(fn)
508 and fn.__name__[0] != "_"
509 and fn.__module__ == "alembic.command"
510 ):
512 spec = compat.inspect_getargspec(fn)
513 if spec[3]:
514 positional = spec[0][1 : -len(spec[3])]
515 kwarg = spec[0][-len(spec[3]) :]
516 else:
517 positional = spec[0][1:]
518 kwarg = []
520 if fn in positional_translations:
521 positional = [
522 positional_translations[fn].get(name, name)
523 for name in positional
524 ]
526 # parse first line(s) of helptext without a line break
527 help_ = fn.__doc__
528 if help_:
529 help_text = []
530 for line in help_.split("\n"):
531 if not line.strip():
532 break
533 else:
534 help_text.append(line.strip())
535 else:
536 help_text = ""
537 subparser = subparsers.add_parser(
538 fn.__name__, help=" ".join(help_text)
539 )
540 add_options(fn, subparser, positional, kwarg)
541 subparser.set_defaults(cmd=(fn, positional, kwarg))
542 self.parser = parser
544 def run_cmd(self, config, options):
545 fn, positional, kwarg = options.cmd
547 try:
548 fn(
549 config,
550 *[getattr(options, k, None) for k in positional],
551 **dict((k, getattr(options, k, None)) for k in kwarg)
552 )
553 except util.CommandError as e:
554 if options.raiseerr:
555 raise
556 else:
557 util.err(str(e))
559 def main(self, argv=None):
560 options = self.parser.parse_args(argv)
561 if not hasattr(options, "cmd"):
562 # see http://bugs.python.org/issue9253, argparse
563 # behavior changed incompatibly in py3.3
564 self.parser.error("too few arguments")
565 else:
566 cfg = Config(
567 file_=options.config,
568 ini_section=options.name,
569 cmd_opts=options,
570 )
571 self.run_cmd(cfg, options)
574def main(argv=None, prog=None, **kwargs):
575 """The console runner function for Alembic."""
577 CommandLine(prog=prog).main(argv=argv)
580if __name__ == "__main__":
581 main()