plain 0.66.0__py3-none-any.whl → 0.101.2__py3-none-any.whl

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 (197) hide show
  1. plain/CHANGELOG.md +684 -0
  2. plain/README.md +1 -1
  3. plain/assets/compile.py +25 -12
  4. plain/assets/finders.py +24 -17
  5. plain/assets/fingerprints.py +10 -7
  6. plain/assets/urls.py +1 -1
  7. plain/assets/views.py +47 -33
  8. plain/chores/README.md +25 -23
  9. plain/chores/__init__.py +2 -1
  10. plain/chores/core.py +27 -0
  11. plain/chores/registry.py +23 -53
  12. plain/cli/README.md +185 -16
  13. plain/cli/__init__.py +2 -1
  14. plain/cli/agent.py +236 -0
  15. plain/cli/build.py +7 -8
  16. plain/cli/changelog.py +11 -5
  17. plain/cli/chores.py +32 -34
  18. plain/cli/core.py +112 -28
  19. plain/cli/docs.py +52 -11
  20. plain/cli/formatting.py +40 -17
  21. plain/cli/install.py +10 -54
  22. plain/cli/{agent/llmdocs.py → llmdocs.py} +21 -9
  23. plain/cli/output.py +6 -2
  24. plain/cli/preflight.py +175 -102
  25. plain/cli/print.py +4 -4
  26. plain/cli/registry.py +95 -26
  27. plain/cli/request.py +206 -0
  28. plain/cli/runtime.py +45 -0
  29. plain/cli/scaffold.py +2 -7
  30. plain/cli/server.py +153 -0
  31. plain/cli/settings.py +53 -49
  32. plain/cli/shell.py +15 -12
  33. plain/cli/startup.py +9 -8
  34. plain/cli/upgrade.py +17 -104
  35. plain/cli/urls.py +12 -7
  36. plain/cli/utils.py +3 -3
  37. plain/csrf/README.md +65 -40
  38. plain/csrf/middleware.py +53 -43
  39. plain/debug.py +5 -2
  40. plain/exceptions.py +22 -114
  41. plain/forms/README.md +453 -24
  42. plain/forms/__init__.py +55 -4
  43. plain/forms/boundfield.py +15 -8
  44. plain/forms/exceptions.py +1 -1
  45. plain/forms/fields.py +346 -143
  46. plain/forms/forms.py +75 -45
  47. plain/http/README.md +356 -9
  48. plain/http/__init__.py +41 -26
  49. plain/http/cookie.py +15 -7
  50. plain/http/exceptions.py +65 -0
  51. plain/http/middleware.py +32 -0
  52. plain/http/multipartparser.py +99 -88
  53. plain/http/request.py +362 -250
  54. plain/http/response.py +99 -197
  55. plain/internal/__init__.py +8 -1
  56. plain/internal/files/base.py +35 -19
  57. plain/internal/files/locks.py +19 -11
  58. plain/internal/files/move.py +8 -3
  59. plain/internal/files/temp.py +25 -6
  60. plain/internal/files/uploadedfile.py +47 -28
  61. plain/internal/files/uploadhandler.py +64 -58
  62. plain/internal/files/utils.py +24 -10
  63. plain/internal/handlers/base.py +34 -23
  64. plain/internal/handlers/exception.py +68 -65
  65. plain/internal/handlers/wsgi.py +65 -54
  66. plain/internal/middleware/headers.py +37 -11
  67. plain/internal/middleware/hosts.py +11 -13
  68. plain/internal/middleware/https.py +17 -7
  69. plain/internal/middleware/slash.py +14 -9
  70. plain/internal/reloader.py +77 -0
  71. plain/json.py +2 -1
  72. plain/logs/README.md +161 -62
  73. plain/logs/__init__.py +1 -1
  74. plain/logs/{loggers.py → app.py} +71 -67
  75. plain/logs/configure.py +63 -14
  76. plain/logs/debug.py +17 -6
  77. plain/logs/filters.py +15 -0
  78. plain/logs/formatters.py +7 -4
  79. plain/packages/README.md +105 -23
  80. plain/packages/config.py +15 -7
  81. plain/packages/registry.py +40 -15
  82. plain/paginator.py +31 -21
  83. plain/preflight/README.md +208 -23
  84. plain/preflight/__init__.py +5 -24
  85. plain/preflight/checks.py +12 -0
  86. plain/preflight/files.py +19 -13
  87. plain/preflight/registry.py +80 -58
  88. plain/preflight/results.py +37 -0
  89. plain/preflight/security.py +65 -71
  90. plain/preflight/settings.py +54 -0
  91. plain/preflight/urls.py +10 -48
  92. plain/runtime/README.md +115 -47
  93. plain/runtime/__init__.py +10 -6
  94. plain/runtime/global_settings.py +43 -33
  95. plain/runtime/secret.py +20 -0
  96. plain/runtime/user_settings.py +110 -38
  97. plain/runtime/utils.py +1 -1
  98. plain/server/LICENSE +35 -0
  99. plain/server/README.md +155 -0
  100. plain/server/__init__.py +9 -0
  101. plain/server/app.py +52 -0
  102. plain/server/arbiter.py +555 -0
  103. plain/server/config.py +118 -0
  104. plain/server/errors.py +31 -0
  105. plain/server/glogging.py +292 -0
  106. plain/server/http/__init__.py +12 -0
  107. plain/server/http/body.py +283 -0
  108. plain/server/http/errors.py +155 -0
  109. plain/server/http/message.py +400 -0
  110. plain/server/http/parser.py +70 -0
  111. plain/server/http/unreader.py +88 -0
  112. plain/server/http/wsgi.py +421 -0
  113. plain/server/pidfile.py +92 -0
  114. plain/server/sock.py +240 -0
  115. plain/server/util.py +317 -0
  116. plain/server/workers/__init__.py +6 -0
  117. plain/server/workers/base.py +304 -0
  118. plain/server/workers/sync.py +212 -0
  119. plain/server/workers/thread.py +399 -0
  120. plain/server/workers/workertmp.py +50 -0
  121. plain/signals/README.md +170 -1
  122. plain/signals/__init__.py +0 -1
  123. plain/signals/dispatch/dispatcher.py +49 -27
  124. plain/signing.py +131 -35
  125. plain/skills/README.md +36 -0
  126. plain/skills/plain-docs/SKILL.md +25 -0
  127. plain/skills/plain-install/SKILL.md +26 -0
  128. plain/skills/plain-request/SKILL.md +39 -0
  129. plain/skills/plain-shell/SKILL.md +24 -0
  130. plain/skills/plain-upgrade/SKILL.md +35 -0
  131. plain/templates/README.md +211 -20
  132. plain/templates/jinja/__init__.py +14 -27
  133. plain/templates/jinja/environments.py +5 -4
  134. plain/templates/jinja/extensions.py +12 -5
  135. plain/templates/jinja/filters.py +7 -2
  136. plain/templates/jinja/globals.py +2 -2
  137. plain/test/README.md +184 -22
  138. plain/test/client.py +340 -222
  139. plain/test/encoding.py +9 -6
  140. plain/test/exceptions.py +7 -2
  141. plain/urls/README.md +157 -73
  142. plain/urls/converters.py +18 -15
  143. plain/urls/exceptions.py +2 -2
  144. plain/urls/patterns.py +56 -40
  145. plain/urls/resolvers.py +38 -28
  146. plain/urls/utils.py +5 -1
  147. plain/utils/README.md +250 -3
  148. plain/utils/cache.py +17 -11
  149. plain/utils/crypto.py +21 -5
  150. plain/utils/datastructures.py +89 -56
  151. plain/utils/dateparse.py +9 -6
  152. plain/utils/deconstruct.py +15 -7
  153. plain/utils/decorators.py +5 -1
  154. plain/utils/dotenv.py +373 -0
  155. plain/utils/duration.py +8 -4
  156. plain/utils/encoding.py +14 -7
  157. plain/utils/functional.py +66 -49
  158. plain/utils/hashable.py +5 -1
  159. plain/utils/html.py +36 -22
  160. plain/utils/http.py +16 -9
  161. plain/utils/inspect.py +14 -6
  162. plain/utils/ipv6.py +7 -3
  163. plain/utils/itercompat.py +6 -1
  164. plain/utils/module_loading.py +7 -3
  165. plain/utils/regex_helper.py +37 -23
  166. plain/utils/safestring.py +14 -6
  167. plain/utils/text.py +41 -23
  168. plain/utils/timezone.py +33 -22
  169. plain/utils/tree.py +35 -19
  170. plain/validators.py +94 -52
  171. plain/views/README.md +156 -79
  172. plain/views/__init__.py +0 -1
  173. plain/views/base.py +25 -18
  174. plain/views/errors.py +13 -5
  175. plain/views/exceptions.py +4 -1
  176. plain/views/forms.py +6 -6
  177. plain/views/objects.py +52 -49
  178. plain/views/redirect.py +18 -15
  179. plain/views/templates.py +5 -3
  180. plain/wsgi.py +3 -1
  181. {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/METADATA +4 -2
  182. plain-0.101.2.dist-info/RECORD +201 -0
  183. {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/WHEEL +1 -1
  184. plain-0.101.2.dist-info/entry_points.txt +2 -0
  185. plain/AGENTS.md +0 -18
  186. plain/cli/agent/__init__.py +0 -20
  187. plain/cli/agent/docs.py +0 -80
  188. plain/cli/agent/md.py +0 -87
  189. plain/cli/agent/prompt.py +0 -45
  190. plain/cli/agent/request.py +0 -181
  191. plain/csrf/views.py +0 -31
  192. plain/logs/utils.py +0 -46
  193. plain/preflight/messages.py +0 -81
  194. plain/templates/AGENTS.md +0 -3
  195. plain-0.66.0.dist-info/RECORD +0 -168
  196. plain-0.66.0.dist-info/entry_points.txt +0 -4
  197. {plain-0.66.0.dist-info → plain-0.101.2.dist-info}/licenses/LICENSE +0 -0
plain/cli/agent/docs.py DELETED
@@ -1,80 +0,0 @@
1
- import importlib.util
2
- import pkgutil
3
- from pathlib import Path
4
-
5
- import click
6
-
7
- from .llmdocs import LLMDocs
8
-
9
-
10
- @click.command()
11
- @click.argument("package", default="", required=False)
12
- @click.option(
13
- "--list",
14
- "show_list",
15
- is_flag=True,
16
- help="List available packages",
17
- )
18
- def docs(package, show_list):
19
- """Show LLM-friendly documentation and source for a package."""
20
-
21
- if show_list:
22
- # List available packages using same discovery logic as md command
23
- try:
24
- available_packages = []
25
-
26
- # Check for plain.* subpackages (including core plain)
27
- try:
28
- import plain
29
-
30
- # Check core plain package (namespace package)
31
- plain_spec = importlib.util.find_spec("plain")
32
- if plain_spec and plain_spec.submodule_search_locations:
33
- available_packages.append("plain")
34
-
35
- # Check other plain.* subpackages
36
- for importer, modname, ispkg in pkgutil.iter_modules(
37
- plain.__path__, "plain."
38
- ):
39
- if ispkg:
40
- available_packages.append(modname)
41
- except Exception:
42
- pass
43
-
44
- if available_packages:
45
- for pkg in sorted(available_packages):
46
- click.echo(f"- {pkg}")
47
- else:
48
- click.echo("No packages found.")
49
- except Exception as e:
50
- click.echo(f"Error listing packages: {e}")
51
- return
52
-
53
- if not package:
54
- raise click.UsageError(
55
- "Package name required. Usage: plain agent docs [package-name]"
56
- )
57
-
58
- # Convert hyphens to dots (e.g., plain-models -> plain.models)
59
- package = package.replace("-", ".")
60
-
61
- # Automatically prefix if we need to
62
- if not package.startswith("plain"):
63
- package = f"plain.{package}"
64
-
65
- try:
66
- # Get the path for this specific package
67
- spec = importlib.util.find_spec(package)
68
- if not spec or not spec.origin:
69
- raise click.UsageError(f"Package {package} not found")
70
-
71
- package_path = Path(spec.origin).parent
72
- paths = [package_path]
73
-
74
- # Generate docs for this specific package
75
- source_docs = LLMDocs(paths)
76
- source_docs.load()
77
- source_docs.print(relative_to=package_path.parent)
78
-
79
- except Exception as e:
80
- raise click.UsageError(f"Error loading documentation for {package}: {e}")
plain/cli/agent/md.py DELETED
@@ -1,87 +0,0 @@
1
- import importlib.util
2
- import pkgutil
3
- from pathlib import Path
4
-
5
- import click
6
-
7
- from ..output import iterate_markdown
8
-
9
-
10
- def _get_packages_with_agents():
11
- """Get dict mapping package names to AGENTS.md paths."""
12
- agents_files = {}
13
-
14
- # Check for plain.* subpackages (including core plain)
15
- try:
16
- import plain
17
-
18
- # Check core plain package (namespace package)
19
- plain_spec = importlib.util.find_spec("plain")
20
- if plain_spec and plain_spec.submodule_search_locations:
21
- # For namespace packages, use the first search location
22
- plain_path = Path(plain_spec.submodule_search_locations[0])
23
- agents_path = plain_path / "AGENTS.md"
24
- if agents_path.exists():
25
- agents_files["plain"] = agents_path
26
-
27
- # Check other plain.* subpackages
28
- for importer, modname, ispkg in pkgutil.iter_modules(plain.__path__, "plain."):
29
- if ispkg:
30
- try:
31
- spec = importlib.util.find_spec(modname)
32
- if spec and spec.origin:
33
- package_path = Path(spec.origin).parent
34
- # Look for AGENTS.md at package root
35
- agents_path = package_path / "AGENTS.md"
36
- if agents_path.exists():
37
- agents_files[modname] = agents_path
38
- except Exception:
39
- continue
40
- except Exception:
41
- pass
42
-
43
- return agents_files
44
-
45
-
46
- @click.command("md")
47
- @click.argument("package", default="", required=False)
48
- @click.option(
49
- "--all",
50
- "show_all",
51
- is_flag=True,
52
- help="Show AGENTS.md for all packages that have them",
53
- )
54
- @click.option(
55
- "--list",
56
- "show_list",
57
- is_flag=True,
58
- help="List packages with AGENTS.md files",
59
- )
60
- def md(package, show_all, show_list):
61
- """Show AGENTS.md for a package."""
62
-
63
- agents_files = _get_packages_with_agents()
64
-
65
- if show_list:
66
- for pkg in sorted(agents_files.keys()):
67
- click.echo(f"- {pkg}")
68
-
69
- return
70
-
71
- if show_all:
72
- for pkg in sorted(agents_files.keys()):
73
- agents_path = agents_files[pkg]
74
- for line in iterate_markdown(agents_path.read_text()):
75
- click.echo(line, nl=False)
76
- print()
77
-
78
- return
79
-
80
- if not package:
81
- raise click.UsageError(
82
- "Package name or --all required. Use --list to see available packages."
83
- )
84
-
85
- agents_path = agents_files[package]
86
- for line in iterate_markdown(agents_path.read_text()):
87
- click.echo(line, nl=False)
plain/cli/agent/prompt.py DELETED
@@ -1,45 +0,0 @@
1
- import os
2
- import shlex
3
- import subprocess
4
-
5
- import click
6
-
7
-
8
- def is_agent_environment():
9
- """Check if we're running inside a coding agent."""
10
- return bool(
11
- os.environ.get("CLAUDECODE")
12
- or os.environ.get("CODEX_SANDBOX")
13
- or os.environ.get("CURSOR_ENVIRONMENT")
14
- )
15
-
16
-
17
- def prompt_agent(
18
- prompt: str, agent_command: str | None = None, print_only: bool = False
19
- ) -> bool:
20
- if is_agent_environment():
21
- click.echo(prompt)
22
- return True
23
-
24
- if print_only or not agent_command:
25
- click.echo(prompt)
26
- if not print_only:
27
- click.secho(
28
- "\nCopy the prompt above to a coding agent. To run an agent automatically, use --agent-command or set the PLAIN_AGENT_COMMAND environment variable.",
29
- dim=True,
30
- italic=True,
31
- err=True,
32
- )
33
- return True
34
- else:
35
- cmd = shlex.split(agent_command)
36
- cmd.append(prompt)
37
- result = subprocess.run(cmd, check=False)
38
- if result.returncode != 0:
39
- click.secho(
40
- f"Agent command failed with exit code {result.returncode}",
41
- fg="red",
42
- err=True,
43
- )
44
- return False
45
- return True
@@ -1,181 +0,0 @@
1
- import json
2
-
3
- import click
4
-
5
- from plain.runtime import settings
6
- from plain.test import Client
7
-
8
-
9
- @click.command()
10
- @click.argument("path")
11
- @click.option(
12
- "--method",
13
- default="GET",
14
- help="HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)",
15
- )
16
- @click.option(
17
- "--data",
18
- help="Request data (JSON string for POST/PUT/PATCH)",
19
- )
20
- @click.option(
21
- "--user",
22
- "user_id",
23
- help="User ID to authenticate as (skips normal authentication)",
24
- )
25
- @click.option(
26
- "--follow/--no-follow",
27
- default=True,
28
- help="Follow redirects (default: True)",
29
- )
30
- @click.option(
31
- "--content-type",
32
- help="Content-Type header for request data",
33
- )
34
- @click.option(
35
- "--header",
36
- "headers",
37
- multiple=True,
38
- help="Additional headers (format: 'Name: Value')",
39
- )
40
- def request(path, method, data, user_id, follow, content_type, headers):
41
- """Make an HTTP request using the test client against the development database."""
42
-
43
- try:
44
- # Only allow in DEBUG mode for security
45
- if not settings.DEBUG:
46
- click.secho("This command only works when DEBUG=True", fg="red", err=True)
47
- return
48
-
49
- # Temporarily add testserver to ALLOWED_HOSTS so the test client can make requests
50
- original_allowed_hosts = settings.ALLOWED_HOSTS
51
- settings.ALLOWED_HOSTS = ["*"]
52
-
53
- try:
54
- # Create test client
55
- client = Client()
56
-
57
- # If user_id provided, force login
58
- if user_id:
59
- try:
60
- # Get the User model using plain.auth utility
61
- from plain.auth import get_user_model
62
-
63
- User = get_user_model()
64
-
65
- # Get the user
66
- try:
67
- user = User.query.get(id=user_id)
68
- client.force_login(user)
69
- click.secho(
70
- f"Authenticated as user {user_id}", fg="green", dim=True
71
- )
72
- except User.DoesNotExist:
73
- click.secho(f"User {user_id} not found", fg="red", err=True)
74
- return
75
-
76
- except Exception as e:
77
- click.secho(f"Authentication error: {e}", fg="red", err=True)
78
- return
79
-
80
- # Parse additional headers
81
- header_dict = {}
82
- for header in headers:
83
- if ":" in header:
84
- key, value = header.split(":", 1)
85
- header_dict[key.strip()] = value.strip()
86
-
87
- # Prepare request data
88
- if data and content_type and "json" in content_type.lower():
89
- try:
90
- # Validate JSON
91
- json.loads(data)
92
- except json.JSONDecodeError as e:
93
- click.secho(f"Invalid JSON data: {e}", fg="red", err=True)
94
- return
95
-
96
- # Make the request
97
- method = method.upper()
98
- kwargs = {
99
- "path": path,
100
- "follow": follow,
101
- "headers": header_dict or None,
102
- }
103
-
104
- if method in ("POST", "PUT", "PATCH") and data:
105
- kwargs["data"] = data
106
- if content_type:
107
- kwargs["content_type"] = content_type
108
-
109
- # Call the appropriate client method
110
- if method == "GET":
111
- response = client.get(**kwargs)
112
- elif method == "POST":
113
- response = client.post(**kwargs)
114
- elif method == "PUT":
115
- response = client.put(**kwargs)
116
- elif method == "PATCH":
117
- response = client.patch(**kwargs)
118
- elif method == "DELETE":
119
- response = client.delete(**kwargs)
120
- elif method == "HEAD":
121
- response = client.head(**kwargs)
122
- elif method == "OPTIONS":
123
- response = client.options(**kwargs)
124
- elif method == "TRACE":
125
- response = client.trace(**kwargs)
126
- else:
127
- click.secho(f"Unsupported HTTP method: {method}", fg="red", err=True)
128
- return
129
-
130
- # Display response information
131
- click.secho(
132
- f"HTTP {response.status_code}",
133
- fg="green" if response.status_code < 400 else "red",
134
- bold=True,
135
- )
136
-
137
- # Show additional response info first
138
- if hasattr(response, "user"):
139
- click.secho(f"Authenticated user: {response.user}", fg="blue", dim=True)
140
-
141
- if hasattr(response, "resolver_match") and response.resolver_match:
142
- match = response.resolver_match
143
- url_name = match.namespaced_url_name or match.url_name or "unnamed"
144
- click.secho(f"URL pattern matched: {url_name}", fg="blue", dim=True)
145
-
146
- # Show headers
147
- if response.headers:
148
- click.secho("Response Headers:", fg="yellow", bold=True)
149
- for key, value in response.headers.items():
150
- click.echo(f" {key}: {value}")
151
- click.echo()
152
-
153
- # Show response content last
154
- if response.content:
155
- content_type = response.headers.get("Content-Type", "")
156
-
157
- if "json" in content_type.lower():
158
- try:
159
- json_data = response.json()
160
- click.secho("Response Body (JSON):", fg="yellow", bold=True)
161
- click.echo(json.dumps(json_data, indent=2))
162
- except Exception:
163
- click.secho("Response Body:", fg="yellow", bold=True)
164
- click.echo(response.content.decode("utf-8", errors="replace"))
165
- elif "html" in content_type.lower():
166
- click.secho("Response Body (HTML):", fg="yellow", bold=True)
167
- content = response.content.decode("utf-8", errors="replace")
168
- click.echo(content)
169
- else:
170
- click.secho("Response Body:", fg="yellow", bold=True)
171
- content = response.content.decode("utf-8", errors="replace")
172
- click.echo(content)
173
- else:
174
- click.secho("(No response body)", fg="yellow", dim=True)
175
-
176
- finally:
177
- # Restore original ALLOWED_HOSTS
178
- settings.ALLOWED_HOSTS = original_allowed_hosts
179
-
180
- except Exception as e:
181
- click.secho(f"Request failed: {e}", fg="red", err=True)
plain/csrf/views.py DELETED
@@ -1,31 +0,0 @@
1
- from plain.views import TemplateView
2
-
3
-
4
- class CsrfFailureView(TemplateView):
5
- template_name = "403.html"
6
-
7
- def get_response(self):
8
- response = super().get_response()
9
- response.status_code = 403
10
- return response
11
-
12
- def post(self):
13
- return self.get()
14
-
15
- def put(self):
16
- return self.get()
17
-
18
- def patch(self):
19
- return self.get()
20
-
21
- def delete(self):
22
- return self.get()
23
-
24
- def head(self):
25
- return self.get()
26
-
27
- def options(self):
28
- return self.get()
29
-
30
- def trace(self):
31
- return self.get()
plain/logs/utils.py DELETED
@@ -1,46 +0,0 @@
1
- import logging
2
-
3
- request_logger = logging.getLogger("plain.request")
4
-
5
-
6
- def log_response(
7
- message,
8
- *args,
9
- response=None,
10
- request=None,
11
- logger=request_logger,
12
- level=None,
13
- exception=None,
14
- ):
15
- """
16
- Log errors based on Response status.
17
-
18
- Log 5xx responses as errors and 4xx responses as warnings (unless a level
19
- is given as a keyword argument). The Response status_code and the
20
- request are passed to the logger's extra parameter.
21
- """
22
- # Check if the response has already been logged. Multiple requests to log
23
- # the same response can be received in some cases, e.g., when the
24
- # response is the result of an exception and is logged when the exception
25
- # is caught, to record the exception.
26
- if getattr(response, "_has_been_logged", False):
27
- return
28
-
29
- if level is None:
30
- if response.status_code >= 500:
31
- level = "error"
32
- elif response.status_code >= 400:
33
- level = "warning"
34
- else:
35
- level = "info"
36
-
37
- getattr(logger, level)(
38
- message,
39
- *args,
40
- extra={
41
- "status_code": response.status_code,
42
- "request": request,
43
- },
44
- exc_info=exception,
45
- )
46
- response._has_been_logged = True
@@ -1,81 +0,0 @@
1
- # Levels
2
- DEBUG = 10
3
- INFO = 20
4
- WARNING = 30
5
- ERROR = 40
6
- CRITICAL = 50
7
-
8
-
9
- class CheckMessage:
10
- def __init__(self, level, msg, hint=None, obj=None, id=None):
11
- if not isinstance(level, int):
12
- raise TypeError("The first argument should be level.")
13
- self.level = level
14
- self.msg = msg
15
- self.hint = hint
16
- self.obj = obj
17
- self.id = id
18
-
19
- def __eq__(self, other):
20
- return isinstance(other, self.__class__) and all(
21
- getattr(self, attr) == getattr(other, attr)
22
- for attr in ["level", "msg", "hint", "obj", "id"]
23
- )
24
-
25
- def __str__(self):
26
- try:
27
- from plain import models
28
-
29
- ModelBase = models.base.ModelBase
30
- using_db = True
31
- except ImportError:
32
- using_db = False
33
- ModelBase = object
34
-
35
- if self.obj is None:
36
- obj = "?"
37
- elif using_db and isinstance(self.obj, ModelBase):
38
- # We need to hardcode ModelBase and Field cases because its __str__
39
- # method doesn't return "applabel.modellabel" and cannot be changed.
40
- obj = self.obj._meta.label
41
- else:
42
- obj = str(self.obj)
43
- id = f"({self.id}) " if self.id else ""
44
- hint = f"\n\tHINT: {self.hint}" if self.hint else ""
45
- return f"{obj}: {id}{self.msg}{hint}"
46
-
47
- def __repr__(self):
48
- return f"<{self.__class__.__name__}: level={self.level!r}, msg={self.msg!r}, hint={self.hint!r}, obj={self.obj!r}, id={self.id!r}>"
49
-
50
- def is_serious(self, level=ERROR):
51
- return self.level >= level
52
-
53
- def is_silenced(self):
54
- from plain.runtime import settings
55
-
56
- return self.id in settings.PREFLIGHT_SILENCED_CHECKS
57
-
58
-
59
- class Debug(CheckMessage):
60
- def __init__(self, *args, **kwargs):
61
- super().__init__(DEBUG, *args, **kwargs)
62
-
63
-
64
- class Info(CheckMessage):
65
- def __init__(self, *args, **kwargs):
66
- super().__init__(INFO, *args, **kwargs)
67
-
68
-
69
- class Warning(CheckMessage):
70
- def __init__(self, *args, **kwargs):
71
- super().__init__(WARNING, *args, **kwargs)
72
-
73
-
74
- class Error(CheckMessage):
75
- def __init__(self, *args, **kwargs):
76
- super().__init__(ERROR, *args, **kwargs)
77
-
78
-
79
- class Critical(CheckMessage):
80
- def __init__(self, *args, **kwargs):
81
- super().__init__(CRITICAL, *args, **kwargs)
plain/templates/AGENTS.md DELETED
@@ -1,3 +0,0 @@
1
- # Plain Templates AGENTS.md
2
-
3
- - Plain templates use Jinja2.