cherrypy-foundation 1.0.0a20__py3-none-any.whl → 1.0.1__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.
@@ -77,8 +77,8 @@ class LdapPlugin(SimplePlugin):
77
77
  """
78
78
  Used this plugin to authenticate user against an LDAP server.
79
79
 
80
- `authenticate(username, password)` return None if the credentials are not
81
- valid. Otherwise it return a tuple or username and extra attributes.
80
+ `authenticate(login, password)` return None if the credentials are not
81
+ valid. Otherwise it return a tuple or login and extra attributes.
82
82
  The extra attribute may contains `_fullname` and `_email`.
83
83
  """
84
84
 
@@ -89,7 +89,7 @@ class LdapPlugin(SimplePlugin):
89
89
  scope = 'subtree'
90
90
  tls = False
91
91
  user_filter = '(objectClass=*)'
92
- username_attribute = ['uid']
92
+ login_attribute = ['uid']
93
93
  required_group = None
94
94
  group_attribute = 'member'
95
95
  group_attribute_is_dn = False
@@ -133,14 +133,14 @@ class LdapPlugin(SimplePlugin):
133
133
  self.stop()
134
134
  self.start()
135
135
 
136
- def authenticate(self, username, password):
136
+ def authenticate(self, login, password):
137
137
  """
138
138
  Check if the given credential as valid according to LDAP.
139
139
  Return False if invalid.
140
140
  Return None if the plugin is unavailable to validate credentials or if the plugin is disabled.
141
- Return tuple (<username>, <attributes>) if the credentials are valid.
141
+ Return tuple (<login>, <attributes>) if the credentials are valid.
142
142
  """
143
- assert isinstance(username, str)
143
+ assert isinstance(login, str)
144
144
  assert isinstance(password, str)
145
145
 
146
146
  if not hasattr(self, '_pool'):
@@ -150,14 +150,14 @@ class LdapPlugin(SimplePlugin):
150
150
  with self._pool as conn:
151
151
  try:
152
152
  # Search the LDAP server for user's DN.
153
- safe_username = _safe(username)
154
- attr_filter = ''.join([f'({_safe(attr)}={safe_username})' for attr in self.username_attribute])
153
+ safe_login = _safe(login)
154
+ attr_filter = ''.join([f'({_safe(attr)}={safe_login})' for attr in self.login_attribute])
155
155
  search_filter = f"(&{self.user_filter}(|{attr_filter}))"
156
156
  response = self._search(conn, search_filter)
157
157
  if not response:
158
- cherrypy.log(f"lookup failed username={username} reason=not_found", context='LDAP')
158
+ cherrypy.log(f"lookup failed login={login} reason=not_found", context='LDAP')
159
159
  return False
160
- cherrypy.log(f"lookup successful username={username}", context='LDAP')
160
+ cherrypy.log(f"lookup successful login={login}", context='LDAP')
161
161
  user_dn = response[0]['dn']
162
162
 
163
163
  # Use a separate connection to validate credentials
@@ -171,18 +171,18 @@ class LdapPlugin(SimplePlugin):
171
171
  )
172
172
  if not login_conn.bind():
173
173
  cherrypy.log(
174
- f'ldap authentication failed username={username} reason=wrong_password',
174
+ f'ldap authentication failed login={login} reason=wrong_password',
175
175
  context='LDAP',
176
176
  severity=logging.WARNING,
177
177
  )
178
178
  return False
179
179
 
180
- # Get username
180
+ # Get user's login
181
181
  attrs = response[0]['attributes']
182
- new_username = first_attribute(attrs, self.username_attribute)
183
- if not new_username:
182
+ new_login = first_attribute(attrs, self.login_attribute[0])
183
+ if not new_login:
184
184
  cherrypy.log(
185
- f"object missing username attribute user_dn={user_dn} attribute={self.username_attribute}",
185
+ f"object missing login attribute user_dn={user_dn} attribute={self.login_attribute[0]}",
186
186
  context='LDAP',
187
187
  severity=logging.WARNING,
188
188
  )
@@ -192,7 +192,7 @@ class LdapPlugin(SimplePlugin):
192
192
  if self.required_group:
193
193
  if not isinstance(self.required_group, list):
194
194
  self.required_group = [self.required_group]
195
- user_value = user_dn if self.group_attribute_is_dn else new_username
195
+ user_value = user_dn if self.group_attribute_is_dn else new_login
196
196
  group_filter = '(&(%s=%s)(|%s)%s)' % (
197
197
  _safe(self.group_attribute),
198
198
  _safe(user_value),
@@ -201,13 +201,13 @@ class LdapPlugin(SimplePlugin):
201
201
  )
202
202
  # Search LDAP Server for matching groups.
203
203
  cherrypy.log(
204
- f"group check start username={user_value} required_groups={' '.join(self.required_group)}",
204
+ f"group check start login={user_value} required_groups={' '.join(self.required_group)}",
205
205
  context='LDAP',
206
206
  )
207
207
  response = self._search(conn, group_filter, attributes=['cn'])
208
208
  if not response:
209
209
  cherrypy.log(
210
- f"group check failed username={user_value} required_groups={' '.join(self.required_group)}",
210
+ f"group check failed login={user_value} required_groups={' '.join(self.required_group)}",
211
211
  context='LDAP',
212
212
  )
213
213
  return False
@@ -220,13 +220,11 @@ class LdapPlugin(SimplePlugin):
220
220
  firstname = first_attribute(attrs, self.firstname_attribute, '')
221
221
  lastname = first_attribute(attrs, self.lastname_attribute, '')
222
222
  attrs['fullname'] = ' '.join([name for name in [firstname, lastname] if name])
223
- return (new_username, attrs)
223
+ return (new_login, attrs)
224
224
  except LDAPInvalidCredentialsResult:
225
225
  return False
226
226
  except LDAPException:
227
- cherrypy.log(
228
- f"unexpected error username={username}", context='LDAP', severity=logging.ERROR, traceback=True
229
- )
227
+ cherrypy.log(f"unexpected error login={login}", context='LDAP', severity=logging.ERROR, traceback=True)
230
228
  return None
231
229
 
232
230
  def search(self, filter, attributes=ldap3.ALL_ATTRIBUTES, search_base=None, paged_size=None):
@@ -1,20 +1,4 @@
1
- # Session timeout tool for cherrypy
2
- # Copyright (C) 2012-2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- # BFH Science Self-Service Portal
1
+ # Cherrypy-foundation
18
2
  # Copyright (C) 2025-2026 IKUS Software
19
3
  #
20
4
  # This program is free software: you can redistribute it and/or modify
@@ -29,6 +13,10 @@
29
13
  #
30
14
  # You should have received a copy of the GNU General Public License
31
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+
18
+ import logging
19
+ import os
32
20
  import time
33
21
  from contextlib import contextmanager
34
22
 
@@ -38,7 +26,6 @@ from cherrypy.lib import locking
38
26
  from cherrypy.lib.sessions import FileSession as CPFileSession
39
27
 
40
28
 
41
- # Issue in cherrypy: https://github.com/cherrypy/cherrypy/issues/2065
42
29
  class FileSession(CPFileSession):
43
30
  """
44
31
  Override implementation of cherrpy file session to improve file locking.
@@ -46,6 +33,8 @@ class FileSession(CPFileSession):
46
33
 
47
34
  def acquire_lock(self, path=None):
48
35
  """Acquire an exclusive lock on the currently-loaded session data."""
36
+ # See Issue https://github.com/cherrypy/cherrypy/issues/2065
37
+
49
38
  if path is None:
50
39
  path = self._get_file_path()
51
40
  path += self.LOCK_SUFFIX
@@ -62,6 +51,28 @@ class FileSession(CPFileSession):
62
51
  if self.debug:
63
52
  cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
64
53
 
54
+ def clean_up(self):
55
+ """Also clean-up left over lock files."""
56
+ # See Issue https://github.com/cherrypy/cherrypy/issues/1855
57
+
58
+ # Clean-up session files.
59
+ CPFileSession.clean_up(self)
60
+
61
+ # Then clean-up any orphane lock files.
62
+ suffix_len = len(self.LOCK_SUFFIX)
63
+ files = os.listdir(self.storage_path)
64
+ lock_files = [
65
+ fname for fname in files if fname.startswith(self.SESSION_PREFIX) and fname.endswith(self.LOCK_SUFFIX)
66
+ ]
67
+ for fname in lock_files:
68
+ session_file = fname[:-suffix_len]
69
+ if session_file not in files:
70
+ filepath = os.path.join(self.storage_path, fname)
71
+ try:
72
+ os.unlink(filepath)
73
+ except Exception as e:
74
+ cherrypy.log(f'Error deleting {filepath}: {e}', 'TOOLS.SESSIONS', severity=logging.WARNING)
75
+
65
76
 
66
77
  @contextmanager
67
78
  def session_lock():
@@ -0,0 +1,89 @@
1
+ # Cherrypy-foundation
2
+ # Copyright (C) 2026 IKUS Software
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import os
18
+ import tempfile
19
+
20
+ import cherrypy
21
+ from cherrypy.test import helper
22
+
23
+ from cherrypy_foundation.sessions import FileSession, session_lock
24
+
25
+
26
+ @cherrypy.tools.sessions(on=True, locking='explicit', storage_class=FileSession)
27
+ class Root:
28
+
29
+ @cherrypy.expose
30
+ def index(self, value='OK'):
31
+ if value:
32
+ with session_lock() as s:
33
+ s['value'] = value
34
+ return s['value']
35
+
36
+
37
+ class FileSessionTest(helper.CPWebCase):
38
+ interactive = False
39
+
40
+ @classmethod
41
+ def setup_class(cls):
42
+ cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-file-session-test')
43
+ cls.storage_path = cls.tempdir.name
44
+ super().setup_class()
45
+
46
+ @classmethod
47
+ def teardown_class(cls):
48
+ cls.tempdir.cleanup()
49
+ super().teardown_class()
50
+
51
+ @classmethod
52
+ def setup_server(cls):
53
+ cherrypy.config.update(
54
+ {
55
+ 'tools.sessions.storage_path': cls.storage_path,
56
+ }
57
+ )
58
+ cherrypy.tree.mount(Root(), '/')
59
+
60
+ @property
61
+ def _session_id(self):
62
+ """Return session id from cookie."""
63
+ if hasattr(self, 'cookies') and self.cookies:
64
+ for unused, value in self.cookies:
65
+ for part in value.split(';'):
66
+ key, unused, value = part.partition('=')
67
+ if key == 'session_id':
68
+ return value
69
+
70
+ def test_get_page(self):
71
+ # Given a page with session enabled
72
+ # When the page get queried
73
+ self.getPage("/")
74
+ # Then a session is created with a id
75
+ self.assertStatus(200)
76
+ self.assertTrue(self._session_id)
77
+ # Then this session is created on disk.
78
+ s = FileSession(id=self._session_id, storage_path=self.storage_path)
79
+ self.assertTrue(s._exists())
80
+ # When session timeout and get clean-up
81
+ s.acquire_lock()
82
+ s.load()
83
+ s.timeout = 0
84
+ s.save()
85
+ s.clean_up()
86
+ # Then session get deleted
87
+ self.assertFalse(s._exists())
88
+ # Lock file also get deleted.
89
+ self.assertFalse(os.path.exists(s._get_file_path() + FileSession.LOCK_SUFFIX))
@@ -20,6 +20,7 @@ import time
20
20
 
21
21
  import cherrypy
22
22
  import jinja2
23
+ from markupsafe import Markup
23
24
 
24
25
  # Sentinel value
25
26
  _UNDEFINED = object()
@@ -27,6 +28,8 @@ _UNDEFINED = object()
27
28
  # Capture epoch time to invalidate cache of static file.
28
29
  _cache_invalidate = int(time.time())
29
30
 
31
+ _JINJAX_TOKEN = '@@JINJAX-CATALOG-TOKEN@@'
32
+
30
33
 
31
34
  class Jinja2Tool(cherrypy.Tool):
32
35
  """
@@ -41,12 +44,47 @@ class Jinja2Tool(cherrypy.Tool):
41
44
  Replace the placeholder token in the rendered HTML with the fully
42
45
  formatted asset tags, then reset asset state for the next render.
43
46
  """
44
- assets_html = catalog._format_collected_assets()
45
- assets_html = str(assets_html).replace('<script type="module" ', '<script ')
46
- catalog._emit_assets_later = False
47
- catalog.collected_css = []
48
- catalog.collected_js = []
49
- return str(html).replace(catalog._assets_placeholder, assets_html)
47
+ # Skip this step if render_assets() has not been called.
48
+ if hasattr(catalog, '_emit_assets_later') and getattr(catalog, '_emit_assets_later') is False:
49
+ return html
50
+
51
+ # Render Js and Css collected assets
52
+ if hasattr(catalog, '_format_collected_assets'):
53
+ # JinjaX > 0.60
54
+ catalog._emit_assets_later = False
55
+ assets_html = catalog._format_collected_assets()
56
+ assets_html = str(assets_html).replace('<script type="module" ', '<script ')
57
+ return str(html).replace(catalog._assets_placeholder, assets_html)
58
+ else:
59
+ # JinjaX<=0.60
60
+ html_css = []
61
+ rendered_urls = set()
62
+
63
+ for url in catalog.collected_css:
64
+ if url == _JINJAX_TOKEN:
65
+ continue
66
+ if not url.startswith(("http://", "https://")):
67
+ full_url = f"{catalog.root_url}{url}"
68
+ else:
69
+ full_url = url
70
+
71
+ if full_url not in rendered_urls:
72
+ html_css.append(f'<link rel="stylesheet" href="{full_url}">')
73
+ rendered_urls.add(full_url)
74
+
75
+ html_js = []
76
+ for url in catalog.collected_js:
77
+ if not url.startswith(("http://", "https://")):
78
+ full_url = f"{catalog.root_url}{url}"
79
+ else:
80
+ full_url = url
81
+
82
+ if full_url not in rendered_urls:
83
+ html_js.append(f'<script src="{full_url}"></script>')
84
+ rendered_urls.add(full_url)
85
+
86
+ assets_html = Markup("\n".join(html_css + html_js))
87
+ return str(html).replace(f'<link rel="stylesheet" href="/static/components/{_JINJAX_TOKEN}">', assets_html)
50
88
 
51
89
  def _wrap_handler(self, env, template, extra_processor=None, debug=False):
52
90
 
@@ -147,11 +185,23 @@ class Jinja2Tool(cherrypy.Tool):
147
185
  tmpl = env.select_template(names)
148
186
  else:
149
187
  tmpl = env.get_template(template)
188
+
189
+ # Monkey patch JinjaX catalog.render_assets() for JinjaX <= 0.60
190
+ catalog = env.globals.get('catalog')
191
+ if catalog:
192
+ catalog.collected_css = []
193
+ catalog.collected_js = []
194
+ if not hasattr(catalog, '_assets_placeholder'):
195
+ # JinjaX <= 0.60
196
+ # Leave a token to be replace.
197
+ catalog.collected_css = [_JINJAX_TOKEN]
198
+
199
+ # Render template
150
200
  out = tmpl.render(context)
151
201
 
152
- # With JinjaX > 0.60 render explicitly here.
153
- if 'catalog' in env.globals and getattr(env.globals['catalog'], '_emit_assets_later', False):
154
- return self._finalize_assets(catalog=env.globals['catalog'], html=out)
202
+ # With JinjaX finalise render_assets().
203
+ if catalog:
204
+ return self._finalize_assets(catalog=catalog, html=out)
155
205
  return out
156
206
 
157
207
 
@@ -124,6 +124,7 @@ class Jinja2Test(helper.CPWebCase, SeleniumUnitTest):
124
124
  # Then the page return without error
125
125
  self.assertStatus(200)
126
126
  # Then the page is render dynamically using page context
127
+ self.assertInBody('<script src="/static/components/LocaleSelection.js"></script>')
127
128
  if expected_lang == 'fr':
128
129
  self.assertInBody('lang="fr"')
129
130
  self.assertInBody('français')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cherrypy-foundation
3
- Version: 1.0.0a20
3
+ Version: 1.0.1
4
4
  Summary: Cherrypy-foundation
5
5
  Author-email: Patrik Dufresne <patrik@ikus-soft.com>
6
6
  License: GPLv3
@@ -4,7 +4,7 @@ cherrypy_foundation/flash.py,sha256=vraKvmYmjdV1cGsPSxTLVsekwqOnoyiVPqKjv6UW5OU,
4
4
  cherrypy_foundation/form.py,sha256=8zp72fpYrmMGsIK5LxVY7sL_qqhoMDnIQoMND1s6CXk,3994
5
5
  cherrypy_foundation/logging.py,sha256=JyzcuAu3HWzvMTSFBuKpwgnJLz1lRlx-j6EkuTwlb6I,3463
6
6
  cherrypy_foundation/passwd.py,sha256=iyz3SgcvpgxsZgPUoTEXiqdKtwOANPrRKYhVPm2d_NQ,2139
7
- cherrypy_foundation/sessions.py,sha256=uboQcf4W2zsHFoplRtSziZu1KzvhsudIehHGbG1XYW8,2977
7
+ cherrypy_foundation/sessions.py,sha256=6OmjrJtX2Hhjhx5Q8Cy1nwG6_Ur6PwPErfPPA7FfzYg,3180
8
8
  cherrypy_foundation/url.py,sha256=WtfD0XEOs5FNmLvxhnBvi9aW4hQpUz0ZZXPfEXd4To8,2849
9
9
  cherrypy_foundation/widgets.py,sha256=-wxNv3wIgf7eB8WyyycfsqSCXep3YhZgBK_5PJ1qYbY,1549
10
10
  cherrypy_foundation/components/ColorModes.jinja,sha256=Ai2fy1qHFwEgutvyvvGjKJmffcBdNb7wmY20DJqZ8R4,3528
@@ -84,7 +84,7 @@ cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.css,sha256=
84
84
  cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.js,sha256=eFBtgouYdW587kYINWsjkN-UR8GVWAG_fQe1fpJfjOw,47828
85
85
  cherrypy_foundation/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
86
  cherrypy_foundation/plugins/db.py,sha256=S2OqujZ-D_TxUM6NbQW7XGDt8eGDup1Wbhf0XCcYSbs,10108
87
- cherrypy_foundation/plugins/ldap.py,sha256=j2KxkkHZ8PJxywRpkHQHXi1o6ZSiAdMVvTWC1vpXEvU,9970
87
+ cherrypy_foundation/plugins/ldap.py,sha256=Y0gUD5CcFaUAI7SSPIlrlr4IkQ1QePX2--qfZrIBlbo,9861
88
88
  cherrypy_foundation/plugins/restapi.py,sha256=1W1mmVh-rIzg_mcFaNQZZXpqF7eN7VhRL9oTEiQOr-w,2843
89
89
  cherrypy_foundation/plugins/scheduler.py,sha256=m2XLM7euRjC5bkU4NILvwnRo0w6NWRwmCI19iEiQlE0,10777
90
90
  cherrypy_foundation/plugins/smtp.py,sha256=7qZv0VrQxi7gNoVdRds0F6Ya9pyo3LlZ12uFaVpAsms,7658
@@ -100,6 +100,7 @@ cherrypy_foundation/tests/test_flash.py,sha256=NNbT3XI1dHk0hXqJsWndCmPPohK9ig87Z
100
100
  cherrypy_foundation/tests/test_form.py,sha256=-2h38e00hWlYNJeVWrUB2V11UZ3x0FmTcsCez5vWoas,5542
101
101
  cherrypy_foundation/tests/test_logging.py,sha256=Lg1yGwFHTSpGbPvNXBi9risypZziJlAOMi1DeennxRs,2623
102
102
  cherrypy_foundation/tests/test_passwd.py,sha256=nGqJZYwRPziifLvf5rm3YifxAvIOFdU_6LkHaUvRk8w,1951
103
+ cherrypy_foundation/tests/test_sessions.py,sha256=_jcCn75hMGr7ETucWZnCqWcI3pw5PCdlzPQuxdE4yZE,2873
103
104
  cherrypy_foundation/tests/test_url.py,sha256=sSNwK7KZJcrLtnf_XROYQAPQoYNO5lAsa2BjhFjgmws,6434
104
105
  cherrypy_foundation/tests/templates/test_flash.html,sha256=b1S4I9v0n-Y1yoTUh2ZKNysR1NMrqv8ldvqONtmInzw,213
105
106
  cherrypy_foundation/tests/templates/test_form.html,sha256=liubTm2q74-3hqQb4weaGJU3sq4vdq868GdVahBafSQ,333
@@ -108,7 +109,7 @@ cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
108
109
  cherrypy_foundation/tools/auth.py,sha256=WRXiZdRXJcd4uZFDb8DAGX-ySqiYytdWyjiQiLZb9Vo,9935
109
110
  cherrypy_foundation/tools/auth_mfa.py,sha256=hDwfKc2zFY3bxOm1JRLLFQRXVPL9atBQ-PEwQTEQUww,9579
110
111
  cherrypy_foundation/tools/i18n.py,sha256=f4m9fc2FKtcdUpXgnLXzhoipn4oLDuHY94Vv3KPSfp0,17126
111
- cherrypy_foundation/tools/jinja2.py,sha256=hTiOepdb7kyDEqCK4NH2O-v1lrgPGNX4VV3AWZ-OsAc,5967
112
+ cherrypy_foundation/tools/jinja2.py,sha256=StEKv7nWtnCwGoOGYzIKtcPkEsRGPg1TtsjdyK_VW0Q,7834
112
113
  cherrypy_foundation/tools/ratelimit.py,sha256=C_bGzkx9waIJlpK-X6_SH8r1rP0I_xr0wLnGZhSEGEo,8742
113
114
  cherrypy_foundation/tools/secure_headers.py,sha256=XqmjU5MCK8fTacQ2uTUiODYN4uaa9cCGfQJZVnp4J4g,3921
114
115
  cherrypy_foundation/tools/sessions_timeout.py,sha256=M54IpRc3c4rUH0Cbri363O6GhD89EmzrjqvWeul8khc,7484
@@ -116,7 +117,7 @@ cherrypy_foundation/tools/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
116
117
  cherrypy_foundation/tools/tests/test_auth.py,sha256=qQxCs8uCgu3C2Of9DRWplpCLLB7zSiJkrV4F9muLkJg,3414
117
118
  cherrypy_foundation/tools/tests/test_auth_mfa.py,sha256=qYIlw4tDQUi9R7nPwph6UpMsThWDN1EYW-FrrOF_grQ,15111
118
119
  cherrypy_foundation/tools/tests/test_i18n.py,sha256=6lHV3kxriRI5jo-YMnxzKKDFtLhogUrqK7D4X2-m42U,10758
119
- cherrypy_foundation/tools/tests/test_jinja2.py,sha256=3r96jcm68Jtw_hGwpQOlT8Jao6upPSnwENaScluL-7E,5545
120
+ cherrypy_foundation/tools/tests/test_jinja2.py,sha256=8BsxpwLQkoSJCS1cQi5a_wZMu9Zdry4JDKBAUxy7pz0,5636
120
121
  cherrypy_foundation/tools/tests/test_ratelimit.py,sha256=BZn2lhbuaVeIPq80eUn-SFu0mMN3Mb4ttwVoXbozWUA,3431
121
122
  cherrypy_foundation/tools/tests/test_secure_headers.py,sha256=Iu3X7u6VJpWOmuNMv1qZFh0oPWdLZAdoJppRyuTV148,7769
122
123
  cherrypy_foundation/tools/tests/components/Button.jinja,sha256=uSLp1GpEIgZNXK_GWglu0E_a1c3jHpDLI66MRfMqGhE,95
@@ -128,8 +129,8 @@ cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9I
128
129
  cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=s1bHmy-lyf0YW0t-LOx3ugILV2kqFoBNYxziWgrZbo0,216
129
130
  cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NImzIW0mUHxilFd61PSoxFC-yu1nayEVwv-5zlgD9yo,179
130
131
  cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html,sha256=yre8j7HBjpTQZHpM0PuB3ASGD3O4vKkJ-y72Fm6STgY,771
131
- cherrypy_foundation-1.0.0a20.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
132
- cherrypy_foundation-1.0.0a20.dist-info/METADATA,sha256=5aY_cGJ0ziWu_GUyinwsYjM2oPEWsgCpkJS01kaOsjg,3551
133
- cherrypy_foundation-1.0.0a20.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
134
- cherrypy_foundation-1.0.0a20.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
135
- cherrypy_foundation-1.0.0a20.dist-info/RECORD,,
132
+ cherrypy_foundation-1.0.1.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
133
+ cherrypy_foundation-1.0.1.dist-info/METADATA,sha256=pNbkEH_cQBF1jXYE3ejQPDcXTJ6HGPiKRcqcmk7ndVw,3548
134
+ cherrypy_foundation-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
135
+ cherrypy_foundation-1.0.1.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
136
+ cherrypy_foundation-1.0.1.dist-info/RECORD,,