plain 0.29.0__py3-none-any.whl → 0.31.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/assets/urls.py CHANGED
@@ -1,12 +1,11 @@
1
1
  from plain.runtime import settings
2
- from plain.urls import RouterBase, path, register_router, reverse
2
+ from plain.urls import Router, path, reverse
3
3
 
4
4
  from .fingerprints import get_fingerprinted_url_path
5
5
  from .views import AssetView
6
6
 
7
7
 
8
- @register_router
9
- class Router(RouterBase):
8
+ class AssetsRouter(Router):
10
9
  namespace = "assets"
11
10
  urls = [
12
11
  path("<path:path>", AssetView, name="asset"),
@@ -29,4 +28,4 @@ def get_asset_url(url_path):
29
28
  if settings.ASSETS_BASE_URL:
30
29
  return settings.ASSETS_BASE_URL + resolved_url_path
31
30
 
32
- return reverse(Router.namespace + ":asset", path=resolved_url_path)
31
+ return reverse(AssetsRouter.namespace + ":asset", path=resolved_url_path)
plain/assets/views.py CHANGED
@@ -206,9 +206,9 @@ class AssetView(View):
206
206
  # or we're already looking at it.
207
207
  return
208
208
 
209
- from .urls import Router
209
+ from .urls import AssetsRouter
210
210
 
211
- namespace = Router.namespace
211
+ namespace = AssetsRouter.namespace
212
212
 
213
213
  return ResponseRedirect(
214
214
  redirect_to=reverse(f"{namespace}:asset", fingerprinted_url_path),
plain/cli/core.py CHANGED
@@ -379,10 +379,10 @@ def create(package_name):
379
379
  # Create a urls.py file with a default namespace
380
380
  if not (package_dir / "urls.py").exists():
381
381
  (package_dir / "urls.py").write_text(
382
- f"""from plain.urls import path, RouterBase, register_router
382
+ f"""from plain.urls import path, Router
383
383
 
384
- @register_router
385
- class Router(RouterBase):
384
+
385
+ class {package_name.capitalize()}Router(Router):
386
386
  namespace = f"{package_name}"
387
387
  urls = [
388
388
  # path("", views.IndexView, name="index"),
@@ -422,10 +422,15 @@ def generate_secret_key():
422
422
  @plain_cli.command()
423
423
  @click.option("--flat", is_flag=True, help="List all URLs in a flat list")
424
424
  def urls(flat):
425
- """Print all URL patterns under settings.URLS_MODULE"""
425
+ """Print all URL patterns under settings.URLS_ROUTER"""
426
+ from plain.runtime import settings
426
427
  from plain.urls import URLResolver, get_resolver
427
428
 
428
- resolver = get_resolver()
429
+ if not settings.URLS_ROUTER:
430
+ click.secho("URLS_ROUTER is not set", fg="red")
431
+ sys.exit(1)
432
+
433
+ resolver = get_resolver(settings.URLS_ROUTER)
429
434
  if flat:
430
435
 
431
436
  def flat_list(patterns, prefix="", curr_ns=""):
plain/forms/fields.py CHANGED
@@ -43,7 +43,6 @@ __all__ = (
43
43
  "FloatField",
44
44
  "DecimalField",
45
45
  "JSONField",
46
- "SlugField",
47
46
  "TypedChoiceField",
48
47
  "UUIDField",
49
48
  )
@@ -900,16 +899,6 @@ class MultipleChoiceField(ChoiceField):
900
899
  return data.getlist(html_name)
901
900
 
902
901
 
903
- class SlugField(CharField):
904
- default_validators = [validators.validate_slug]
905
-
906
- def __init__(self, *, allow_unicode=False, **kwargs):
907
- self.allow_unicode = allow_unicode
908
- if self.allow_unicode:
909
- self.default_validators = [validators.validate_unicode_slug]
910
- super().__init__(**kwargs)
911
-
912
-
913
902
  class UUIDField(CharField):
914
903
  default_error_messages = {
915
904
  "invalid": "Enter a valid UUID.",
plain/preflight/urls.py CHANGED
@@ -7,11 +7,12 @@ from . import Error, Warning, register
7
7
 
8
8
  @register
9
9
  def check_url_config(package_configs, **kwargs):
10
- if getattr(settings, "URLS_MODULE", None):
10
+ if getattr(settings, "URLS_ROUTER", None):
11
11
  from plain.urls import get_resolver
12
12
 
13
13
  resolver = get_resolver()
14
14
  return check_resolver(resolver)
15
+
15
16
  return []
16
17
 
17
18
 
@@ -33,7 +34,7 @@ def check_url_namespaces_unique(package_configs, **kwargs):
33
34
  """
34
35
  Warn if URL namespaces used in applications aren't unique.
35
36
  """
36
- if not getattr(settings, "URLS_MODULE", None):
37
+ if not getattr(settings, "URLS_ROUTER", None):
37
38
  return []
38
39
 
39
40
  from plain.urls import get_resolver
@@ -74,7 +74,7 @@ SECRET_KEY: str
74
74
  # secret key rotation.
75
75
  SECRET_KEY_FALLBACKS: list[str] = []
76
76
 
77
- URLS_MODULE = "app.urls"
77
+ URLS_ROUTER: str
78
78
 
79
79
  # List of upload handler classes to be applied in order.
80
80
  FILE_UPLOAD_HANDLERS = [
plain/urls/__init__.py CHANGED
@@ -6,7 +6,7 @@ from .resolvers import (
6
6
  URLResolver,
7
7
  get_resolver,
8
8
  )
9
- from .routers import RouterBase, include, path, register_router
9
+ from .routers import Router, include, path
10
10
  from .utils import (
11
11
  reverse,
12
12
  reverse_lazy,
@@ -24,6 +24,5 @@ __all__ = [
24
24
  "register_converter",
25
25
  "reverse",
26
26
  "reverse_lazy",
27
- "RouterBase",
28
- "register_router",
27
+ "Router",
29
28
  ]
plain/urls/resolvers.py CHANGED
@@ -8,7 +8,6 @@ attributes of the resolved URL match.
8
8
 
9
9
  import functools
10
10
  import re
11
- from importlib import import_module
12
11
  from pickle import PicklingError
13
12
  from threading import local
14
13
  from urllib.parse import quote
@@ -17,6 +16,7 @@ from plain.preflight.urls import check_resolver
17
16
  from plain.runtime import settings
18
17
  from plain.utils.datastructures import MultiValueDict
19
18
  from plain.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
19
+ from plain.utils.module_loading import import_string
20
20
  from plain.utils.regex_helper import normalize
21
21
 
22
22
  from .exceptions import NoReverseMatch, Resolver404
@@ -87,30 +87,26 @@ class ResolverMatch:
87
87
  raise PicklingError(f"Cannot pickle {self.__class__.__qualname__}.")
88
88
 
89
89
 
90
- def get_resolver(urls_module=None):
91
- if urls_module is None:
92
- urls_module = settings.URLS_MODULE
90
+ def get_resolver(router=None):
91
+ if router is None:
92
+ router = settings.URLS_ROUTER
93
93
 
94
- return _get_cached_resolver(urls_module)
94
+ return _get_cached_resolver(router)
95
95
 
96
96
 
97
97
  @functools.cache
98
- def _get_cached_resolver(urls_module):
99
- from .routers import routers_registry
98
+ def _get_cached_resolver(router):
99
+ if isinstance(router, str):
100
+ # Do this inside the cached call, primarily for the URLS_ROUTER
101
+ router_class = import_string(router)
102
+ router = router_class()
100
103
 
101
- if isinstance(urls_module, str):
102
- # Need to trigger an import in order for the @register_router
103
- # decorators to run. So this is a sensible entrypoint to do that,
104
- # usually just for the root URLS_MODULE but could be for anything.
105
- urls_module = import_module(urls_module)
106
-
107
- router = routers_registry.get_module_router(urls_module)
108
104
  return URLResolver(pattern=RegexPattern(r"^/"), router=router)
109
105
 
110
106
 
111
107
  @functools.cache
112
108
  def get_ns_resolver(ns_pattern, resolver, converters):
113
- from .routers import RouterBase
109
+ from .routers import Router
114
110
 
115
111
  # Build a namespaced resolver for the given parent urls_module pattern.
116
112
  # This makes it possible to have captured parameters in the parent
@@ -118,13 +114,13 @@ def get_ns_resolver(ns_pattern, resolver, converters):
118
114
  pattern = RegexPattern(ns_pattern)
119
115
  pattern.converters = dict(converters)
120
116
 
121
- class _NestedRouter(RouterBase):
117
+ class _NestedRouter(Router):
122
118
  namespace = ""
123
119
  urls = resolver.url_patterns
124
120
 
125
121
  ns_resolver = URLResolver(pattern=pattern, router=_NestedRouter())
126
122
 
127
- class _NamespacedRouter(RouterBase):
123
+ class _NamespacedRouter(Router):
128
124
  namespace = ""
129
125
  urls = [ns_resolver]
130
126
 
plain/urls/routers.py CHANGED
@@ -1,9 +1,6 @@
1
1
  import re
2
- from types import ModuleType
3
2
  from typing import TYPE_CHECKING
4
3
 
5
- from plain.exceptions import ImproperlyConfigured
6
-
7
4
  from .patterns import RegexPattern, RoutePattern, URLPattern
8
5
  from .resolvers import (
9
6
  URLResolver,
@@ -13,7 +10,7 @@ if TYPE_CHECKING:
13
10
  from plain.views import View
14
11
 
15
12
 
16
- class RouterBase:
13
+ class Router:
17
14
  """
18
15
  Base class for defining url patterns.
19
16
 
@@ -25,37 +22,8 @@ class RouterBase:
25
22
  urls: list
26
23
 
27
24
 
28
- class RoutersRegistry:
29
- """Keep track of all the Routers that are explicitly registered in packages."""
30
-
31
- def __init__(self):
32
- self._routers = {}
33
-
34
- def register_router(self, router_class):
35
- router = (
36
- router_class()
37
- ) # Don't necessarily need to instantiate it yet, but will likely add methods.
38
- router_module_name = router_class.__module__
39
- self._routers[router_module_name] = router
40
- return router
41
-
42
- def get_module_router(self, module):
43
- if isinstance(module, str):
44
- module_name = module
45
- else:
46
- module_name = module.__name__
47
-
48
- try:
49
- return self._routers[module_name]
50
- except KeyError as e:
51
- registered_routers = ", ".join(self._routers.keys()) or "None"
52
- raise ImproperlyConfigured(
53
- f"Router {module_name} is not registered with the resolver. Use @register_router on the Router class in urls.py.\n\nRegistered routers: {registered_routers}"
54
- ) from e
55
-
56
-
57
25
  def include(
58
- route: str | re.Pattern, module_or_urls: list | tuple | str | ModuleType
26
+ route: str | re.Pattern, router_or_urls: list | tuple | str | Router
59
27
  ) -> URLResolver:
60
28
  """
61
29
  Include URLs from another module or a nested list of URL patterns.
@@ -67,23 +35,26 @@ def include(
67
35
  else:
68
36
  raise TypeError("include() route must be a string or regex")
69
37
 
70
- if isinstance(module_or_urls, list | tuple):
38
+ if isinstance(router_or_urls, list | tuple):
71
39
  # We were given an explicit list of sub-patterns,
72
40
  # so we generate a router for it
73
- class _IncludeRouter(RouterBase):
41
+ class _IncludeRouter(Router):
74
42
  namespace = ""
75
- urls = module_or_urls
43
+ urls = router_or_urls
76
44
 
77
45
  return URLResolver(pattern=pattern, router=_IncludeRouter())
78
- else:
79
- # We were given a module, so we need to look up the router for that module
80
- module = module_or_urls
81
- router = routers_registry.get_module_router(module)
46
+ elif isinstance(router_or_urls, type) and issubclass(router_or_urls, Router):
47
+ router_class = router_or_urls
48
+ router = router_class()
82
49
 
83
50
  return URLResolver(
84
51
  pattern=pattern,
85
52
  router=router,
86
53
  )
54
+ else:
55
+ raise TypeError(
56
+ f"include() urls must be a list, tuple, or Router class (not a Router() instance): {router_or_urls}"
57
+ )
87
58
 
88
59
 
89
60
  def path(route: str | re.Pattern, view: "View", *, name: str = "") -> URLPattern:
@@ -116,12 +87,3 @@ def path(route: str | re.Pattern, view: "View", *, name: str = "") -> URLPattern
116
87
  return URLPattern(pattern=pattern, view=view, name=name)
117
88
 
118
89
  raise TypeError("view must be a View class or View.as_view()")
119
-
120
-
121
- routers_registry = RoutersRegistry()
122
-
123
-
124
- def register_router(router_class):
125
- """Decorator to register a router class"""
126
- routers_registry.register_router(router_class)
127
- return router_class # Return the class, not the instance
plain/validators.py CHANGED
@@ -237,21 +237,6 @@ class EmailValidator:
237
237
 
238
238
  validate_email = EmailValidator()
239
239
 
240
- slug_re = _lazy_re_compile(r"^[-a-zA-Z0-9_]+\Z")
241
- validate_slug = RegexValidator(
242
- slug_re,
243
- # Translators: "letters" means latin letters: a-z and A-Z.
244
- "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
245
- "invalid",
246
- )
247
-
248
- slug_unicode_re = _lazy_re_compile(r"^[-\w]+\Z")
249
- validate_unicode_slug = RegexValidator(
250
- slug_unicode_re,
251
- "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens."
252
- "invalid",
253
- )
254
-
255
240
 
256
241
  def validate_ipv4_address(value):
257
242
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.29.0
3
+ Version: 0.31.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
@@ -5,18 +5,18 @@ plain/exceptions.py,sha256=Z9cbPE5im_Y-bjVq8cqC85gBoqOr80rLFG5wTKixrwE,5894
5
5
  plain/json.py,sha256=McJdsbMT1sYwkGRG--f2NSZz0hVXPMix9x3nKaaak2o,1262
6
6
  plain/paginator.py,sha256=-fpLJd6c-V8bLCaNCHfTqPtm-Lm2Y1TuKqFDfy7n3ZE,5857
7
7
  plain/signing.py,sha256=sf7g1Mp-FzdjFAEoLxHyu2YvbUl5w4FOtTVDAfq6TO0,8733
8
- plain/validators.py,sha256=byAdFophb3Mfs09IwqzpIgumQHIil76ZDj2suYNaUNQ,19723
8
+ plain/validators.py,sha256=TePzFHzwR4JXUAZ_Y2vC6mkKgVxHX3QBXI6Oex0rV8c,19236
9
9
  plain/wsgi.py,sha256=R6k5FiAElvGDApEbMPTT0MPqSD7n2e2Az5chQqJZU0I,236
10
10
  plain/assets/README.md,sha256=Ukm_gU7Xj-itAmEjsWUXXDtU5d8BSRpy7ZgGB2LbSo0,2847
11
11
  plain/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  plain/assets/compile.py,sha256=Qg-rMWykij_Jheq4THrPFWlmYv07ihHzWiNsD815HYE,3336
13
13
  plain/assets/finders.py,sha256=rhkHG5QW3H3IlBGHB5WJf9J6VTdDWgUC0qEs6u2Z4RQ,1233
14
14
  plain/assets/fingerprints.py,sha256=1NKAnnXVlncY5iimXztr0NL3RIjBKsNlZRIe6nmItJc,931
15
- plain/assets/urls.py,sha256=lW7VzKNzTKY11JqbszhJQ1Yy0HtljZlsHDnnkTPdLOM,992
16
- plain/assets/views.py,sha256=z7noLzoelGw_8-MXcvGKjXs9KZ43Tivmy2TIfnZIpgw,9253
15
+ plain/assets/urls.py,sha256=3BSvp5B--V6Q03BizmZ6qa2dUamF1r5IdlFbwD401eU,962
16
+ plain/assets/views.py,sha256=s6ERHfgRyoZ8P6mH9ahGZxU1LyN8WByAiJAjlwBCl-k,9265
17
17
  plain/cli/README.md,sha256=t3k4jmSK0QFALO3bVWTUsJC09KhY4CvauStTvVLLUdI,1922
18
18
  plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
19
- plain/cli/core.py,sha256=5_4hsXxzk2ocIez5BBY2YoYtFUdyfKks-2twpqy-zoU,18256
19
+ plain/cli/core.py,sha256=8UelQ8vif6ahUwYnJc7jWy2t7Ml68oyEkt0MtQMycxE,18411
20
20
  plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
21
21
  plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
22
22
  plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
@@ -28,7 +28,7 @@ plain/forms/README.md,sha256=fglB9MmHiEgfGGdZmcRstNl6eYaFljrElu2mzapK52M,377
28
28
  plain/forms/__init__.py,sha256=UxqPwB8CiYPCQdHmUc59jadqaXqDmXBH8y4bt9vTPms,226
29
29
  plain/forms/boundfield.py,sha256=LhydhCVR0okrli0-QBMjGjAJ8-06gTCXVEaBZhBouQk,1741
30
30
  plain/forms/exceptions.py,sha256=XCLDRl5snIEDu5-8mLB0NnU_tegcBfyIHMiJxqvbxnc,164
31
- plain/forms/fields.py,sha256=rsKPsb9cSbvSKhu-eg4yGV1gI-Lv_LaFK_6U22D7oJI,35327
31
+ plain/forms/fields.py,sha256=Fw77LP06aO5h6ZdJmS2S_2On4YSrsl4gu142Y6nGF50,34987
32
32
  plain/forms/forms.py,sha256=fEKBee1b8I_DJ-FufzWJGtSQoUoyieYfqUaGEre9B4Q,10418
33
33
  plain/http/README.md,sha256=HjEtoAhn14OoMdgb-wK-uc8No7C4d4gZUhzseOp7Fg4,236
34
34
  plain/http/__init__.py,sha256=DIsDRbBsCGa4qZgq-fUuQS0kkxfbTU_3KpIM9VvH04w,1067
@@ -69,10 +69,10 @@ plain/preflight/files.py,sha256=wbHCNgps7o1c1zQNBd8FDCaVaqX90UwuvLgEQ_DbUpY,510
69
69
  plain/preflight/messages.py,sha256=HwatjA6MRFfzFAnSOa_uAw1Pvk_CLuNfW3IYi71_1Mk,2322
70
70
  plain/preflight/registry.py,sha256=7s7f_iEwURzv-Ye515P5lJWcHltd5Ca2fsX1Wpbf1wQ,2306
71
71
  plain/preflight/security.py,sha256=sNpv5AHobPcaO48cOUGRNe2EjusTducjY8vyShR8EhI,2645
72
- plain/preflight/urls.py,sha256=OSTLvCpftAD_8VbQ0V3p1CTPlRRwtlnXVBZeWgr7l2k,2881
72
+ plain/preflight/urls.py,sha256=Wlz6n8nTBPzKhDjcT1helwrsLe4PKRlUPLxuxvjuZz4,2882
73
73
  plain/runtime/README.md,sha256=Q8VVO7JRGuYrDxzuYL6ptoilhclbecxKzpRXKgbWGkU,2061
74
74
  plain/runtime/__init__.py,sha256=o2RVETiL8U0lMFBpbtfnxflhw_4MFllMV6CEpX3RqZs,1965
75
- plain/runtime/global_settings.py,sha256=SfOhwzpZe2zpNqSpdx3hHgCN89xdbW9KJVR4KJfS_Gk,5498
75
+ plain/runtime/global_settings.py,sha256=DUx_RPZsyplBV9i8sDy7S1fZdU9LUuxqnXYEBTOMzEI,5490
76
76
  plain/runtime/user_settings.py,sha256=uRHHVfzUvHon91_fOKj7K2WaBYwJ1gCPLfeXqKj5CTs,10902
77
77
  plain/signals/README.md,sha256=cd3tKEgH-xc88CUWyDxl4-qv-HBXx8VT32BXVwA5azA,230
78
78
  plain/signals/__init__.py,sha256=eAs0kLqptuP6I31dWXeAqRNji3svplpAV4Ez6ktjwXM,131
@@ -92,12 +92,12 @@ plain/test/README.md,sha256=Zso3Ir7a8vQerzKB6egjROQWkpveLAbscn7VTROPAiU,37
92
92
  plain/test/__init__.py,sha256=rXe88Y602NP8DBnReSyXb7dUzKoWweLuT43j-qwOUl4,138
93
93
  plain/test/client.py,sha256=36Cir1KbrNEhza-5FTgdwxe1iZ5zaEDtESrqcnyndnY,31318
94
94
  plain/urls/README.md,sha256=pWnCvgYkWN7rG7hSyBOtX4ZUP3iO7FhqM6lvwwYll6c,33
95
- plain/urls/__init__.py,sha256=XF-W2GqLMA4bHbDRKnpZ7tiUtJ-BhWN-yAzw4nNnHdc,590
95
+ plain/urls/__init__.py,sha256=DFO2OL1IllHW5USPIb5uYvvzf_G-Bl0Qu1zrRLHmWyM,542
96
96
  plain/urls/converters.py,sha256=s2JZVOdzZC16lgobsI93hygcdH5L0Kj4742WEkXsVcs,1193
97
97
  plain/urls/exceptions.py,sha256=q4iPh3Aa-zHbA-tw8v6WyX1J1n5WdAady2xvxFuyXB0,114
98
98
  plain/urls/patterns.py,sha256=bU_xfhZbKMSgRG9OJ8w_NSuYRm_9zGnqoz_WY44fhUk,9358
99
- plain/urls/resolvers.py,sha256=3ntfYe2-mQa1tR8jF1-UesnnbZaUp_aVNY3OtiSi5R4,15466
100
- plain/urls/routers.py,sha256=J7v-o4BbTk_iPy_kMP_hOMNOPk-D2lockUmSD0Wx1R0,4056
99
+ plain/urls/resolvers.py,sha256=PyqbO1JIoJq2ayCSmONW_6O8a3vM7cTVbqQJdCJHIK0,15218
100
+ plain/urls/routers.py,sha256=iEsQtTpPNDDVn7r_BQX84FESGSjOeD5qgyO_ep5rzaU,2819
101
101
  plain/urls/utils.py,sha256=WiGq6hHI-5DLFOxCQTAZ2qm0J-UdGosLcjuxlfK6_Tg,2137
102
102
  plain/utils/README.md,sha256=Bf5OG-MkOJDz_U8RGVreDfAI4M4nnPaLtk-LdinxHSc,99
103
103
  plain/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -134,8 +134,8 @@ plain/views/forms.py,sha256=RhlaUcZCkeqokY_fvv-NOS-kgZAG4XhDLOPbf9K_Zlc,2691
134
134
  plain/views/objects.py,sha256=g5Lzno0Zsv0K449UpcCtxwCoO7WMRAWqKlxxV2V0_qg,8263
135
135
  plain/views/redirect.py,sha256=9zHZgKvtSkdrMX9KmsRM8hJTPmBktxhc4d8OitbuniI,1724
136
136
  plain/views/templates.py,sha256=cBkFNCSXgVi8cMqQbhsqJ4M_rIQYVl8cUvq9qu4YIes,1951
137
- plain-0.29.0.dist-info/METADATA,sha256=ONh32xHnWT3cqrGSkuvVsTvkci_KvLkY95bBGf3GW2Y,319
138
- plain-0.29.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
139
- plain-0.29.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
140
- plain-0.29.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
141
- plain-0.29.0.dist-info/RECORD,,
137
+ plain-0.31.0.dist-info/METADATA,sha256=TLCqQ6BvVGA9Wq16yoEQGKF3XtbmNEwFj-iiP7KefKw,319
138
+ plain-0.31.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
139
+ plain-0.31.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
140
+ plain-0.31.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
141
+ plain-0.31.0.dist-info/RECORD,,
File without changes