plain 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {plain-0.3.1 → plain-0.4.0}/PKG-INFO +1 -1
  2. {plain-0.3.1 → plain-0.4.0}/plain/cli/README.md +0 -4
  3. {plain-0.3.1 → plain-0.4.0}/plain/cli/cli.py +0 -20
  4. {plain-0.3.1 → plain-0.4.0}/pyproject.toml +1 -1
  5. plain-0.3.1/plain/internal/legacy/__main__.py +0 -12
  6. plain-0.3.1/plain/internal/legacy/management/__init__.py +0 -414
  7. plain-0.3.1/plain/internal/legacy/management/base.py +0 -692
  8. plain-0.3.1/plain/internal/legacy/management/color.py +0 -113
  9. plain-0.3.1/plain/internal/legacy/management/sql.py +0 -67
  10. plain-0.3.1/plain/internal/legacy/management/utils.py +0 -175
  11. plain-0.3.1/plain/utils/__init__.py +0 -0
  12. {plain-0.3.1 → plain-0.4.0}/LICENSE +0 -0
  13. {plain-0.3.1 → plain-0.4.0}/README.md +0 -0
  14. {plain-0.3.1 → plain-0.4.0}/plain/README.md +0 -0
  15. {plain-0.3.1 → plain-0.4.0}/plain/__main__.py +0 -0
  16. {plain-0.3.1 → plain-0.4.0}/plain/assets/README.md +0 -0
  17. {plain-0.3.1 → plain-0.4.0}/plain/assets/__init__.py +0 -0
  18. {plain-0.3.1 → plain-0.4.0}/plain/assets/compile.py +0 -0
  19. {plain-0.3.1 → plain-0.4.0}/plain/assets/finders.py +0 -0
  20. {plain-0.3.1 → plain-0.4.0}/plain/assets/fingerprints.py +0 -0
  21. {plain-0.3.1 → plain-0.4.0}/plain/assets/urls.py +0 -0
  22. {plain-0.3.1 → plain-0.4.0}/plain/assets/views.py +0 -0
  23. {plain-0.3.1 → plain-0.4.0}/plain/cli/__init__.py +0 -0
  24. {plain-0.3.1 → plain-0.4.0}/plain/cli/formatting.py +0 -0
  25. {plain-0.3.1 → plain-0.4.0}/plain/cli/packages.py +0 -0
  26. {plain-0.3.1 → plain-0.4.0}/plain/cli/print.py +0 -0
  27. {plain-0.3.1 → plain-0.4.0}/plain/cli/startup.py +0 -0
  28. {plain-0.3.1 → plain-0.4.0}/plain/csrf/README.md +0 -0
  29. {plain-0.3.1 → plain-0.4.0}/plain/csrf/middleware.py +0 -0
  30. {plain-0.3.1 → plain-0.4.0}/plain/csrf/views.py +0 -0
  31. {plain-0.3.1 → plain-0.4.0}/plain/debug.py +0 -0
  32. {plain-0.3.1 → plain-0.4.0}/plain/exceptions.py +0 -0
  33. {plain-0.3.1 → plain-0.4.0}/plain/forms/README.md +0 -0
  34. {plain-0.3.1 → plain-0.4.0}/plain/forms/__init__.py +0 -0
  35. {plain-0.3.1 → plain-0.4.0}/plain/forms/boundfield.py +0 -0
  36. {plain-0.3.1 → plain-0.4.0}/plain/forms/exceptions.py +0 -0
  37. {plain-0.3.1 → plain-0.4.0}/plain/forms/fields.py +0 -0
  38. {plain-0.3.1 → plain-0.4.0}/plain/forms/forms.py +0 -0
  39. {plain-0.3.1 → plain-0.4.0}/plain/http/README.md +0 -0
  40. {plain-0.3.1 → plain-0.4.0}/plain/http/__init__.py +0 -0
  41. {plain-0.3.1 → plain-0.4.0}/plain/http/cookie.py +0 -0
  42. {plain-0.3.1 → plain-0.4.0}/plain/http/multipartparser.py +0 -0
  43. {plain-0.3.1 → plain-0.4.0}/plain/http/request.py +0 -0
  44. {plain-0.3.1 → plain-0.4.0}/plain/http/response.py +0 -0
  45. {plain-0.3.1 → plain-0.4.0}/plain/internal/__init__.py +0 -0
  46. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/README.md +0 -0
  47. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/__init__.py +0 -0
  48. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/base.py +0 -0
  49. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/locks.py +0 -0
  50. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/move.py +0 -0
  51. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/temp.py +0 -0
  52. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/uploadedfile.py +0 -0
  53. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/uploadhandler.py +0 -0
  54. {plain-0.3.1 → plain-0.4.0}/plain/internal/files/utils.py +0 -0
  55. {plain-0.3.1 → plain-0.4.0}/plain/internal/handlers/__init__.py +0 -0
  56. {plain-0.3.1 → plain-0.4.0}/plain/internal/handlers/base.py +0 -0
  57. {plain-0.3.1 → plain-0.4.0}/plain/internal/handlers/exception.py +0 -0
  58. {plain-0.3.1 → plain-0.4.0}/plain/internal/handlers/wsgi.py +0 -0
  59. {plain-0.3.1 → plain-0.4.0}/plain/json.py +0 -0
  60. {plain-0.3.1 → plain-0.4.0}/plain/logs/README.md +0 -0
  61. {plain-0.3.1 → plain-0.4.0}/plain/logs/__init__.py +0 -0
  62. {plain-0.3.1 → plain-0.4.0}/plain/logs/configure.py +0 -0
  63. {plain-0.3.1 → plain-0.4.0}/plain/logs/loggers.py +0 -0
  64. {plain-0.3.1 → plain-0.4.0}/plain/logs/utils.py +0 -0
  65. {plain-0.3.1 → plain-0.4.0}/plain/middleware/README.md +0 -0
  66. {plain-0.3.1/plain/internal/legacy → plain-0.4.0/plain/middleware}/__init__.py +0 -0
  67. {plain-0.3.1 → plain-0.4.0}/plain/middleware/clickjacking.py +0 -0
  68. {plain-0.3.1 → plain-0.4.0}/plain/middleware/common.py +0 -0
  69. {plain-0.3.1 → plain-0.4.0}/plain/middleware/gzip.py +0 -0
  70. {plain-0.3.1 → plain-0.4.0}/plain/middleware/security.py +0 -0
  71. {plain-0.3.1 → plain-0.4.0}/plain/packages/README.md +0 -0
  72. {plain-0.3.1 → plain-0.4.0}/plain/packages/__init__.py +0 -0
  73. {plain-0.3.1 → plain-0.4.0}/plain/packages/config.py +0 -0
  74. {plain-0.3.1 → plain-0.4.0}/plain/packages/registry.py +0 -0
  75. {plain-0.3.1 → plain-0.4.0}/plain/paginator.py +0 -0
  76. {plain-0.3.1 → plain-0.4.0}/plain/preflight/README.md +0 -0
  77. {plain-0.3.1 → plain-0.4.0}/plain/preflight/__init__.py +0 -0
  78. {plain-0.3.1/plain/middleware → plain-0.4.0/plain/preflight/compatibility}/__init__.py +0 -0
  79. {plain-0.3.1 → plain-0.4.0}/plain/preflight/compatibility/django_4_0.py +0 -0
  80. {plain-0.3.1 → plain-0.4.0}/plain/preflight/files.py +0 -0
  81. {plain-0.3.1 → plain-0.4.0}/plain/preflight/messages.py +0 -0
  82. {plain-0.3.1 → plain-0.4.0}/plain/preflight/registry.py +0 -0
  83. {plain-0.3.1/plain/preflight/compatibility → plain-0.4.0/plain/preflight/security}/__init__.py +0 -0
  84. {plain-0.3.1 → plain-0.4.0}/plain/preflight/security/base.py +0 -0
  85. {plain-0.3.1 → plain-0.4.0}/plain/preflight/security/csrf.py +0 -0
  86. {plain-0.3.1 → plain-0.4.0}/plain/preflight/urls.py +0 -0
  87. {plain-0.3.1 → plain-0.4.0}/plain/runtime/README.md +0 -0
  88. {plain-0.3.1 → plain-0.4.0}/plain/runtime/__init__.py +0 -0
  89. {plain-0.3.1 → plain-0.4.0}/plain/runtime/global_settings.py +0 -0
  90. {plain-0.3.1 → plain-0.4.0}/plain/runtime/user_settings.py +0 -0
  91. {plain-0.3.1 → plain-0.4.0}/plain/signals/README.md +0 -0
  92. {plain-0.3.1 → plain-0.4.0}/plain/signals/__init__.py +0 -0
  93. {plain-0.3.1 → plain-0.4.0}/plain/signals/dispatch/__init__.py +0 -0
  94. {plain-0.3.1 → plain-0.4.0}/plain/signals/dispatch/dispatcher.py +0 -0
  95. {plain-0.3.1 → plain-0.4.0}/plain/signals/dispatch/license.txt +0 -0
  96. {plain-0.3.1 → plain-0.4.0}/plain/signing.py +0 -0
  97. {plain-0.3.1 → plain-0.4.0}/plain/templates/README.md +0 -0
  98. {plain-0.3.1 → plain-0.4.0}/plain/templates/__init__.py +0 -0
  99. {plain-0.3.1 → plain-0.4.0}/plain/templates/core.py +0 -0
  100. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/README.md +0 -0
  101. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/__init__.py +0 -0
  102. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/defaults.py +0 -0
  103. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/extensions.py +0 -0
  104. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/filters.py +0 -0
  105. {plain-0.3.1 → plain-0.4.0}/plain/templates/jinja/globals.py +0 -0
  106. {plain-0.3.1 → plain-0.4.0}/plain/test/README.md +0 -0
  107. {plain-0.3.1 → plain-0.4.0}/plain/test/__init__.py +0 -0
  108. {plain-0.3.1 → plain-0.4.0}/plain/test/client.py +0 -0
  109. {plain-0.3.1 → plain-0.4.0}/plain/urls/README.md +0 -0
  110. {plain-0.3.1 → plain-0.4.0}/plain/urls/__init__.py +0 -0
  111. {plain-0.3.1 → plain-0.4.0}/plain/urls/base.py +0 -0
  112. {plain-0.3.1 → plain-0.4.0}/plain/urls/conf.py +0 -0
  113. {plain-0.3.1 → plain-0.4.0}/plain/urls/converters.py +0 -0
  114. {plain-0.3.1 → plain-0.4.0}/plain/urls/exceptions.py +0 -0
  115. {plain-0.3.1 → plain-0.4.0}/plain/urls/resolvers.py +0 -0
  116. {plain-0.3.1 → plain-0.4.0}/plain/utils/README.md +0 -0
  117. {plain-0.3.1/plain/preflight/security → plain-0.4.0/plain/utils}/__init__.py +0 -0
  118. {plain-0.3.1 → plain-0.4.0}/plain/utils/_os.py +0 -0
  119. {plain-0.3.1 → plain-0.4.0}/plain/utils/cache.py +0 -0
  120. {plain-0.3.1 → plain-0.4.0}/plain/utils/connection.py +0 -0
  121. {plain-0.3.1 → plain-0.4.0}/plain/utils/crypto.py +0 -0
  122. {plain-0.3.1 → plain-0.4.0}/plain/utils/datastructures.py +0 -0
  123. {plain-0.3.1 → plain-0.4.0}/plain/utils/dateformat.py +0 -0
  124. {plain-0.3.1 → plain-0.4.0}/plain/utils/dateparse.py +0 -0
  125. {plain-0.3.1 → plain-0.4.0}/plain/utils/dates.py +0 -0
  126. {plain-0.3.1 → plain-0.4.0}/plain/utils/deconstruct.py +0 -0
  127. {plain-0.3.1 → plain-0.4.0}/plain/utils/decorators.py +0 -0
  128. {plain-0.3.1 → plain-0.4.0}/plain/utils/deprecation.py +0 -0
  129. {plain-0.3.1 → plain-0.4.0}/plain/utils/duration.py +0 -0
  130. {plain-0.3.1 → plain-0.4.0}/plain/utils/email.py +0 -0
  131. {plain-0.3.1 → plain-0.4.0}/plain/utils/encoding.py +0 -0
  132. {plain-0.3.1 → plain-0.4.0}/plain/utils/functional.py +0 -0
  133. {plain-0.3.1 → plain-0.4.0}/plain/utils/hashable.py +0 -0
  134. {plain-0.3.1 → plain-0.4.0}/plain/utils/html.py +0 -0
  135. {plain-0.3.1 → plain-0.4.0}/plain/utils/http.py +0 -0
  136. {plain-0.3.1 → plain-0.4.0}/plain/utils/inspect.py +0 -0
  137. {plain-0.3.1 → plain-0.4.0}/plain/utils/ipv6.py +0 -0
  138. {plain-0.3.1 → plain-0.4.0}/plain/utils/itercompat.py +0 -0
  139. {plain-0.3.1 → plain-0.4.0}/plain/utils/module_loading.py +0 -0
  140. {plain-0.3.1 → plain-0.4.0}/plain/utils/regex_helper.py +0 -0
  141. {plain-0.3.1 → plain-0.4.0}/plain/utils/safestring.py +0 -0
  142. {plain-0.3.1 → plain-0.4.0}/plain/utils/termcolors.py +0 -0
  143. {plain-0.3.1 → plain-0.4.0}/plain/utils/text.py +0 -0
  144. {plain-0.3.1 → plain-0.4.0}/plain/utils/timesince.py +0 -0
  145. {plain-0.3.1 → plain-0.4.0}/plain/utils/timezone.py +0 -0
  146. {plain-0.3.1 → plain-0.4.0}/plain/utils/tree.py +0 -0
  147. {plain-0.3.1 → plain-0.4.0}/plain/validators.py +0 -0
  148. {plain-0.3.1 → plain-0.4.0}/plain/views/README.md +0 -0
  149. {plain-0.3.1 → plain-0.4.0}/plain/views/__init__.py +0 -0
  150. {plain-0.3.1 → plain-0.4.0}/plain/views/base.py +0 -0
  151. {plain-0.3.1 → plain-0.4.0}/plain/views/csrf.py +0 -0
  152. {plain-0.3.1 → plain-0.4.0}/plain/views/errors.py +0 -0
  153. {plain-0.3.1 → plain-0.4.0}/plain/views/exceptions.py +0 -0
  154. {plain-0.3.1 → plain-0.4.0}/plain/views/forms.py +0 -0
  155. {plain-0.3.1 → plain-0.4.0}/plain/views/objects.py +0 -0
  156. {plain-0.3.1 → plain-0.4.0}/plain/views/redirect.py +0 -0
  157. {plain-0.3.1 → plain-0.4.0}/plain/views/templates.py +0 -0
  158. {plain-0.3.1 → plain-0.4.0}/plain/wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plain
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author: Dave Gaeddert
6
6
  Author-email: dave.gaeddert@dropseed.dev
@@ -36,10 +36,6 @@ Automatically runs `npm run compile` if you have a `package.json` with `scripts.
36
36
 
37
37
  Run a Python script in the context of your app.
38
38
 
39
- ### `plain legacy`
40
-
41
- A temporary holdover for running the old `manage.py` commands that haven't been converted yet.
42
-
43
39
  ### `plain preflight`
44
40
 
45
41
  Run preflight checks to ensure your app is ready to run.
@@ -25,26 +25,6 @@ def plain_cli():
25
25
  pass
26
26
 
27
27
 
28
- @plain_cli.command(
29
- "legacy",
30
- context_settings={
31
- "ignore_unknown_options": True,
32
- },
33
- )
34
- @click.argument("legacy_args", nargs=-1, type=click.UNPROCESSED)
35
- def legacy_alias(legacy_args):
36
- result = subprocess.run(
37
- [
38
- "python",
39
- "-m",
40
- "plain.internal.legacy",
41
- *legacy_args,
42
- ],
43
- )
44
- if result.returncode:
45
- sys.exit(result.returncode)
46
-
47
-
48
28
  # @plain_cli.command
49
29
  # def docs():
50
30
  # """Open the Forge documentation in your browser"""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "plain"
3
- version = "0.3.1"
3
+ version = "0.4.0"
4
4
  description = "A web framework for building products with Python."
5
5
  authors = ["Dave Gaeddert <dave.gaeddert@dropseed.dev>"]
6
6
  readme = "README.md"
@@ -1,12 +0,0 @@
1
- import sys
2
- from pathlib import Path
3
-
4
- from plain.internal.legacy import management
5
-
6
- if __name__ == "__main__":
7
- # Automatically put the app dir on the Python path for convenience
8
- app_dir = Path.cwd() / "app"
9
- if app_dir.exists() and app_dir not in sys.path:
10
- sys.path.insert(0, app_dir.as_posix())
11
-
12
- management.execute_from_command_line()
@@ -1,414 +0,0 @@
1
- import functools
2
- import os
3
- import pkgutil
4
- import sys
5
- from argparse import (
6
- _AppendConstAction,
7
- _CountAction,
8
- _StoreConstAction,
9
- _SubParsersAction,
10
- )
11
- from collections import defaultdict
12
- from difflib import get_close_matches
13
- from importlib import import_module
14
-
15
- import plain.runtime
16
- from plain.exceptions import ImproperlyConfigured
17
- from plain.internal.legacy.management.base import (
18
- BaseCommand,
19
- CommandError,
20
- CommandParser,
21
- handle_default_options,
22
- )
23
- from plain.internal.legacy.management.color import color_style
24
- from plain.packages import packages
25
- from plain.runtime import settings
26
-
27
-
28
- def find_commands(management_dir):
29
- """
30
- Given a path to a management directory, return a list of all the command
31
- names that are available.
32
- """
33
- command_dir = os.path.join(management_dir, "commands")
34
- return [
35
- name
36
- for _, name, is_pkg in pkgutil.iter_modules([command_dir])
37
- if not is_pkg and not name.startswith("_")
38
- ]
39
-
40
-
41
- def load_command_class(package_name, name):
42
- """
43
- Given a command name and an application name, return the Command
44
- class instance. Allow all errors raised by the import process
45
- (ImportError, AttributeError) to propagate.
46
- """
47
- module = import_module(f"{package_name}.management.commands.{name}")
48
- return module.Command()
49
-
50
-
51
- @functools.cache
52
- def get_commands():
53
- """
54
- Return a dictionary mapping command names to their callback applications.
55
-
56
- Look for a management.commands package in plain.internal.legacy, and in each
57
- installed application -- if a commands package exists, register all
58
- commands in that package.
59
-
60
- Core commands are always included. If a settings module has been
61
- specified, also include user-defined commands.
62
-
63
- The dictionary is in the format {command_name: package_name}. Key-value
64
- pairs from this dictionary can then be used in calls to
65
- load_command_class(package_name, command_name)
66
-
67
- The dictionary is cached on the first call and reused on subsequent
68
- calls.
69
- """
70
- commands = {name: "plain.internal.legacy" for name in find_commands(__path__[0])}
71
-
72
- if not settings.configured:
73
- return commands
74
-
75
- for package_config in reversed(packages.get_package_configs()):
76
- path = os.path.join(package_config.path, "management")
77
- commands.update({name: package_config.name for name in find_commands(path)})
78
-
79
- return commands
80
-
81
-
82
- def call_command(command_name, *args, **options):
83
- """
84
- Call the given command, with the given options and args/kwargs.
85
-
86
- This is the primary API you should use for calling specific commands.
87
-
88
- `command_name` may be a string or a command object. Using a string is
89
- preferred unless the command object is required for further processing or
90
- testing.
91
-
92
- Some examples:
93
- call_command('migrate')
94
- call_command('shell', plain=True)
95
- call_command('sqlmigrate', 'myapp')
96
-
97
- from plain.internal.legacy.management.commands import flush
98
- cmd = flush.Command()
99
- call_command(cmd, verbosity=0, interactive=False)
100
- # Do something with cmd ...
101
- """
102
- if isinstance(command_name, BaseCommand):
103
- # Command object passed in.
104
- command = command_name
105
- command_name = command.__class__.__module__.split(".")[-1]
106
- else:
107
- # Load the command object by name.
108
- try:
109
- package_name = get_commands()[command_name]
110
- except KeyError:
111
- raise CommandError("Unknown command: %r" % command_name)
112
-
113
- if isinstance(package_name, BaseCommand):
114
- # If the command is already loaded, use it directly.
115
- command = package_name
116
- else:
117
- command = load_command_class(package_name, command_name)
118
-
119
- # Simulate argument parsing to get the option defaults (see #10080 for details).
120
- parser = command.create_parser("", command_name)
121
- # Use the `dest` option name from the parser option
122
- opt_mapping = {
123
- min(s_opt.option_strings).lstrip("-").replace("-", "_"): s_opt.dest
124
- for s_opt in parser._actions
125
- if s_opt.option_strings
126
- }
127
- arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
128
- parse_args = []
129
- for arg in args:
130
- if isinstance(arg, list | tuple):
131
- parse_args += map(str, arg)
132
- else:
133
- parse_args.append(str(arg))
134
-
135
- def get_actions(parser):
136
- # Parser actions and actions from sub-parser choices.
137
- for opt in parser._actions:
138
- if isinstance(opt, _SubParsersAction):
139
- for sub_opt in opt.choices.values():
140
- yield from get_actions(sub_opt)
141
- else:
142
- yield opt
143
-
144
- parser_actions = list(get_actions(parser))
145
- mutually_exclusive_required_options = {
146
- opt
147
- for group in parser._mutually_exclusive_groups
148
- for opt in group._group_actions
149
- if group.required
150
- }
151
- # Any required arguments which are passed in via **options must be passed
152
- # to parse_args().
153
- for opt in parser_actions:
154
- if opt.dest in options and (
155
- opt.required or opt in mutually_exclusive_required_options
156
- ):
157
- opt_dest_count = sum(v == opt.dest for v in opt_mapping.values())
158
- if opt_dest_count > 1:
159
- raise TypeError(
160
- f"Cannot pass the dest {opt.dest!r} that matches multiple "
161
- f"arguments via **options."
162
- )
163
- parse_args.append(min(opt.option_strings))
164
- if isinstance(opt, _AppendConstAction | _CountAction | _StoreConstAction):
165
- continue
166
- value = arg_options[opt.dest]
167
- if isinstance(value, list | tuple):
168
- parse_args += map(str, value)
169
- else:
170
- parse_args.append(str(value))
171
- defaults = parser.parse_args(args=parse_args)
172
- defaults = dict(defaults._get_kwargs(), **arg_options)
173
- # Raise an error if any unknown options were passed.
174
- stealth_options = set(command.base_stealth_options + command.stealth_options)
175
- dest_parameters = {action.dest for action in parser_actions}
176
- valid_options = (dest_parameters | stealth_options).union(opt_mapping)
177
- unknown_options = set(options) - valid_options
178
- if unknown_options:
179
- raise TypeError(
180
- "Unknown option(s) for {} command: {}. Valid options are: {}.".format(
181
- command_name,
182
- ", ".join(sorted(unknown_options)),
183
- ", ".join(sorted(valid_options)),
184
- )
185
- )
186
- # Move positional args out of options to mimic legacy optparse
187
- args = defaults.pop("args", ())
188
- if "skip_checks" not in options:
189
- defaults["skip_checks"] = True
190
-
191
- return command.execute(*args, **defaults)
192
-
193
-
194
- class ManagementUtility:
195
- """
196
- Encapsulate the logic of the django-admin and manage.py utilities.
197
- """
198
-
199
- def __init__(self, argv=None):
200
- self.argv = argv or sys.argv[:]
201
- self.prog_name = os.path.basename(self.argv[0])
202
- if self.prog_name == "__main__.py":
203
- self.prog_name = "python -m django"
204
- self.settings_exception = None
205
-
206
- def main_help_text(self, commands_only=False):
207
- """Return the script's main help text, as a string."""
208
- if commands_only:
209
- usage = sorted(get_commands())
210
- else:
211
- usage = [
212
- "",
213
- "Type '%s help <subcommand>' for help on a specific subcommand."
214
- % self.prog_name,
215
- "",
216
- "Available subcommands:",
217
- ]
218
- commands_dict = defaultdict(lambda: [])
219
- for name, app in get_commands().items():
220
- if app == "plain.internal.legacy":
221
- app = "django"
222
- else:
223
- app = app.rpartition(".")[-1]
224
- commands_dict[app].append(name)
225
- style = color_style()
226
- for app in sorted(commands_dict):
227
- usage.append("")
228
- usage.append(style.NOTICE("[%s]" % app))
229
- for name in sorted(commands_dict[app]):
230
- usage.append(" %s" % name)
231
- # Output an extra note if settings are not properly configured
232
- if self.settings_exception is not None:
233
- usage.append(
234
- style.NOTICE(
235
- "Note that only Plain core commands are listed "
236
- "as settings are not properly configured (error: %s)."
237
- % self.settings_exception
238
- )
239
- )
240
-
241
- return "\n".join(usage)
242
-
243
- def fetch_command(self, subcommand):
244
- """
245
- Try to fetch the given subcommand, printing a message with the
246
- appropriate command called from the command line (usually
247
- "django-admin" or "manage.py") if it can't be found.
248
- """
249
- # Get commands outside of try block to prevent swallowing exceptions
250
- commands = get_commands()
251
- try:
252
- package_name = commands[subcommand]
253
- except KeyError:
254
- if os.environ.get("PLAIN_SETTINGS_MODULE"):
255
- # If `subcommand` is missing due to misconfigured settings, the
256
- # following line will retrigger an ImproperlyConfigured exception
257
- # (get_commands() swallows the original one) so the user is
258
- # informed about it.
259
- settings.INSTALLED_PACKAGES
260
- elif not settings.configured:
261
- sys.stderr.write("No Plain settings specified.\n")
262
- possible_matches = get_close_matches(subcommand, commands)
263
- sys.stderr.write("Unknown command: %r" % subcommand)
264
- if possible_matches:
265
- sys.stderr.write(". Did you mean %s?" % possible_matches[0])
266
- sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
267
- sys.exit(1)
268
- if isinstance(package_name, BaseCommand):
269
- # If the command is already loaded, use it directly.
270
- klass = package_name
271
- else:
272
- klass = load_command_class(package_name, subcommand)
273
- return klass
274
-
275
- def autocomplete(self):
276
- """
277
- Output completion suggestions for BASH.
278
-
279
- The output of this function is passed to BASH's `COMREPLY` variable and
280
- treated as completion suggestions. `COMREPLY` expects a space
281
- separated string as the result.
282
-
283
- The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
284
- to get information about the cli input. Please refer to the BASH
285
- man-page for more information about this variables.
286
-
287
- Subcommand options are saved as pairs. A pair consists of
288
- the long option string (e.g. '--exclude') and a boolean
289
- value indicating if the option requires arguments. When printing to
290
- stdout, an equal sign is appended to options which require arguments.
291
-
292
- Note: If debugging this function, it is recommended to write the debug
293
- output in a separate file. Otherwise the debug output will be treated
294
- and formatted as potential completion suggestions.
295
- """
296
- # Don't complete if user hasn't sourced bash_completion file.
297
- if "DJANGO_AUTO_COMPLETE" not in os.environ:
298
- return
299
-
300
- cwords = os.environ["COMP_WORDS"].split()[1:]
301
- cword = int(os.environ["COMP_CWORD"])
302
-
303
- try:
304
- curr = cwords[cword - 1]
305
- except IndexError:
306
- curr = ""
307
-
308
- subcommands = [*get_commands(), "help"]
309
- options = [("--help", False)]
310
-
311
- # subcommand
312
- if cword == 1:
313
- print(" ".join(sorted(filter(lambda x: x.startswith(curr), subcommands))))
314
- # subcommand options
315
- # special case: the 'help' subcommand has no options
316
- elif cwords[0] in subcommands and cwords[0] != "help":
317
- subcommand_cls = self.fetch_command(cwords[0])
318
- # special case: add the names of installed packages to options
319
- if cwords[0] in ("dumpdata", "sqlmigrate", "sqlsequencereset", "test"):
320
- try:
321
- package_configs = packages.get_package_configs()
322
- # Get the last part of the dotted path as the app name.
323
- options.extend(
324
- (package_config.label, 0) for package_config in package_configs
325
- )
326
- except ImportError:
327
- # Fail silently if PLAIN_SETTINGS_MODULE isn't set. The
328
- # user will find out once they execute the command.
329
- pass
330
- parser = subcommand_cls.create_parser("", cwords[0])
331
- options.extend(
332
- (min(s_opt.option_strings), s_opt.nargs != 0)
333
- for s_opt in parser._actions
334
- if s_opt.option_strings
335
- )
336
- # filter out previously specified options from available options
337
- prev_opts = {x.split("=")[0] for x in cwords[1 : cword - 1]}
338
- options = (opt for opt in options if opt[0] not in prev_opts)
339
-
340
- # filter options by current input
341
- options = sorted((k, v) for k, v in options if k.startswith(curr))
342
- for opt_label, require_arg in options:
343
- # append '=' to options which require args
344
- if require_arg:
345
- opt_label += "="
346
- print(opt_label)
347
- # Exit code of the bash completion function is never passed back to
348
- # the user, so it's safe to always exit with 0.
349
- # For more details see #25420.
350
- sys.exit(0)
351
-
352
- def execute(self):
353
- """
354
- Given the command-line arguments, figure out which subcommand is being
355
- run, create a parser appropriate to that command, and run it.
356
- """
357
- try:
358
- subcommand = self.argv[1]
359
- except IndexError:
360
- subcommand = "help" # Display help if no arguments were given.
361
-
362
- # Preprocess options to extract --settings and --pythonpath.
363
- # These options could affect the commands that are available, so they
364
- # must be processed early.
365
- parser = CommandParser(
366
- prog=self.prog_name,
367
- usage="%(prog)s subcommand [options] [args]",
368
- add_help=False,
369
- allow_abbrev=False,
370
- )
371
- parser.add_argument("--settings")
372
- parser.add_argument("--pythonpath")
373
- parser.add_argument("args", nargs="*") # catch-all
374
- try:
375
- options, args = parser.parse_known_args(self.argv[2:])
376
- handle_default_options(options)
377
- except CommandError:
378
- pass # Ignore any option errors at this point.
379
-
380
- try:
381
- settings.INSTALLED_PACKAGES
382
- except ImproperlyConfigured as exc:
383
- self.settings_exception = exc
384
- except ImportError as exc:
385
- self.settings_exception = exc
386
-
387
- if settings.configured:
388
- plain.runtime.setup()
389
-
390
- self.autocomplete()
391
-
392
- if subcommand == "help":
393
- if "--commands" in args:
394
- sys.stdout.write(self.main_help_text(commands_only=True) + "\n")
395
- elif not options.args:
396
- sys.stdout.write(self.main_help_text() + "\n")
397
- else:
398
- self.fetch_command(options.args[0]).print_help(
399
- self.prog_name, options.args[0]
400
- )
401
- # Special-cases: We want 'django-admin --version' and
402
- # 'django-admin --help' to work, for backwards compatibility.
403
- elif subcommand == "version" or self.argv[1:] == ["--version"]:
404
- sys.stdout.write(plain.runtime.__version__ + "\n")
405
- elif self.argv[1:] in (["--help"], ["-h"]):
406
- sys.stdout.write(self.main_help_text() + "\n")
407
- else:
408
- self.fetch_command(subcommand).run_from_argv(self.argv)
409
-
410
-
411
- def execute_from_command_line(argv=None):
412
- """Run a ManagementUtility."""
413
- utility = ManagementUtility(argv)
414
- utility.execute()