plain 0.60.0__py3-none-any.whl → 0.62.0__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.
plain/logs/loggers.py CHANGED
@@ -1,74 +1,183 @@
1
1
  import logging
2
-
3
- app_logger = logging.getLogger("app")
4
-
5
-
6
- class KVLogger:
7
- def __init__(self, logger):
8
- self.logger = logger
9
- self.context = {} # A dict that will be output in every log message
10
-
11
- def log(self, level, message, **kwargs):
12
- msg_kwargs = {
13
- **kwargs,
14
- **self.context, # Put these last so they're at the end of the line
15
- }
16
- self.logger.log(level, f"{message} {self._format_kwargs(msg_kwargs)}")
17
-
18
- def _format_kwargs(self, kwargs):
19
- outputs = []
20
-
21
- for k, v in kwargs.items():
22
- self._validate_key(k)
23
- formatted_value = self._format_value(v)
24
- outputs.append(f"{k}={formatted_value}")
25
-
26
- return " ".join(outputs)
27
-
28
- def _validate_key(self, key):
29
- if " " in key:
30
- raise ValueError("Keys cannot have spaces")
31
-
32
- if "=" in key:
33
- raise ValueError("Keys cannot have equals signs")
34
-
35
- if '"' in key or "'" in key:
36
- raise ValueError("Keys cannot have quotes")
37
-
38
- def _format_value(self, value):
39
- if isinstance(value, str):
40
- s = value
41
- else:
42
- s = str(value)
43
-
44
- if '"' in s:
45
- # Escape quotes and surround it
46
- s = s.replace('"', '\\"')
47
- s = f'"{s}"'
48
- elif s == "":
49
- # Quote empty strings instead of printing nothing
50
- s = '""'
51
- elif any(char in s for char in [" ", "/", "'", ":", "=", "."]):
52
- # Surround these with quotes for parsers
53
- s = f'"{s}"'
54
-
55
- return s
56
-
57
- def info(self, message, **kwargs):
58
- self.log(logging.INFO, message, **kwargs)
59
-
60
- def debug(self, message, **kwargs):
61
- self.log(logging.DEBUG, message, **kwargs)
62
-
63
- def warning(self, message, **kwargs):
64
- self.log(logging.WARNING, message, **kwargs)
65
-
66
- def error(self, message, **kwargs):
67
- self.log(logging.ERROR, message, **kwargs)
68
-
69
- def critical(self, message, **kwargs):
70
- self.log(logging.CRITICAL, message, **kwargs)
71
-
72
-
73
- # Make this accessible from the app_logger
74
- app_logger.kv = KVLogger(app_logger)
2
+ from contextlib import contextmanager
3
+
4
+ from .debug import DebugMode
5
+
6
+
7
+ class AppLogger(logging.Logger):
8
+ """Enhanced logger that supports kwargs-style logging and context management."""
9
+
10
+ def __init__(self, name):
11
+ super().__init__(name)
12
+ self.context = {} # Public, mutable context dict
13
+ self.debug_mode = DebugMode(self)
14
+
15
+ @contextmanager
16
+ def include_context(self, **kwargs):
17
+ """Context manager for temporary context."""
18
+ # Store original context
19
+ original_context = self.context.copy()
20
+
21
+ # Add temporary context
22
+ self.context.update(kwargs)
23
+
24
+ try:
25
+ yield
26
+ finally:
27
+ # Restore original context
28
+ self.context = original_context
29
+
30
+ def force_debug(self):
31
+ """Return context manager for temporarily enabling DEBUG level logging."""
32
+ return self.debug_mode
33
+
34
+ # Override logging methods with explicit parameters for IDE support
35
+ def debug(
36
+ self,
37
+ msg,
38
+ *args,
39
+ exc_info=None,
40
+ extra=None,
41
+ stack_info=False,
42
+ stacklevel=1,
43
+ **context,
44
+ ):
45
+ if self.isEnabledFor(logging.DEBUG):
46
+ self._log(
47
+ logging.DEBUG,
48
+ msg,
49
+ args,
50
+ exc_info=exc_info,
51
+ extra=extra,
52
+ stack_info=stack_info,
53
+ stacklevel=stacklevel,
54
+ **context,
55
+ )
56
+
57
+ def info(
58
+ self,
59
+ msg,
60
+ *args,
61
+ exc_info=None,
62
+ extra=None,
63
+ stack_info=False,
64
+ stacklevel=1,
65
+ **context,
66
+ ):
67
+ if self.isEnabledFor(logging.INFO):
68
+ self._log(
69
+ logging.INFO,
70
+ msg,
71
+ args,
72
+ exc_info=exc_info,
73
+ extra=extra,
74
+ stack_info=stack_info,
75
+ stacklevel=stacklevel,
76
+ **context,
77
+ )
78
+
79
+ def warning(
80
+ self,
81
+ msg,
82
+ *args,
83
+ exc_info=None,
84
+ extra=None,
85
+ stack_info=False,
86
+ stacklevel=1,
87
+ **context,
88
+ ):
89
+ if self.isEnabledFor(logging.WARNING):
90
+ self._log(
91
+ logging.WARNING,
92
+ msg,
93
+ args,
94
+ exc_info=exc_info,
95
+ extra=extra,
96
+ stack_info=stack_info,
97
+ stacklevel=stacklevel,
98
+ **context,
99
+ )
100
+
101
+ def error(
102
+ self,
103
+ msg,
104
+ *args,
105
+ exc_info=None,
106
+ extra=None,
107
+ stack_info=False,
108
+ stacklevel=1,
109
+ **context,
110
+ ):
111
+ if self.isEnabledFor(logging.ERROR):
112
+ self._log(
113
+ logging.ERROR,
114
+ msg,
115
+ args,
116
+ exc_info=exc_info,
117
+ extra=extra,
118
+ stack_info=stack_info,
119
+ stacklevel=stacklevel,
120
+ **context,
121
+ )
122
+
123
+ def critical(
124
+ self,
125
+ msg,
126
+ *args,
127
+ exc_info=None,
128
+ extra=None,
129
+ stack_info=False,
130
+ stacklevel=1,
131
+ **context,
132
+ ):
133
+ if self.isEnabledFor(logging.CRITICAL):
134
+ self._log(
135
+ logging.CRITICAL,
136
+ msg,
137
+ args,
138
+ exc_info=exc_info,
139
+ extra=extra,
140
+ stack_info=stack_info,
141
+ stacklevel=stacklevel,
142
+ **context,
143
+ )
144
+
145
+ def _log(
146
+ self,
147
+ level,
148
+ msg,
149
+ args,
150
+ exc_info=None,
151
+ extra=None,
152
+ stack_info=False,
153
+ stacklevel=1,
154
+ **context,
155
+ ):
156
+ """Low-level logging routine which creates a LogRecord and then calls all handlers."""
157
+ # Check if extra already has a 'context' key
158
+ if extra and "context" in extra:
159
+ raise ValueError(
160
+ "The 'context' key in extra is reserved for Plain's context system"
161
+ )
162
+
163
+ # Build final extra with context
164
+ extra = extra.copy() if extra else {}
165
+
166
+ # Add our context (persistent + kwargs) to extra["context"]
167
+ if self.context or context:
168
+ extra["context"] = {**self.context, **context}
169
+
170
+ # Call the parent logger's _log method with explicit parameters
171
+ super()._log(
172
+ level=level,
173
+ msg=msg,
174
+ args=args,
175
+ exc_info=exc_info,
176
+ extra=extra or None,
177
+ stack_info=stack_info,
178
+ stacklevel=stacklevel,
179
+ )
180
+
181
+
182
+ # Create the default app logger instance
183
+ app_logger = AppLogger("app")
plain/runtime/__init__.py CHANGED
@@ -3,6 +3,9 @@ import sys
3
3
  from importlib.metadata import entry_points
4
4
  from pathlib import Path
5
5
 
6
+ from plain.logs.configure import configure_logging
7
+ from plain.packages import packages_registry
8
+
6
9
  from .user_settings import Settings
7
10
 
8
11
  try:
@@ -48,9 +51,6 @@ def setup():
48
51
  for entry_point in entry_points().select(group="plain.setup"):
49
52
  entry_point.load()()
50
53
 
51
- from plain.logs import configure_logging
52
- from plain.packages import packages_registry
53
-
54
54
  if not APP_PATH.exists():
55
55
  raise AppPathNotFound(
56
56
  "No app directory found. Are you sure you're in a Plain project?"
@@ -62,7 +62,11 @@ def setup():
62
62
  if APP_PATH.parent.as_posix() not in sys.path:
63
63
  sys.path.insert(0, APP_PATH.parent.as_posix())
64
64
 
65
- configure_logging(settings.LOGGING)
65
+ configure_logging(
66
+ plain_log_level=settings.PLAIN_LOG_LEVEL,
67
+ app_log_level=settings.APP_LOG_LEVEL,
68
+ app_log_format=settings.APP_LOG_FORMAT,
69
+ )
66
70
 
67
71
  packages_registry.populate(settings.INSTALLED_PACKAGES)
68
72
 
@@ -3,6 +3,8 @@ Default Plain settings. Override these with settings in the module pointed to
3
3
  by the PLAIN_SETTINGS_MODULE environment variable.
4
4
  """
5
5
 
6
+ from os import environ
7
+
6
8
  from .utils import get_app_info_from_pyproject
7
9
 
8
10
  # MARK: Core Settings
@@ -137,9 +139,11 @@ CSRF_TRUSTED_ORIGINS: list[str] = []
137
139
  CSRF_EXEMPT_PATHS: list[str] = []
138
140
 
139
141
  # MARK: Logging
142
+ # (Uses some custom env names in addition to PLAIN_ prefixed )
140
143
 
141
- # Custom logging configuration.
142
- LOGGING = {}
144
+ PLAIN_LOG_LEVEL: str = environ.get("PLAIN_LOG_LEVEL", "INFO")
145
+ APP_LOG_LEVEL: str = environ.get("APP_LOG_LEVEL", "INFO")
146
+ APP_LOG_FORMAT: str = environ.get("APP_LOG_FORMAT", "keyvalue")
143
147
 
144
148
  # MARK: Assets
145
149
 
@@ -0,0 +1,3 @@
1
+ # Plain Templates AGENTS.md
2
+
3
+ - Plain templates use Jinja2.
plain/views/objects.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from functools import cached_property
2
+ from typing import Any
2
3
 
3
4
  from plain.exceptions import ImproperlyConfigured, ObjectDoesNotExist
4
5
  from plain.forms import Form
@@ -38,19 +39,19 @@ class ObjectTemplateViewMixin:
38
39
  context_object_name = ""
39
40
 
40
41
  @cached_property
41
- def object(self):
42
+ def object(self) -> Any:
42
43
  try:
43
44
  obj = self.get_object()
44
45
  except ObjectDoesNotExist:
45
46
  raise Http404
46
47
 
48
+ # Also raise 404 if get_object() returns None
47
49
  if not obj:
48
- # Also raise 404 if the object is None
49
50
  raise Http404
50
51
 
51
52
  return obj
52
53
 
53
- def get_object(self): # Intentionally untyped... subclasses must override this.
54
+ def get_object(self) -> Any:
54
55
  raise NotImplementedError(
55
56
  f"get_object() is not implemented on {self.__class__.__name__}"
56
57
  )
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.60.0
3
+ Version: 0.62.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
7
- Requires-Python: >=3.11
7
+ Requires-Python: >=3.13
8
8
  Requires-Dist: click>=8.0.0
9
9
  Requires-Dist: jinja2>=3.1.2
10
10
  Requires-Dist: opentelemetry-api>=1.34.1
@@ -1,4 +1,5 @@
1
- plain/CHANGELOG.md,sha256=D_pNXRYALt2M76lgFw0DsNBweBA3wjnESTQycSEMz68,10968
1
+ plain/AGENTS.md,sha256=5XMGBpJgbCNIpp60DPXB7bpAtFk8FAzqiZke95T965o,1038
2
+ plain/CHANGELOG.md,sha256=qEE2843NosxXT0v1-2HE02EX3pesOtoaBDYBqSFCI8k,12429
2
3
  plain/README.md,sha256=5BJyKhf0TDanWVbOQyZ3zsi5Lov9xk-LlJYCDWofM6Y,4078
3
4
  plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
4
5
  plain/debug.py,sha256=XdjnXcbPGsi0J2SpHGaLthhYU5AjhBlkHdemaP4sbYY,758
@@ -20,28 +21,32 @@ plain/chores/__init__.py,sha256=r9TXtQCH-VbvfnIJ5F8FxgQC35GRWFOfmMZN3q9niLg,67
20
21
  plain/chores/registry.py,sha256=V3WjuekRI22LFvJbqSkUXQtiOtuE2ZK8gKV1TRvxRUI,1866
21
22
  plain/cli/README.md,sha256=5C7vsH0ISxu7q5H6buC25MBOILkI_rzdySitswpQgJw,1032
22
23
  plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
23
- plain/cli/agent.py,sha256=nf-Tuc3abxpyV-nShBn1wq0JWjfgd3zY9lLH6rAZSRs,1678
24
24
  plain/cli/build.py,sha256=Lo6AYghJz0DM9fIVUSiBSOKa5vR0XCOxZWEjza6sc8Q,3172
25
25
  plain/cli/changelog.py,sha256=j-k1yZk9mpm-fLZgeWastiyIisxNSuAJfXTQ2B6WQmk,3457
26
26
  plain/cli/chores.py,sha256=xXSSFvr8T5jWfLWqe6E8YVMw1BkQxyOHHVuY0x9RH0A,2412
27
- plain/cli/core.py,sha256=HZZcDVNEMAhantDM4Hnug-SrwU3YgAq1-KDa3_a9-K0,3111
28
- plain/cli/docs.py,sha256=MjFIpNc0FsHACi_spPVumXM-oPCi__3U0Jqf41lqjQM,7481
27
+ plain/cli/core.py,sha256=g0D1OZkYGWt05-V1oDNzX2wcoCIAxrZjlgaQs2qWLlc,3106
28
+ plain/cli/docs.py,sha256=YEEE-Th1CSxiL-wj5fF-ZagqkqAZYkEPRMO1OYUsQrU,1066
29
29
  plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
30
- plain/cli/help.py,sha256=NefZSEIixrX_WELVSnJDHRpLDWf7_4PXmkkMm3Q2mzo,787
31
- plain/cli/install.py,sha256=wvEc3gLP5tvSb1Xwm2467DCXqCPTWxuzktGnlK94SGg,2784
30
+ plain/cli/install.py,sha256=mffSYBmSJSj44OPBfu53nBQoyoz4jk69DvppubIB0mU,2791
32
31
  plain/cli/output.py,sha256=Fe3xS6Va4Bi1ZNrqi0nh09THTsdCyMW2b9SPY5I4n-o,1318
33
32
  plain/cli/preflight.py,sha256=8tHBD4L4nPLUKThfaYx3SUZSJzC48oV2m_Hbn6W4ODc,4124
34
33
  plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
35
34
  plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
36
35
  plain/cli/scaffold.py,sha256=mcywA9DzfwoBSqWl5-Zpgcy1mTNUGEgdvoxXUrGcEVk,1351
37
36
  plain/cli/settings.py,sha256=9cx4bue664I2P7kUedlf4YhCPB0tSKSE4Q8mGyzEv2o,1995
38
- plain/cli/shell.py,sha256=iIwvlTdTBjLBBUdXMAmIRWSoynszOZI79-mrBg4RegU,1373
37
+ plain/cli/shell.py,sha256=PMHdwcRv48qXDToeq82aZaNth-cKc3V2pQ1yISrNMvY,1802
39
38
  plain/cli/startup.py,sha256=wLaFuyUb4ewWhtehBCGicrRCXIIGCRbeCT3ce9hUv-A,1022
40
- plain/cli/upgrade.py,sha256=eGVWm0gpn-Pr6uPsfzojRmh_VU5--B0h9dYfQuXSzi8,5625
39
+ plain/cli/upgrade.py,sha256=T8u81rA2_dSfJaK4vF1_OPkQpspBVWnlPxatyk_mdx0,5632
41
40
  plain/cli/urls.py,sha256=ghCW36aRszxmTo06A50FIvYopb6kQ07QekkDzM6_A1o,3824
42
41
  plain/cli/utils.py,sha256=VwlIh0z7XxzVV8I3qM2kZo07fkJFPoeeVZa1ODG616k,258
42
+ plain/cli/agent/__init__.py,sha256=Ipp65kuIF14TVxNqsj71MsWUePaKHUcdP3QmaYyNcg0,480
43
+ plain/cli/agent/docs.py,sha256=ubX3ZeRHxVaetLk9fjiN9mJ07GZExC-CHUvQoX2DD7c,2464
44
+ plain/cli/agent/llmdocs.py,sha256=AUpNDb1xSOsSpzGOiFvpzUe4f7PUGMiR9cI13aVZouo,5038
45
+ plain/cli/agent/md.py,sha256=7r1II8ckubBFOZNGPASWaPmJdgByWFPINLqIOzRetLQ,2581
46
+ plain/cli/agent/prompt.py,sha256=rugYyQHV7JDNqGrx3_PPShwwqYlnEVbxw8RsczOo8tg,1253
47
+ plain/cli/agent/request.py,sha256=JILrcxEMPagBXWrjNGMy3qatCYCXw-_uJMKkVHk_bho,6549
43
48
  plain/csrf/README.md,sha256=ApWpB-qlEf0LkOKm9Yr-6f_lB9XJEvGFDo_fraw8ghI,2391
44
- plain/csrf/middleware.py,sha256=d_vb8l0-KxzyqCivVq0jTCsFOm-ljwjmjVuZXKVYR5U,5113
49
+ plain/csrf/middleware.py,sha256=n5_7v6qwFKgiAnKVyJa7RhwHoWepLkPudzIgZtdku5A,5119
45
50
  plain/csrf/views.py,sha256=HwQqfI6KPelHP9gSXhjfZaTLQic71PKsoZ6DPhr1rKI,572
46
51
  plain/forms/README.md,sha256=7MJQxNBoKkg0rW16qF6bGpUBxZrMrWjl2DZZk6gjzAU,2258
47
52
  plain/forms/__init__.py,sha256=UxqPwB8CiYPCQdHmUc59jadqaXqDmXBH8y4bt9vTPms,226
@@ -65,17 +70,19 @@ plain/internal/files/uploadedfile.py,sha256=JRB7T3quQjg-1y3l1ASPxywtSQZhaeMc45uF
65
70
  plain/internal/files/uploadhandler.py,sha256=63_QUwAwfq3bevw79i0S7zt2EB2UBoO7MaauvezaVMY,7198
66
71
  plain/internal/files/utils.py,sha256=xN4HTJXDRdcoNyrL1dFd528MBwodRlHZM8DGTD_oBIg,2646
67
72
  plain/internal/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- plain/internal/handlers/base.py,sha256=GFN5qoGqZmGinz6IOJgRW-dV4_nY5Dwo9L0F7muHvoc,6000
69
- plain/internal/handlers/exception.py,sha256=vfha_6-fz6S6VYCP1PMBfue2Gw-_th6jqaTE372fGlw,4809
73
+ plain/internal/handlers/base.py,sha256=ur-nYmpvXjXhu03aPP1KV5GSNaLL_QZoT8x0v8l6_wg,6006
74
+ plain/internal/handlers/exception.py,sha256=TbPYtgZ7ITJahUKhQWkptHK28Lb4zh_nOviNctC2EYs,4815
70
75
  plain/internal/handlers/wsgi.py,sha256=dgPT29t_F9llB-c5RYU3SHxGuZNaZ83xRjOfuOmtOl8,8209
71
76
  plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
77
  plain/internal/middleware/headers.py,sha256=ENIW1Gwat54hv-ejgp2R8QTZm-PlaI7k44WU01YQrNk,964
73
78
  plain/internal/middleware/https.py,sha256=mS1YejfLB_5qlAoMfanh8Wn2O-RdSpOBdhvw2DRcTHs,1257
74
79
  plain/internal/middleware/slash.py,sha256=JWcIfGbXEKH00I7STq1AMdHhFGmQHC8lkKENa6280ko,2846
75
- plain/logs/README.md,sha256=ON6Zylg_WA_V7QiIbRY7sgsl2nfG7KFzIbxK2x3rPuc,1419
76
- plain/logs/__init__.py,sha256=rASvo4qFBDIHfkACmGLNGa6lRGbG9PbNjW6FmBt95ys,168
77
- plain/logs/configure.py,sha256=2kDJ-WPv3PV4H46mz5tTfzIa2kvN6cjVlb3t-AEbMyk,1307
78
- plain/logs/loggers.py,sha256=iz9SYcwP9w5QAuwpULl48SFkVyJuuMoQ_fdLgdCHpNg,2121
80
+ plain/logs/README.md,sha256=rzOHfngjizLgXL21g0svC1Cdya2s_gBA_E-IljtHpy8,4069
81
+ plain/logs/__init__.py,sha256=gFVMcNn5D6z0JrvUJgGsOeYj1NKNtEXhw0MvPDtkN6w,58
82
+ plain/logs/configure.py,sha256=G5kLP-92hOWE7vlWG3lhSbzOKXobavFbqjohevJF1Jg,1322
83
+ plain/logs/debug.py,sha256=QBXA_M498uGtqFnwHN08z6fItiGR4A732JyIWG2b39Q,1048
84
+ plain/logs/formatters.py,sha256=sHB4yo7806YN_V6cCzs1WOGMIZLq5q_eOndRqODI-T4,2380
85
+ plain/logs/loggers.py,sha256=kV2uZDxA5XU4GJF8dAOqny5LmbC8nPKZmakS-xX4x1Y,4625
79
86
  plain/logs/utils.py,sha256=9UzdCCQXJinGDs71Ngw297mlWkhgZStSd67ya4NOW98,1257
80
87
  plain/packages/README.md,sha256=iNqMtwFDVNf2TqKUzLKQW5Y4_GsssmdB4cVerzu27Ro,2674
81
88
  plain/packages/__init__.py,sha256=OpQny0xLplPdPpozVUUkrW2gB-IIYyDT1b4zMzOcCC4,160
@@ -89,8 +96,8 @@ plain/preflight/registry.py,sha256=vcqzaE1MIneNL_ydKPy_1zrSThnzsrWARSClLCJ-4b8,2
89
96
  plain/preflight/security.py,sha256=oxUZBp2M0bpBfUoLYepIxoex2Y90nyjlrL8XU8UTHYY,2438
90
97
  plain/preflight/urls.py,sha256=cQ-WnFa_5oztpKdtwhuIGb7pXEml__bHsjs1SWO2YNI,1468
91
98
  plain/runtime/README.md,sha256=sTqXXJkckwqkk9O06XMMSNRokAYjrZBnB50JD36BsYI,4873
92
- plain/runtime/__init__.py,sha256=8GtvKROf3HUCtneDYXTbEioPcCtwnV76dP57n2PnjuE,2343
93
- plain/runtime/global_settings.py,sha256=LX4g0ncNif_STuM83Idcron1j_TnQ9TJwWbVywexyZo,5788
99
+ plain/runtime/__init__.py,sha256=byFYnHrpUCwkpkHtdNhxr9iUdLDCWJjy92HPj30Ilck,2478
100
+ plain/runtime/global_settings.py,sha256=cDhsZOh0FemxUQE41viBjoMOriXV9_JnbSu28Kon_uI,6014
94
101
  plain/runtime/user_settings.py,sha256=OzMiEkE6ZQ50nxd1WIqirXPiNuMAQULklYHEzgzLWgA,11027
95
102
  plain/runtime/utils.py,sha256=p5IuNTzc7Kq-9Ym7etYnt_xqHw5TioxfSkFeq1bKdgk,832
96
103
  plain/signals/README.md,sha256=XefXqROlDhzw7Z5l_nx6Mhq6n9jjQ-ECGbH0vvhKWYg,272
@@ -98,6 +105,7 @@ plain/signals/__init__.py,sha256=eAs0kLqptuP6I31dWXeAqRNji3svplpAV4Ez6ktjwXM,131
98
105
  plain/signals/dispatch/__init__.py,sha256=FzEygqV9HsM6gopio7O2Oh_X230nA4d5Q9s0sUjMq0E,292
99
106
  plain/signals/dispatch/dispatcher.py,sha256=VxSlqn9PCOTghPPJLOqZPs6FNQZfV2BJpMfFMSg6Dtc,11531
100
107
  plain/signals/dispatch/license.txt,sha256=o9EhDhsC4Q5HbmD-IfNGVTEkXtNE33r5rIt3lleJ8gc,1727
108
+ plain/templates/AGENTS.md,sha256=o6H_qEZ2eY5IrNmSZG7o7ZYfqdD0SkJm_2VPBaaOo5w,59
101
109
  plain/templates/README.md,sha256=QAQxoygpc0CE13fh4eH4ZILwl2xc-oMdGKtiZLLrNCk,2565
102
110
  plain/templates/__init__.py,sha256=bX76FakE9T7mfK3N0deN85HlwHNQpeigytSC9Z8LcOs,451
103
111
  plain/templates/core.py,sha256=mbcH0yTeFOI3XOg9dYSroXRIcdv9sETEy4HzY-ugwco,1258
@@ -149,11 +157,11 @@ plain/views/base.py,sha256=CC9UvMZeAjVvi90vGjoZzsQ0jnhbg3-7qCKQ8-Pb6cg,4184
149
157
  plain/views/errors.py,sha256=jbNCJIzowwCsEvqyJ3opMeZpPDqTyhtrbqb0VnAm2HE,1263
150
158
  plain/views/exceptions.py,sha256=b4euI49ZUKS9O8AGAcFfiDpstzkRAuuj_uYQXzWNHME,138
151
159
  plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
152
- plain/views/objects.py,sha256=YNb8MO1I99HTmQghC5nFk25TQmaB_s45K5yg5BGt4qY,5018
160
+ plain/views/objects.py,sha256=v3Vgvdoc1s0QW6JNWWrO5XXy9zF7vgwndgxX1eOSQoE,4999
153
161
  plain/views/redirect.py,sha256=Xpb3cB7nZYvKgkNqcAxf9Jwm2SWcQ0u2xz4oO5M3vP8,1909
154
162
  plain/views/templates.py,sha256=oAlebEyfES0rzBhfyEJzFmgLkpkbleA6Eip-8zDp-yk,1863
155
- plain-0.60.0.dist-info/METADATA,sha256=w-VNXX3R5YsTPJ_UBnFSzu33jUHnwPHAQdzMJZxILhc,4488
156
- plain-0.60.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
157
- plain-0.60.0.dist-info/entry_points.txt,sha256=nn4uKTRRZuEKOJv3810s3jtSMW0Gew7XDYiKIvBRR6M,93
158
- plain-0.60.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
159
- plain-0.60.0.dist-info/RECORD,,
163
+ plain-0.62.0.dist-info/METADATA,sha256=vjydB1elvpE9PC6UhqU1F8B5eVxzWsoz2lrDJTnEYL0,4488
164
+ plain-0.62.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
165
+ plain-0.62.0.dist-info/entry_points.txt,sha256=nn4uKTRRZuEKOJv3810s3jtSMW0Gew7XDYiKIvBRR6M,93
166
+ plain-0.62.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
167
+ plain-0.62.0.dist-info/RECORD,,
plain/cli/help.py DELETED
@@ -1,27 +0,0 @@
1
- import click
2
- from click.core import Group
3
-
4
-
5
- @click.command("help")
6
- @click.pass_context
7
- def help_cmd(ctx):
8
- """Show help for all commands and subcommands."""
9
-
10
- root = ctx.parent.command
11
- info_name = ctx.parent.info_name or "plain"
12
-
13
- def print_help(cmd, prog, parent=None):
14
- sub_ctx = click.Context(cmd, info_name=prog, parent=parent)
15
-
16
- title = sub_ctx.command_path
17
- click.secho(title, fg="green", bold=True)
18
- click.secho("-" * len(title), fg="green")
19
- click.echo(sub_ctx.get_help())
20
-
21
- if isinstance(cmd, Group):
22
- for name in cmd.list_commands(sub_ctx):
23
- click.echo()
24
- sub_cmd = cmd.get_command(sub_ctx, name)
25
- print_help(sub_cmd, name, sub_ctx)
26
-
27
- print_help(root, info_name)
File without changes