plain 0.62.1__py3-none-any.whl → 0.64.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # plain changelog
2
2
 
3
+ ## [0.64.0](https://github.com/dropseed/plain/releases/plain@0.64.0) (2025-09-19)
4
+
5
+ ### What's changed
6
+
7
+ - Added `plain-build` command as a standalone executable ([4b39ca4](https://github.com/dropseed/plain/commit/4b39ca4599))
8
+ - Removed `constant_time_compare` utility function in favor of `hmac.compare_digest` ([55f3f55](https://github.com/dropseed/plain/commit/55f3f5596d))
9
+ - CLI now forces colors in CI environments (GitHub Actions, GitLab CI, etc.) for better output visibility ([56f7d2b](https://github.com/dropseed/plain/commit/56f7d2b312))
10
+
11
+ ### Upgrade instructions
12
+
13
+ - Replace any usage of `plain.utils.crypto.constant_time_compare` with `hmac.compare_digest` or `secrets.compare_digest`
14
+
15
+ ## [0.63.0](https://github.com/dropseed/plain/releases/plain@0.63.0) (2025-09-12)
16
+
17
+ ### What's changed
18
+
19
+ - Model manager attribute renamed from `objects` to `query` throughout codebase ([037a239](https://github.com/dropseed/plain/commit/037a239ef4))
20
+ - Simplified HTTPS redirect middleware by removing `HTTPS_REDIRECT_EXEMPT_PATHS` and `HTTPS_REDIRECT_HOST` settings ([d264cd3](https://github.com/dropseed/plain/commit/d264cd306b))
21
+ - Database backups are now created automatically during migrations when `DEBUG=True` unless explicitly disabled ([c802307](https://github.com/dropseed/plain/commit/c8023074e9))
22
+
23
+ ### Upgrade instructions
24
+
25
+ - Remove any `HTTPS_REDIRECT_EXEMPT_PATHS` and `HTTPS_REDIRECT_HOST` settings from your configuration - the HTTPS redirect middleware now performs a blanket redirect. For advanced redirect logic, write custom middleware.
26
+
3
27
  ## [0.62.1](https://github.com/dropseed/plain/releases/plain@0.62.1) (2025-09-09)
4
28
 
5
29
  ### What's changed
@@ -126,7 +150,7 @@
126
150
 
127
151
  - Update your URL patterns from `<int:pk>` to `<int:id>` in your URLconf
128
152
  - Update view code that accesses `self.url_kwargs["pk"]` to use `self.url_kwargs["id"]` instead
129
- - Replace any QuerySet filters using `pk` with `id` (e.g., `Model.objects.get(pk=1)` becomes `Model.objects.get(id=1)`)
153
+ - Replace any QuerySet filters using `pk` with `id` (e.g., `Model.query.get(pk=1)` becomes `Model.query.get(id=1)`)
130
154
 
131
155
  ## [0.54.1](https://github.com/dropseed/plain/releases/plain@0.54.1) (2025-07-20)
132
156
 
plain/chores/README.md CHANGED
@@ -27,7 +27,7 @@ def clear_expired():
27
27
  """
28
28
  Delete sessions that have expired.
29
29
  """
30
- result = Session.objects.filter(expires_at__lt=timezone.now()).delete()
30
+ result = Session.query.filter(expires_at__lt=timezone.now()).delete()
31
31
  return f"{result[0]} expired sessions deleted"
32
32
  ```
33
33
 
@@ -64,7 +64,7 @@ def request(path, method, data, user_id, follow, content_type, headers):
64
64
 
65
65
  # Get the user
66
66
  try:
67
- user = User.objects.get(id=user_id)
67
+ user = User.query.get(id=user_id)
68
68
  client.force_login(user)
69
69
  click.secho(
70
70
  f"Authenticated as user {user_id}", fg="green", dim=True
plain/cli/formatting.py CHANGED
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  import click
2
4
  from click.formatting import iter_rows, measure_table, term_len, wrap_text
3
5
 
@@ -59,3 +61,13 @@ class PlainHelpFormatter(click.HelpFormatter):
59
61
 
60
62
  class PlainContext(click.Context):
61
63
  formatter_class = PlainHelpFormatter
64
+
65
+ def __init__(self, *args, **kwargs):
66
+ super().__init__(*args, **kwargs)
67
+
68
+ # Force colors in CI environments
69
+ if any(
70
+ os.getenv(var)
71
+ for var in ["CI", "FORCE_COLOR", "GITHUB_ACTIONS", "GITLAB_CI"]
72
+ ) and not any(os.getenv(var) for var in ["NO_COLOR", "PYTEST_CURRENT_TEST"]):
73
+ self.color = True
plain/cli/upgrade.py CHANGED
@@ -153,7 +153,6 @@ def build_prompt(before_after: dict[str, tuple[str | None, str | None]]) -> str:
153
153
  " - Process ALL packages before testing or validation",
154
154
  " - After all packages are updated, run `uv run plain fix --unsafe-fixes` and `uv run plain pre-commit` to check results",
155
155
  " - DO NOT commit any changes",
156
- " - DO NOT run `plain migrate` with the `--no-backup` option",
157
156
  " - Keep code changes minimal and focused - avoid unnecessary comments",
158
157
  "",
159
158
  "3. **Available tools:**",
@@ -1,5 +1,3 @@
1
- import re
2
-
3
1
  from plain.http import ResponseRedirect
4
2
  from plain.runtime import settings
5
3
 
@@ -8,16 +6,12 @@ class HttpsRedirectMiddleware:
8
6
  def __init__(self, get_response):
9
7
  self.get_response = get_response
10
8
 
11
- # Settings for https (compile regexes once)
9
+ # Settings for HTTPS
12
10
  self.https_redirect_enabled = settings.HTTPS_REDIRECT_ENABLED
13
- self.https_redirect_host = settings.HTTPS_REDIRECT_HOST
14
- self.https_redirect_exempt = [
15
- re.compile(r) for r in settings.HTTPS_REDIRECT_EXEMPT_PATHS
16
- ]
17
11
 
18
12
  def __call__(self, request):
19
13
  """
20
- Rewrite the URL based on settings.APPEND_SLASH
14
+ Perform a blanket HTTP→HTTPS redirect when enabled.
21
15
  """
22
16
 
23
17
  if redirect_response := self.maybe_https_redirect(request):
@@ -26,15 +20,8 @@ class HttpsRedirectMiddleware:
26
20
  return self.get_response(request)
27
21
 
28
22
  def maybe_https_redirect(self, request):
29
- if (
30
- self.https_redirect_enabled
31
- and not request.is_https()
32
- and not any(
33
- pattern.search(request.path_info)
34
- for pattern in self.https_redirect_exempt
35
- )
36
- ):
37
- host = self.https_redirect_host or request.get_host()
23
+ if self.https_redirect_enabled and not request.is_https():
24
+ host = request.get_host()
38
25
  return ResponseRedirect(
39
26
  f"https://{host}{request.get_full_path()}", status_code=301
40
27
  )
@@ -37,17 +37,11 @@ DEFAULT_RESPONSE_HEADERS = {
37
37
  "X-Frame-Options": "DENY",
38
38
  }
39
39
 
40
- # Whether to redirect all non-HTTPS requests to HTTPS.
40
+ # Whether to redirect all non-HTTPS requests to HTTPS (blanket redirect).
41
+ # For anything more advanced (custom host, path exemptions, etc.), write
42
+ # your own middleware.
41
43
  HTTPS_REDIRECT_ENABLED = True
42
44
 
43
- # Regex patterns for paths that should be exempt from HTTPS redirect
44
- # Examples: [r"^/health$", r"/api/internal/.*", r"/dev/.*"]
45
- HTTPS_REDIRECT_EXEMPT_PATHS: list[str] = []
46
-
47
- # Custom host to redirect to for HTTPS. If None, uses the same host as the request.
48
- # Useful for redirecting to a different domain (e.g., "secure.example.com")
49
- HTTPS_REDIRECT_HOST = None
50
-
51
45
  # If your Plain app is behind a proxy that sets a header to specify secure
52
46
  # connections, AND that proxy ensures that user-submitted headers with the
53
47
  # same name are ignored (so that people can't spoof it), set this value to
plain/signing.py CHANGED
@@ -35,12 +35,14 @@ These functions make use of all of them.
35
35
 
36
36
  import base64
37
37
  import datetime
38
+ import hmac
38
39
  import json
39
40
  import time
40
41
  import zlib
41
42
 
42
43
  from plain.runtime import settings
43
- from plain.utils.crypto import constant_time_compare, salted_hmac
44
+ from plain.utils.crypto import salted_hmac
45
+ from plain.utils.encoding import force_bytes
44
46
  from plain.utils.regex_helper import _lazy_re_compile
45
47
 
46
48
  _SEP_UNSAFE = _lazy_re_compile(r"^[A-z0-9-_=]*$")
@@ -196,7 +198,9 @@ class Signer:
196
198
  raise BadSignature(f'No "{self.sep}" found in value')
197
199
  value, sig = signed_value.rsplit(self.sep, 1)
198
200
  for key in [self.key, *self.fallback_keys]:
199
- if constant_time_compare(sig, self.signature(value, key)):
201
+ if hmac.compare_digest(
202
+ force_bytes(sig), force_bytes(self.signature(value, key))
203
+ ):
200
204
  return value
201
205
  raise BadSignature(f'Signature "{sig}" does not match')
202
206
 
plain/test/README.md CHANGED
@@ -24,7 +24,7 @@ def test_client_example():
24
24
  assert client.session["example"] == "value"
25
25
 
26
26
  # Logging in
27
- user = User.objects.first()
27
+ user = User.query.first()
28
28
  client.force_login(user)
29
29
  response = client.get("/protected/")
30
30
  assert response.status_code == 200
plain/utils/crypto.py CHANGED
@@ -62,11 +62,6 @@ def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS):
62
62
  return "".join(secrets.choice(allowed_chars) for i in range(length))
63
63
 
64
64
 
65
- def constant_time_compare(val1, val2):
66
- """Return True if the two strings are equal, False otherwise."""
67
- return secrets.compare_digest(force_bytes(val1), force_bytes(val2))
68
-
69
-
70
65
  def pbkdf2(password, salt, iterations, dklen=0, digest=None):
71
66
  """Return the hash of password using pbkdf2."""
72
67
  if digest is None:
plain/views/README.md CHANGED
@@ -179,7 +179,7 @@ class ExampleDetailView(DetailView):
179
179
  template_name = "detail.html"
180
180
 
181
181
  def get_object(self):
182
- return MyObjectClass.objects.get(
182
+ return MyObjectClass.query.get(
183
183
  id=self.url_kwargs["id"],
184
184
  user=self.request.user, # Limit access
185
185
  )
@@ -197,7 +197,7 @@ class ExampleUpdateView(UpdateView):
197
197
  success_url = "."
198
198
 
199
199
  def get_object(self):
200
- return MyObjectClass.objects.get(
200
+ return MyObjectClass.query.get(
201
201
  id=self.url_kwargs["id"],
202
202
  user=self.request.user, # Limit access
203
203
  )
@@ -211,7 +211,7 @@ class ExampleDeleteView(DeleteView):
211
211
  # Just POST to this view to delete the object.
212
212
 
213
213
  def get_object(self):
214
- return MyObjectClass.objects.get(
214
+ return MyObjectClass.query.get(
215
215
  id=self.url_kwargs["id"],
216
216
  user=self.request.user, # Limit access
217
217
  )
@@ -221,7 +221,7 @@ class ExampleListView(ListView):
221
221
  template_name = "list.html"
222
222
 
223
223
  def get_objects(self):
224
- return MyObjectClass.objects.filter(
224
+ return MyObjectClass.query.filter(
225
225
  user=self.request.user, # Limit access
226
226
  )
227
227
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.62.1
3
+ Version: 0.64.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
@@ -1,12 +1,12 @@
1
1
  plain/AGENTS.md,sha256=5XMGBpJgbCNIpp60DPXB7bpAtFk8FAzqiZke95T965o,1038
2
- plain/CHANGELOG.md,sha256=Fg4keirx_FEqTRzZxPZS4twy3lOjZ1OPpv6JHGHwi04,12970
2
+ plain/CHANGELOG.md,sha256=8EVqRYBSknRtM-becwiafkwoLHz3z2erO8dyULfVKfY,14516
3
3
  plain/README.md,sha256=5BJyKhf0TDanWVbOQyZ3zsi5Lov9xk-LlJYCDWofM6Y,4078
4
4
  plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
5
5
  plain/debug.py,sha256=XdjnXcbPGsi0J2SpHGaLthhYU5AjhBlkHdemaP4sbYY,758
6
6
  plain/exceptions.py,sha256=ljOLqgVwPKlGWqaNmjHcHQf6y053bZ9ogkLFEGcs-Gg,5973
7
7
  plain/json.py,sha256=McJdsbMT1sYwkGRG--f2NSZz0hVXPMix9x3nKaaak2o,1262
8
8
  plain/paginator.py,sha256=iXiOyt2r_YwNrkqCRlaU7V-M_BKaaQ8XZElUBVa6yeU,5844
9
- plain/signing.py,sha256=r2KvCOxkrSWCULFxYa9BHYx3L3a2oLq8RDnq_92inTw,8207
9
+ plain/signing.py,sha256=i8Bf12c96u_1BZYjETiixhsLAWMAt_y4CIYZOsI6IVA,8295
10
10
  plain/validators.py,sha256=TePzFHzwR4JXUAZ_Y2vC6mkKgVxHX3QBXI6Oex0rV8c,19236
11
11
  plain/wsgi.py,sha256=R6k5FiAElvGDApEbMPTT0MPqSD7n2e2Az5chQqJZU0I,236
12
12
  plain/assets/README.md,sha256=iRZHHoXZCEFooXoDYSomF12XTcDFl7oNbRHpAFwxTM8,4349
@@ -16,7 +16,7 @@ plain/assets/finders.py,sha256=2k8QZAbfUbc1LykxbzdazTSB6xNxJZnsZaGhWbSFZZs,1452
16
16
  plain/assets/fingerprints.py,sha256=D-x0o49sUuWfx86BssDyo87yJCjWrV0go6JGPagvMAE,1365
17
17
  plain/assets/urls.py,sha256=zQUA8bAlh9qVqskPJJrqWd9DjvetOi5jPSqy4vUX0J4,1161
18
18
  plain/assets/views.py,sha256=T_0Qh6v9qBerEBYbhToigwOzsij-x1z_R-1zETQcIh0,9447
19
- plain/chores/README.md,sha256=5tIKRLg2I4P-jnfoizRn4fxtqQrjFEimkJ2ckddiGRQ,2056
19
+ plain/chores/README.md,sha256=xTYKOBnfcLBpVeZgJQ6c35vdxHYaKkfJB8B-6sB3lcs,2054
20
20
  plain/chores/__init__.py,sha256=r9TXtQCH-VbvfnIJ5F8FxgQC35GRWFOfmMZN3q9niLg,67
21
21
  plain/chores/registry.py,sha256=V3WjuekRI22LFvJbqSkUXQtiOtuE2ZK8gKV1TRvxRUI,1866
22
22
  plain/cli/README.md,sha256=5C7vsH0ISxu7q5H6buC25MBOILkI_rzdySitswpQgJw,1032
@@ -26,7 +26,7 @@ plain/cli/changelog.py,sha256=j-k1yZk9mpm-fLZgeWastiyIisxNSuAJfXTQ2B6WQmk,3457
26
26
  plain/cli/chores.py,sha256=xXSSFvr8T5jWfLWqe6E8YVMw1BkQxyOHHVuY0x9RH0A,2412
27
27
  plain/cli/core.py,sha256=g0D1OZkYGWt05-V1oDNzX2wcoCIAxrZjlgaQs2qWLlc,3106
28
28
  plain/cli/docs.py,sha256=YEEE-Th1CSxiL-wj5fF-ZagqkqAZYkEPRMO1OYUsQrU,1066
29
- plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
29
+ plain/cli/formatting.py,sha256=JaOiIOxIQpCRgMNGw9xpnU_fMf6hROl7o8lA5aX114w,2580
30
30
  plain/cli/install.py,sha256=mffSYBmSJSj44OPBfu53nBQoyoz4jk69DvppubIB0mU,2791
31
31
  plain/cli/output.py,sha256=Fe3xS6Va4Bi1ZNrqi0nh09THTsdCyMW2b9SPY5I4n-o,1318
32
32
  plain/cli/preflight.py,sha256=8tHBD4L4nPLUKThfaYx3SUZSJzC48oV2m_Hbn6W4ODc,4124
@@ -36,7 +36,7 @@ plain/cli/scaffold.py,sha256=mcywA9DzfwoBSqWl5-Zpgcy1mTNUGEgdvoxXUrGcEVk,1351
36
36
  plain/cli/settings.py,sha256=9cx4bue664I2P7kUedlf4YhCPB0tSKSE4Q8mGyzEv2o,1995
37
37
  plain/cli/shell.py,sha256=PMHdwcRv48qXDToeq82aZaNth-cKc3V2pQ1yISrNMvY,1802
38
38
  plain/cli/startup.py,sha256=wLaFuyUb4ewWhtehBCGicrRCXIIGCRbeCT3ce9hUv-A,1022
39
- plain/cli/upgrade.py,sha256=T8u81rA2_dSfJaK4vF1_OPkQpspBVWnlPxatyk_mdx0,5632
39
+ plain/cli/upgrade.py,sha256=0k33jNKvF_FSBcWNq_yEUgidGWrWlOURNfV8EaGE75U,5555
40
40
  plain/cli/urls.py,sha256=ghCW36aRszxmTo06A50FIvYopb6kQ07QekkDzM6_A1o,3824
41
41
  plain/cli/utils.py,sha256=VwlIh0z7XxzVV8I3qM2kZo07fkJFPoeeVZa1ODG616k,258
42
42
  plain/cli/agent/__init__.py,sha256=Ipp65kuIF14TVxNqsj71MsWUePaKHUcdP3QmaYyNcg0,480
@@ -44,7 +44,7 @@ plain/cli/agent/docs.py,sha256=ubX3ZeRHxVaetLk9fjiN9mJ07GZExC-CHUvQoX2DD7c,2464
44
44
  plain/cli/agent/llmdocs.py,sha256=AUpNDb1xSOsSpzGOiFvpzUe4f7PUGMiR9cI13aVZouo,5038
45
45
  plain/cli/agent/md.py,sha256=7r1II8ckubBFOZNGPASWaPmJdgByWFPINLqIOzRetLQ,2581
46
46
  plain/cli/agent/prompt.py,sha256=rugYyQHV7JDNqGrx3_PPShwwqYlnEVbxw8RsczOo8tg,1253
47
- plain/cli/agent/request.py,sha256=JILrcxEMPagBXWrjNGMy3qatCYCXw-_uJMKkVHk_bho,6549
47
+ plain/cli/agent/request.py,sha256=U4acLmpXlbFRjivrPtVv_r8DBts8OXg3m3-qotQxGL4,6547
48
48
  plain/csrf/README.md,sha256=ApWpB-qlEf0LkOKm9Yr-6f_lB9XJEvGFDo_fraw8ghI,2391
49
49
  plain/csrf/middleware.py,sha256=n5_7v6qwFKgiAnKVyJa7RhwHoWepLkPudzIgZtdku5A,5119
50
50
  plain/csrf/views.py,sha256=HwQqfI6KPelHP9gSXhjfZaTLQic71PKsoZ6DPhr1rKI,572
@@ -75,7 +75,7 @@ plain/internal/handlers/exception.py,sha256=TbPYtgZ7ITJahUKhQWkptHK28Lb4zh_nOviN
75
75
  plain/internal/handlers/wsgi.py,sha256=dgPT29t_F9llB-c5RYU3SHxGuZNaZ83xRjOfuOmtOl8,8209
76
76
  plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  plain/internal/middleware/headers.py,sha256=ENIW1Gwat54hv-ejgp2R8QTZm-PlaI7k44WU01YQrNk,964
78
- plain/internal/middleware/https.py,sha256=mS1YejfLB_5qlAoMfanh8Wn2O-RdSpOBdhvw2DRcTHs,1257
78
+ plain/internal/middleware/https.py,sha256=ctW90yJnn-blMb1lv17IsSWlAThHJ4RDku5XFfFNECM,834
79
79
  plain/internal/middleware/slash.py,sha256=JWcIfGbXEKH00I7STq1AMdHhFGmQHC8lkKENa6280ko,2846
80
80
  plain/logs/README.md,sha256=rzOHfngjizLgXL21g0svC1Cdya2s_gBA_E-IljtHpy8,4069
81
81
  plain/logs/__init__.py,sha256=gFVMcNn5D6z0JrvUJgGsOeYj1NKNtEXhw0MvPDtkN6w,58
@@ -97,7 +97,7 @@ plain/preflight/security.py,sha256=oxUZBp2M0bpBfUoLYepIxoex2Y90nyjlrL8XU8UTHYY,2
97
97
  plain/preflight/urls.py,sha256=cQ-WnFa_5oztpKdtwhuIGb7pXEml__bHsjs1SWO2YNI,1468
98
98
  plain/runtime/README.md,sha256=sTqXXJkckwqkk9O06XMMSNRokAYjrZBnB50JD36BsYI,4873
99
99
  plain/runtime/__init__.py,sha256=byFYnHrpUCwkpkHtdNhxr9iUdLDCWJjy92HPj30Ilck,2478
100
- plain/runtime/global_settings.py,sha256=cDhsZOh0FemxUQE41viBjoMOriXV9_JnbSu28Kon_uI,6014
100
+ plain/runtime/global_settings.py,sha256=PjgrsTQc3aQ0YxbZ43Lj2eNrOcP6hf4jBjjQ2lT0MfE,5767
101
101
  plain/runtime/user_settings.py,sha256=OzMiEkE6ZQ50nxd1WIqirXPiNuMAQULklYHEzgzLWgA,11027
102
102
  plain/runtime/utils.py,sha256=p5IuNTzc7Kq-9Ym7etYnt_xqHw5TioxfSkFeq1bKdgk,832
103
103
  plain/signals/README.md,sha256=XefXqROlDhzw7Z5l_nx6Mhq6n9jjQ-ECGbH0vvhKWYg,272
@@ -114,7 +114,7 @@ plain/templates/jinja/environments.py,sha256=9plifzvQj--aTN1cCpJ2WdzQxZJpzB8S_4h
114
114
  plain/templates/jinja/extensions.py,sha256=AEmmmHDbdRW8fhjYDzq9eSSNbp9WHsXenD8tPthjc0s,1351
115
115
  plain/templates/jinja/filters.py,sha256=ft5XUr4OLeQayn-MSxrycpFLyyN_yEo7j5WhWMwpTOs,1445
116
116
  plain/templates/jinja/globals.py,sha256=VMpuMZvwWOmb5MbzKK-ox-QEX_WSsXFxq0mm8biJgaU,558
117
- plain/test/README.md,sha256=wem0myAd-ij74nNV1MB9g4iH6FZUgii7-_euRq45oHs,1142
117
+ plain/test/README.md,sha256=tNzaVjma0sgowIrViJguCgVy8A2d8mUKApZO2RxTYyU,1140
118
118
  plain/test/__init__.py,sha256=MhNHtp7MYBl9kq-pMRGY11kJ6kU1I6vOkjNkit1TYRg,94
119
119
  plain/test/client.py,sha256=OcL8wQDOu6PUNYvwcmslT5IGt81ffcPsXn05n-2n9oA,25128
120
120
  plain/test/encoding.py,sha256=YJBOIE-BQRA5yl4zHnQy-9l67mJDTFmfy1DQXK0Wk-Q,3199
@@ -130,7 +130,7 @@ plain/urls/utils.py,sha256=lKxTX_A3XJpIH7FjlNYju108stY6-8Sw2uVdiSsxOKQ,1249
130
130
  plain/utils/README.md,sha256=3RTH9t4SBLkYLMlnhpWwp72IjgQhOjYYxpT1gHwRUdE,232
131
131
  plain/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
132
132
  plain/utils/cache.py,sha256=iuvOTIfI1s857iVOAPNLK5lkzlrl0fIiBYaiUXWQu40,5303
133
- plain/utils/crypto.py,sha256=zFDydnaqNMGYFHUc-CAn8f93685a17BhGusAcITH1lI,2662
133
+ plain/utils/crypto.py,sha256=kONJB9aFFDHnbFixfigJkzaU-qBAtktCEzbhoHdodoA,2480
134
134
  plain/utils/datastructures.py,sha256=g4UYTbxIb_n8F9JWMP4dHPwUz71591fHreGATPO4qEc,10240
135
135
  plain/utils/dateparse.py,sha256=u9_tF85YteXSjW9KQzNg_pcCEFDZS3EGorCddcWU0vE,5351
136
136
  plain/utils/deconstruct.py,sha256=7NwEFIDCiadAArUBFmiErzDgfIgDWeKqqQFDXwSgQoQ,1830
@@ -151,7 +151,7 @@ plain/utils/text.py,sha256=42hJv06sadbWfsaAHNhqCQaP1W9qZ69trWDTS-Xva7k,9496
151
151
  plain/utils/timesince.py,sha256=a_-ZoPK_s3Pt998CW4rWp0clZ1XyK2x04hCqak2giII,5928
152
152
  plain/utils/timezone.py,sha256=6u0sE-9RVp0_OCe0Y1KiYYQoq5THWLokZFQYY8jf78g,6221
153
153
  plain/utils/tree.py,sha256=wdWzmfsgc26YDF2wxhAY3yVxXTixQYqYDKE9mL3L3ZY,4383
154
- plain/views/README.md,sha256=JmRGCQJgSr17uzUfsJfZbix1md3Qj6mmCkzWuoTFurQ,7223
154
+ plain/views/README.md,sha256=caUSKUhCSs5hdxHC5wIVzKkumPXiuNoOFRnIs3CUHfo,7215
155
155
  plain/views/__init__.py,sha256=a-N1nkklVohJTtz0yD1MMaS0g66HviEjsKydNVVjvVc,392
156
156
  plain/views/base.py,sha256=CC9UvMZeAjVvi90vGjoZzsQ0jnhbg3-7qCKQ8-Pb6cg,4184
157
157
  plain/views/errors.py,sha256=jbNCJIzowwCsEvqyJ3opMeZpPDqTyhtrbqb0VnAm2HE,1263
@@ -160,8 +160,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
160
160
  plain/views/objects.py,sha256=v3Vgvdoc1s0QW6JNWWrO5XXy9zF7vgwndgxX1eOSQoE,4999
161
161
  plain/views/redirect.py,sha256=Xpb3cB7nZYvKgkNqcAxf9Jwm2SWcQ0u2xz4oO5M3vP8,1909
162
162
  plain/views/templates.py,sha256=oAlebEyfES0rzBhfyEJzFmgLkpkbleA6Eip-8zDp-yk,1863
163
- plain-0.62.1.dist-info/METADATA,sha256=QNtr7cT8EyLNsOz00vcu8WDd_j-DNmS8AM2BkgKzdM8,4488
164
- plain-0.62.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
165
- plain-0.62.1.dist-info/entry_points.txt,sha256=nn4uKTRRZuEKOJv3810s3jtSMW0Gew7XDYiKIvBRR6M,93
166
- plain-0.62.1.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
167
- plain-0.62.1.dist-info/RECORD,,
163
+ plain-0.64.0.dist-info/METADATA,sha256=rXEqF5LoYBy2GzUEq2vrrhCDIG_Xn3VmyXAHgd4OdY4,4488
164
+ plain-0.64.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
165
+ plain-0.64.0.dist-info/entry_points.txt,sha256=iGx7EijzXy87htbSv90RhtAcjhSTH_kvE8aeRCn1TRA,129
166
+ plain-0.64.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
167
+ plain-0.64.0.dist-info/RECORD,,
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  plain = plain.cli.core:cli
3
+ plain-build = plain.cli.build:build
3
4
  plain-changelog = plain.cli.changelog:changelog
File without changes