plain 0.12.1__tar.gz → 0.13.1__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 (148) hide show
  1. {plain-0.12.1 → plain-0.13.1}/PKG-INFO +1 -1
  2. {plain-0.12.1 → plain-0.13.1}/plain/cli/cli.py +5 -14
  3. {plain-0.12.1 → plain-0.13.1}/plain/debug.py +8 -6
  4. {plain-0.12.1 → plain-0.13.1}/plain/runtime/global_settings.py +1 -2
  5. plain-0.13.1/plain/templates/__init__.py +17 -0
  6. {plain-0.12.1 → plain-0.13.1}/plain/templates/jinja/README.md +3 -17
  7. plain-0.13.1/plain/templates/jinja/__init__.py +88 -0
  8. plain-0.13.1/plain/templates/jinja/environments.py +63 -0
  9. {plain-0.12.1 → plain-0.13.1}/pyproject.toml +1 -1
  10. plain-0.12.1/plain/templates/__init__.py +0 -6
  11. plain-0.12.1/plain/templates/jinja/__init__.py +0 -22
  12. plain-0.12.1/plain/templates/jinja/defaults.py +0 -119
  13. {plain-0.12.1 → plain-0.13.1}/LICENSE +0 -0
  14. {plain-0.12.1 → plain-0.13.1}/README.md +0 -0
  15. {plain-0.12.1 → plain-0.13.1}/plain/README.md +0 -0
  16. {plain-0.12.1 → plain-0.13.1}/plain/__main__.py +0 -0
  17. {plain-0.12.1 → plain-0.13.1}/plain/assets/README.md +0 -0
  18. {plain-0.12.1 → plain-0.13.1}/plain/assets/__init__.py +0 -0
  19. {plain-0.12.1 → plain-0.13.1}/plain/assets/compile.py +0 -0
  20. {plain-0.12.1 → plain-0.13.1}/plain/assets/finders.py +0 -0
  21. {plain-0.12.1 → plain-0.13.1}/plain/assets/fingerprints.py +0 -0
  22. {plain-0.12.1 → plain-0.13.1}/plain/assets/urls.py +0 -0
  23. {plain-0.12.1 → plain-0.13.1}/plain/assets/views.py +0 -0
  24. {plain-0.12.1 → plain-0.13.1}/plain/cli/README.md +0 -0
  25. {plain-0.12.1 → plain-0.13.1}/plain/cli/__init__.py +0 -0
  26. {plain-0.12.1 → plain-0.13.1}/plain/cli/formatting.py +0 -0
  27. {plain-0.12.1 → plain-0.13.1}/plain/cli/packages.py +0 -0
  28. {plain-0.12.1 → plain-0.13.1}/plain/cli/print.py +0 -0
  29. {plain-0.12.1 → plain-0.13.1}/plain/cli/startup.py +0 -0
  30. {plain-0.12.1 → plain-0.13.1}/plain/csrf/README.md +0 -0
  31. {plain-0.12.1 → plain-0.13.1}/plain/csrf/middleware.py +0 -0
  32. {plain-0.12.1 → plain-0.13.1}/plain/csrf/views.py +0 -0
  33. {plain-0.12.1 → plain-0.13.1}/plain/exceptions.py +0 -0
  34. {plain-0.12.1 → plain-0.13.1}/plain/forms/README.md +0 -0
  35. {plain-0.12.1 → plain-0.13.1}/plain/forms/__init__.py +0 -0
  36. {plain-0.12.1 → plain-0.13.1}/plain/forms/boundfield.py +0 -0
  37. {plain-0.12.1 → plain-0.13.1}/plain/forms/exceptions.py +0 -0
  38. {plain-0.12.1 → plain-0.13.1}/plain/forms/fields.py +0 -0
  39. {plain-0.12.1 → plain-0.13.1}/plain/forms/forms.py +0 -0
  40. {plain-0.12.1 → plain-0.13.1}/plain/http/README.md +0 -0
  41. {plain-0.12.1 → plain-0.13.1}/plain/http/__init__.py +0 -0
  42. {plain-0.12.1 → plain-0.13.1}/plain/http/cookie.py +0 -0
  43. {plain-0.12.1 → plain-0.13.1}/plain/http/multipartparser.py +0 -0
  44. {plain-0.12.1 → plain-0.13.1}/plain/http/request.py +0 -0
  45. {plain-0.12.1 → plain-0.13.1}/plain/http/response.py +0 -0
  46. {plain-0.12.1 → plain-0.13.1}/plain/internal/__init__.py +0 -0
  47. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/README.md +0 -0
  48. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/__init__.py +0 -0
  49. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/base.py +0 -0
  50. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/locks.py +0 -0
  51. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/move.py +0 -0
  52. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/temp.py +0 -0
  53. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/uploadedfile.py +0 -0
  54. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/uploadhandler.py +0 -0
  55. {plain-0.12.1 → plain-0.13.1}/plain/internal/files/utils.py +0 -0
  56. {plain-0.12.1 → plain-0.13.1}/plain/internal/handlers/__init__.py +0 -0
  57. {plain-0.12.1 → plain-0.13.1}/plain/internal/handlers/base.py +0 -0
  58. {plain-0.12.1 → plain-0.13.1}/plain/internal/handlers/exception.py +0 -0
  59. {plain-0.12.1 → plain-0.13.1}/plain/internal/handlers/wsgi.py +0 -0
  60. {plain-0.12.1 → plain-0.13.1}/plain/internal/middleware/__init__.py +0 -0
  61. {plain-0.12.1 → plain-0.13.1}/plain/internal/middleware/headers.py +0 -0
  62. {plain-0.12.1 → plain-0.13.1}/plain/internal/middleware/https.py +0 -0
  63. {plain-0.12.1 → plain-0.13.1}/plain/internal/middleware/slash.py +0 -0
  64. {plain-0.12.1 → plain-0.13.1}/plain/json.py +0 -0
  65. {plain-0.12.1 → plain-0.13.1}/plain/logs/README.md +0 -0
  66. {plain-0.12.1 → plain-0.13.1}/plain/logs/__init__.py +0 -0
  67. {plain-0.12.1 → plain-0.13.1}/plain/logs/configure.py +0 -0
  68. {plain-0.12.1 → plain-0.13.1}/plain/logs/loggers.py +0 -0
  69. {plain-0.12.1 → plain-0.13.1}/plain/logs/utils.py +0 -0
  70. {plain-0.12.1 → plain-0.13.1}/plain/packages/README.md +0 -0
  71. {plain-0.12.1 → plain-0.13.1}/plain/packages/__init__.py +0 -0
  72. {plain-0.12.1 → plain-0.13.1}/plain/packages/config.py +0 -0
  73. {plain-0.12.1 → plain-0.13.1}/plain/packages/registry.py +0 -0
  74. {plain-0.12.1 → plain-0.13.1}/plain/paginator.py +0 -0
  75. {plain-0.12.1 → plain-0.13.1}/plain/preflight/README.md +0 -0
  76. {plain-0.12.1 → plain-0.13.1}/plain/preflight/__init__.py +0 -0
  77. {plain-0.12.1 → plain-0.13.1}/plain/preflight/files.py +0 -0
  78. {plain-0.12.1 → plain-0.13.1}/plain/preflight/messages.py +0 -0
  79. {plain-0.12.1 → plain-0.13.1}/plain/preflight/registry.py +0 -0
  80. {plain-0.12.1 → plain-0.13.1}/plain/preflight/security.py +0 -0
  81. {plain-0.12.1 → plain-0.13.1}/plain/preflight/urls.py +0 -0
  82. {plain-0.12.1 → plain-0.13.1}/plain/runtime/README.md +0 -0
  83. {plain-0.12.1 → plain-0.13.1}/plain/runtime/__init__.py +0 -0
  84. {plain-0.12.1 → plain-0.13.1}/plain/runtime/user_settings.py +0 -0
  85. {plain-0.12.1 → plain-0.13.1}/plain/signals/README.md +0 -0
  86. {plain-0.12.1 → plain-0.13.1}/plain/signals/__init__.py +0 -0
  87. {plain-0.12.1 → plain-0.13.1}/plain/signals/dispatch/__init__.py +0 -0
  88. {plain-0.12.1 → plain-0.13.1}/plain/signals/dispatch/dispatcher.py +0 -0
  89. {plain-0.12.1 → plain-0.13.1}/plain/signals/dispatch/license.txt +0 -0
  90. {plain-0.12.1 → plain-0.13.1}/plain/signing.py +0 -0
  91. {plain-0.12.1 → plain-0.13.1}/plain/templates/README.md +0 -0
  92. {plain-0.12.1 → plain-0.13.1}/plain/templates/core.py +0 -0
  93. {plain-0.12.1 → plain-0.13.1}/plain/templates/jinja/extensions.py +0 -0
  94. {plain-0.12.1 → plain-0.13.1}/plain/templates/jinja/filters.py +0 -0
  95. {plain-0.12.1 → plain-0.13.1}/plain/templates/jinja/globals.py +0 -0
  96. {plain-0.12.1 → plain-0.13.1}/plain/test/README.md +0 -0
  97. {plain-0.12.1 → plain-0.13.1}/plain/test/__init__.py +0 -0
  98. {plain-0.12.1 → plain-0.13.1}/plain/test/client.py +0 -0
  99. {plain-0.12.1 → plain-0.13.1}/plain/urls/README.md +0 -0
  100. {plain-0.12.1 → plain-0.13.1}/plain/urls/__init__.py +0 -0
  101. {plain-0.12.1 → plain-0.13.1}/plain/urls/base.py +0 -0
  102. {plain-0.12.1 → plain-0.13.1}/plain/urls/conf.py +0 -0
  103. {plain-0.12.1 → plain-0.13.1}/plain/urls/converters.py +0 -0
  104. {plain-0.12.1 → plain-0.13.1}/plain/urls/exceptions.py +0 -0
  105. {plain-0.12.1 → plain-0.13.1}/plain/urls/resolvers.py +0 -0
  106. {plain-0.12.1 → plain-0.13.1}/plain/utils/README.md +0 -0
  107. {plain-0.12.1 → plain-0.13.1}/plain/utils/__init__.py +0 -0
  108. {plain-0.12.1 → plain-0.13.1}/plain/utils/_os.py +0 -0
  109. {plain-0.12.1 → plain-0.13.1}/plain/utils/cache.py +0 -0
  110. {plain-0.12.1 → plain-0.13.1}/plain/utils/connection.py +0 -0
  111. {plain-0.12.1 → plain-0.13.1}/plain/utils/crypto.py +0 -0
  112. {plain-0.12.1 → plain-0.13.1}/plain/utils/datastructures.py +0 -0
  113. {plain-0.12.1 → plain-0.13.1}/plain/utils/dateformat.py +0 -0
  114. {plain-0.12.1 → plain-0.13.1}/plain/utils/dateparse.py +0 -0
  115. {plain-0.12.1 → plain-0.13.1}/plain/utils/dates.py +0 -0
  116. {plain-0.12.1 → plain-0.13.1}/plain/utils/deconstruct.py +0 -0
  117. {plain-0.12.1 → plain-0.13.1}/plain/utils/decorators.py +0 -0
  118. {plain-0.12.1 → plain-0.13.1}/plain/utils/deprecation.py +0 -0
  119. {plain-0.12.1 → plain-0.13.1}/plain/utils/duration.py +0 -0
  120. {plain-0.12.1 → plain-0.13.1}/plain/utils/email.py +0 -0
  121. {plain-0.12.1 → plain-0.13.1}/plain/utils/encoding.py +0 -0
  122. {plain-0.12.1 → plain-0.13.1}/plain/utils/functional.py +0 -0
  123. {plain-0.12.1 → plain-0.13.1}/plain/utils/hashable.py +0 -0
  124. {plain-0.12.1 → plain-0.13.1}/plain/utils/html.py +0 -0
  125. {plain-0.12.1 → plain-0.13.1}/plain/utils/http.py +0 -0
  126. {plain-0.12.1 → plain-0.13.1}/plain/utils/inspect.py +0 -0
  127. {plain-0.12.1 → plain-0.13.1}/plain/utils/ipv6.py +0 -0
  128. {plain-0.12.1 → plain-0.13.1}/plain/utils/itercompat.py +0 -0
  129. {plain-0.12.1 → plain-0.13.1}/plain/utils/module_loading.py +0 -0
  130. {plain-0.12.1 → plain-0.13.1}/plain/utils/regex_helper.py +0 -0
  131. {plain-0.12.1 → plain-0.13.1}/plain/utils/safestring.py +0 -0
  132. {plain-0.12.1 → plain-0.13.1}/plain/utils/termcolors.py +0 -0
  133. {plain-0.12.1 → plain-0.13.1}/plain/utils/text.py +0 -0
  134. {plain-0.12.1 → plain-0.13.1}/plain/utils/timesince.py +0 -0
  135. {plain-0.12.1 → plain-0.13.1}/plain/utils/timezone.py +0 -0
  136. {plain-0.12.1 → plain-0.13.1}/plain/utils/tree.py +0 -0
  137. {plain-0.12.1 → plain-0.13.1}/plain/validators.py +0 -0
  138. {plain-0.12.1 → plain-0.13.1}/plain/views/README.md +0 -0
  139. {plain-0.12.1 → plain-0.13.1}/plain/views/__init__.py +0 -0
  140. {plain-0.12.1 → plain-0.13.1}/plain/views/base.py +0 -0
  141. {plain-0.12.1 → plain-0.13.1}/plain/views/csrf.py +0 -0
  142. {plain-0.12.1 → plain-0.13.1}/plain/views/errors.py +0 -0
  143. {plain-0.12.1 → plain-0.13.1}/plain/views/exceptions.py +0 -0
  144. {plain-0.12.1 → plain-0.13.1}/plain/views/forms.py +0 -0
  145. {plain-0.12.1 → plain-0.13.1}/plain/views/objects.py +0 -0
  146. {plain-0.12.1 → plain-0.13.1}/plain/views/redirect.py +0 -0
  147. {plain-0.12.1 → plain-0.13.1}/plain/views/templates.py +0 -0
  148. {plain-0.12.1 → plain-0.13.1}/plain/wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plain
3
- Version: 0.12.1
3
+ Version: 0.13.1
4
4
  Summary: A web framework for building products with Python.
5
5
  Author: Dave Gaeddert
6
6
  Author-email: dave.gaeddert@dropseed.dev
@@ -446,8 +446,6 @@ class PlainCommandCollection(click.CommandCollection):
446
446
  sources = []
447
447
 
448
448
  try:
449
- # Setup has to run before the installed packages CLI work
450
- # and it also does the .env file loading right now...
451
449
  plain.runtime.setup()
452
450
 
453
451
  sources = [
@@ -457,6 +455,7 @@ class PlainCommandCollection(click.CommandCollection):
457
455
  plain_cli,
458
456
  ]
459
457
  except plain.runtime.AppPathNotFound:
458
+ # Allow some commands to work regardless of being in a valid app
460
459
  click.secho(
461
460
  "Plain `app` directory not found. Some commands may be missing.",
462
461
  fg="yellow",
@@ -468,19 +467,16 @@ class PlainCommandCollection(click.CommandCollection):
468
467
  plain_cli,
469
468
  ]
470
469
  except ImproperlyConfigured as e:
470
+ # Show what was configured incorrectly and exit
471
471
  click.secho(
472
472
  str(e),
473
473
  fg="red",
474
474
  err=True,
475
475
  )
476
476
 
477
- # None of these require the app to be setup
478
- sources = [
479
- EntryPointGroup(),
480
- AppCLIGroup(),
481
- plain_cli,
482
- ]
477
+ exit(1)
483
478
  except Exception as e:
479
+ # Show the exception and exit
484
480
  print("---")
485
481
  print(traceback.format_exc())
486
482
  print("---")
@@ -491,12 +487,7 @@ class PlainCommandCollection(click.CommandCollection):
491
487
  err=True,
492
488
  )
493
489
 
494
- # None of these require the app to be setup
495
- sources = [
496
- EntryPointGroup(),
497
- AppCLIGroup(),
498
- plain_cli,
499
- ]
490
+ exit(1)
500
491
 
501
492
  super().__init__(*args, **kwargs)
502
493
 
@@ -6,20 +6,22 @@ from plain.http import Response
6
6
  from plain.views.exceptions import ResponseException
7
7
 
8
8
 
9
- def dd(obj):
9
+ def dd(*objs):
10
10
  """
11
11
  Dump and die.
12
12
 
13
13
  Dump the object and raise a ResponseException with the dump as the response content.
14
14
  """
15
- dump_str = pformat(obj)
15
+ dump_strs = [
16
+ Markup("<pre><code>") + escape(pformat(obj)) + Markup("</code></pre>")
17
+ for obj in objs
18
+ ]
19
+ combined_dump_str = Markup("\n\n").join(dump_strs)
16
20
 
17
- print(f"Dumping object:\n{dump_str}")
21
+ print(f"Dumping objects:\n{combined_dump_str}")
18
22
 
19
23
  response = Response()
20
24
  response.status_code = 500
21
- response.content = (
22
- Markup("<pre><code>") + escape(dump_str) + Markup("</code></pre>")
23
- )
25
+ response.content = combined_dump_str
24
26
  response.content_type = "text/html"
25
27
  raise ResponseException(response)
@@ -167,5 +167,4 @@ SILENCED_PREFLIGHT_CHECKS = []
167
167
  # Templates #
168
168
  #############
169
169
 
170
- JINJA_LOADER = "jinja2.loaders.FileSystemLoader"
171
- JINJA_ENVIRONMENT = "plain.templates.jinja.defaults.create_default_environment"
170
+ TEMPLATES_JINJA_ENVIRONMENT = "plain.templates.jinja.DefaultEnvironment"
@@ -0,0 +1,17 @@
1
+ from .core import Template, TemplateFileMissing
2
+ from .jinja import (
3
+ register_template_extension,
4
+ register_template_filter,
5
+ register_template_global,
6
+ )
7
+
8
+ __all__ = [
9
+ "Template",
10
+ "TemplateFileMissing",
11
+ # Technically these are jinja-specific,
12
+ # but expected to be used pretty frequently so
13
+ # the shorter import is handy.
14
+ "register_template_extension",
15
+ "register_template_filter",
16
+ "register_template_global",
17
+ ]
@@ -105,25 +105,11 @@ TODO - example middleware, `|localtime` filter
105
105
 
106
106
  ## Settings
107
107
 
108
- Most Jinja customization happens in `jinja.py` at the root of your app or in `INSTALLED_PACKAGES`.
108
+ Most Jinja customization happens in `templates.py` at the root of your app or in `INSTALLED_PACKAGES`.
109
109
  But if you need to further customize the environment,
110
- you can define your own callable which will be used to create the Jinja environment.
110
+ you can define your own class via `TEMPLATES_JINJA_ENVIRONMENT`.
111
111
 
112
- ```python
113
- # app/settings.py
114
- JINJA_ENVIRONMENT = "myjinja.create_environment"
115
- ```
116
-
117
- ```python
118
- # app/myjinja.py
119
- from jinja2 import Environment
120
-
121
-
122
- def create_environment():
123
- return Environment(
124
- # ...
125
- )
126
- ```
112
+ TODO
127
113
 
128
114
  ## HTML Components
129
115
 
@@ -0,0 +1,88 @@
1
+ from importlib import import_module
2
+
3
+ from plain.packages import packages
4
+ from plain.runtime import settings
5
+ from plain.utils.functional import LazyObject
6
+ from plain.utils.module_loading import import_string, module_has_submodule
7
+
8
+ from .environments import DefaultEnvironment, get_template_dirs
9
+
10
+
11
+ class JinjaEnvironment(LazyObject):
12
+ def __init__(self, *args, **kwargs):
13
+ self.__dict__["_imported_modules"] = set()
14
+ super().__init__(*args, **kwargs)
15
+
16
+ def _setup(self):
17
+ environment_setting = settings.TEMPLATES_JINJA_ENVIRONMENT
18
+
19
+ if isinstance(environment_setting, str):
20
+ env = import_string(environment_setting)()
21
+ else:
22
+ env = environment_setting()
23
+
24
+ # We have to set _wrapped before we trigger the autoloading of "register" commands
25
+ self._wrapped = env
26
+
27
+ def _maybe_import_module(name):
28
+ if name not in self._imported_modules:
29
+ import_module(name)
30
+ self._imported_modules.add(name)
31
+
32
+ for package_config in packages.get_package_configs():
33
+ if module_has_submodule(package_config.module, "templates"):
34
+ # Allow this to fail in case there are import errors inside of their file
35
+ _maybe_import_module(f"{package_config.name}.templates")
36
+
37
+ app = import_module("app")
38
+ if module_has_submodule(app, "templates"):
39
+ # Allow this to fail in case there are import errors inside of their file
40
+ _maybe_import_module("app.templates")
41
+
42
+
43
+ environment = JinjaEnvironment()
44
+
45
+
46
+ def register_template_extension(extension_class):
47
+ environment.add_extension(extension_class)
48
+ return extension_class
49
+
50
+
51
+ def register_template_global(value, name=None):
52
+ """
53
+ Adds a global to the Jinja environment.
54
+
55
+ Can be used as a decorator on a function:
56
+
57
+ @register_template_global
58
+ def my_global():
59
+ return "Hello, world!"
60
+
61
+ Or as a function:
62
+
63
+ register_template_global("Hello, world!", name="my_global")
64
+ """
65
+ if callable(value):
66
+ environment.globals[name or value.__name__] = value
67
+ elif name:
68
+ environment.globals[name] = value
69
+ else:
70
+ raise ValueError("name must be provided if value is not callable")
71
+
72
+ return value
73
+
74
+
75
+ def register_template_filter(func, name=None):
76
+ """Adds a filter to the Jinja environment."""
77
+ environment.filters[name or func.__name__] = func
78
+ return func
79
+
80
+
81
+ __all__ = [
82
+ "environment",
83
+ "DefaultEnvironment",
84
+ "get_template_dirs",
85
+ "register_template_extension",
86
+ "register_template_filter",
87
+ "register_template_global",
88
+ ]
@@ -0,0 +1,63 @@
1
+ import functools
2
+ from pathlib import Path
3
+
4
+ from jinja2 import Environment, StrictUndefined
5
+ from jinja2.loaders import FileSystemLoader
6
+
7
+ from plain.packages import packages
8
+ from plain.runtime import settings
9
+
10
+ from .filters import default_filters
11
+ from .globals import default_globals
12
+
13
+
14
+ def finalize_callable_error(obj):
15
+ """Prevent direct rendering of a callable (likely just forgotten ()) by raising a TypeError"""
16
+ if callable(obj):
17
+ raise TypeError(f"{obj} is callable, did you forget parentheses?")
18
+
19
+ # TODO find a way to prevent <object representation> from being rendered
20
+ # if obj.__class__.__str__ is object.__str__:
21
+ # raise TypeError(f"{obj} does not have a __str__ method")
22
+
23
+ return obj
24
+
25
+
26
+ def get_template_dirs():
27
+ jinja_templates = Path(__file__).parent / "templates"
28
+ app_templates = settings.path.parent / "templates"
29
+ return (jinja_templates, app_templates) + _get_app_template_dirs()
30
+
31
+
32
+ @functools.lru_cache
33
+ def _get_app_template_dirs():
34
+ """
35
+ Return an iterable of paths of directories to load app templates from.
36
+
37
+ dirname is the name of the subdirectory containing templates inside
38
+ installed applications.
39
+ """
40
+ dirname = "templates"
41
+ template_dirs = [
42
+ Path(package_config.path) / dirname
43
+ for package_config in packages.get_package_configs()
44
+ if package_config.path and (Path(package_config.path) / dirname).is_dir()
45
+ ]
46
+ # Immutable return value because it will be cached and shared by callers.
47
+ return tuple(template_dirs)
48
+
49
+
50
+ class DefaultEnvironment(Environment):
51
+ def __init__(self):
52
+ super().__init__(
53
+ loader=FileSystemLoader(get_template_dirs()),
54
+ autoescape=True,
55
+ auto_reload=settings.DEBUG,
56
+ undefined=StrictUndefined,
57
+ finalize=finalize_callable_error,
58
+ extensions=["jinja2.ext.loopcontrols", "jinja2.ext.debug"],
59
+ )
60
+
61
+ # Load the top-level defaults
62
+ self.globals.update(default_globals)
63
+ self.filters.update(default_filters)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "plain"
3
- version = "0.12.1"
3
+ version = "0.13.1"
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,6 +0,0 @@
1
- from .core import Template, TemplateFileMissing
2
-
3
- __all__ = [
4
- "Template",
5
- "TemplateFileMissing",
6
- ]
@@ -1,22 +0,0 @@
1
- from plain.runtime import settings
2
- from plain.utils.functional import LazyObject
3
- from plain.utils.module_loading import import_string
4
-
5
- from .defaults import create_default_environment, get_template_dirs
6
-
7
-
8
- class JinjaEnvironment(LazyObject):
9
- def _setup(self):
10
- environment_setting = settings.JINJA_ENVIRONMENT
11
-
12
- if isinstance(environment_setting, str):
13
- environment = import_string(environment_setting)()
14
- else:
15
- environment = environment_setting()
16
-
17
- self._wrapped = environment
18
-
19
-
20
- environment = JinjaEnvironment()
21
-
22
- __all__ = ["environment", "create_default_environment", "get_template_dirs"]
@@ -1,119 +0,0 @@
1
- import functools
2
- from importlib import import_module
3
- from pathlib import Path
4
-
5
- from jinja2 import Environment, StrictUndefined
6
-
7
- from plain.packages import packages
8
- from plain.runtime import settings
9
- from plain.utils.module_loading import import_string, module_has_submodule
10
-
11
- from .filters import default_filters
12
- from .globals import default_globals
13
-
14
-
15
- @functools.lru_cache
16
- def _get_app_template_dirs():
17
- """
18
- Return an iterable of paths of directories to load app templates from.
19
-
20
- dirname is the name of the subdirectory containing templates inside
21
- installed applications.
22
- """
23
- dirname = "templates"
24
- template_dirs = [
25
- Path(package_config.path) / dirname
26
- for package_config in packages.get_package_configs()
27
- if package_config.path and (Path(package_config.path) / dirname).is_dir()
28
- ]
29
- # Immutable return value because it will be cached and shared by callers.
30
- return tuple(template_dirs)
31
-
32
-
33
- def _get_installed_extensions() -> tuple[list, dict, dict]:
34
- """Automatically load extensions, globals, filters from INSTALLED_PACKAGES jinja module and root jinja module"""
35
- extensions = []
36
- globals = {}
37
- filters = {}
38
-
39
- for package_config in packages.get_package_configs():
40
- if module_has_submodule(package_config.module, "jinja"):
41
- module = import_module(f"{package_config.name}.jinja")
42
- else:
43
- continue
44
-
45
- if hasattr(module, "extensions"):
46
- extensions.extend(module.extensions)
47
-
48
- if hasattr(module, "globals"):
49
- globals.update(module.globals)
50
-
51
- if hasattr(module, "filters"):
52
- filters.update(module.filters)
53
-
54
- try:
55
- import jinja
56
-
57
- if hasattr(jinja, "extensions"):
58
- extensions.extend(jinja.extensions)
59
-
60
- if hasattr(jinja, "globals"):
61
- globals.update(jinja.globals)
62
-
63
- if hasattr(jinja, "filters"):
64
- filters.update(jinja.filters)
65
- except ImportError:
66
- pass
67
-
68
- return extensions, globals, filters
69
-
70
-
71
- def finalize_callable_error(obj):
72
- """Prevent direct rendering of a callable (likely just forgotten ()) by raising a TypeError"""
73
- if callable(obj):
74
- raise TypeError(f"{obj} is callable, did you forget parentheses?")
75
-
76
- # TODO find a way to prevent <object representation> from being rendered
77
- # if obj.__class__.__str__ is object.__str__:
78
- # raise TypeError(f"{obj} does not have a __str__ method")
79
-
80
- return obj
81
-
82
-
83
- def get_template_dirs():
84
- jinja_templates = Path(__file__).parent / "templates"
85
- app_templates = settings.path.parent / "templates"
86
- return (jinja_templates, app_templates) + _get_app_template_dirs()
87
-
88
-
89
- def create_default_environment(include_packages=True, **environment_kwargs):
90
- """
91
- This default jinja environment, also used by the error rendering and internal views so
92
- customization needs to happen by using this function, not settings that hook in internally.
93
- """
94
- loader = import_string(settings.JINJA_LOADER)(get_template_dirs())
95
- kwargs = {
96
- "loader": loader,
97
- "autoescape": True,
98
- "auto_reload": settings.DEBUG,
99
- "undefined": StrictUndefined,
100
- "finalize": finalize_callable_error,
101
- "extensions": ["jinja2.ext.loopcontrols", "jinja2.ext.debug"],
102
- }
103
- kwargs.update(**environment_kwargs)
104
- env = Environment(**kwargs)
105
-
106
- # Load the top-level defaults
107
- env.globals.update(default_globals)
108
- env.filters.update(default_filters)
109
-
110
- if include_packages:
111
- app_extensions, app_globals, app_filters = _get_installed_extensions()
112
-
113
- for extension in app_extensions:
114
- env.add_extension(extension)
115
-
116
- env.globals.update(app_globals)
117
- env.filters.update(app_filters)
118
-
119
- return env
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes