cherrypy-foundation 1.0.0a10__py3-none-any.whl → 1.0.0a12__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.
@@ -26,7 +26,7 @@ _safe = ldap3.utils.conv.escape_filter_chars
26
26
 
27
27
  def all_attribute(attributes, keys, default=None):
28
28
  """
29
- Extract the all value from LDAP attributes.
29
+ Extract all values from LDAP attributes.
30
30
  """
31
31
  # Skip loopkup if key is not defined.
32
32
  if not keys:
@@ -61,7 +61,9 @@ def first_attribute(attributes, keys, default=None):
61
61
  for attr in keys:
62
62
  try:
63
63
  value = attributes[attr]
64
- if isinstance(value, list) and len(value) > 0:
64
+ if isinstance(value, list):
65
+ if len(value) == 0:
66
+ continue
65
67
  return value[0]
66
68
  else:
67
69
  return value
@@ -35,7 +35,6 @@ def clear_db_sessions(func):
35
35
 
36
36
  @wraps(func)
37
37
  def func_wrapper(*args, **kwargs):
38
- cherrypy.db.clear_sessions()
39
38
  try:
40
39
  result = func(*args, **kwargs)
41
40
  finally:
@@ -206,6 +205,7 @@ class Scheduler(SimplePlugin):
206
205
  """
207
206
  hour, minute = execution_time.split(':', 2)
208
207
  return self.add_job(
208
+ id=getattr(func, '__name__', str(func)),
209
209
  func=func,
210
210
  name=getattr(func, '__name__', str(func)),
211
211
  args=args,
@@ -214,6 +214,8 @@ class Scheduler(SimplePlugin):
214
214
  hour=hour,
215
215
  minute=minute,
216
216
  misfire_grace_time=None,
217
+ coalesce=True,
218
+ replace_existing=True,
217
219
  )
218
220
 
219
221
  def add_job_now(self, func, *args, **kwargs):
@@ -243,20 +245,29 @@ class Scheduler(SimplePlugin):
243
245
  Remove the given job from scheduler.
244
246
  """
245
247
  # Search for a matching job
248
+ return_value = False
249
+ if self._scheduler is None:
250
+ return return_value
246
251
  for j in self._scheduler.get_jobs(jobstore=jobstore):
247
- if j.func == job:
252
+ if j.func == job or j.name == job:
248
253
  self._scheduler.remove_job(job_id=j.id, jobstore=jobstore)
254
+ return_value = True
255
+ return return_value
249
256
 
250
257
  def remove_all_jobs(self, jobstore=None):
251
258
  """
252
259
  Remove all jobs from scheduler.
253
260
  """
261
+ if self._scheduler is None:
262
+ return
254
263
  self._scheduler.remove_all_jobs(jobstore=jobstore)
255
264
 
256
265
  def wait_for_jobs(self, jobstore=None):
257
266
  """
258
267
  Used to wait for all running jobs to complete.
259
268
  """
269
+ if self._scheduler is None:
270
+ return
260
271
  # Wait until the queue is empty.
261
272
  while any(
262
273
  job for job in self._scheduler.get_jobs(jobstore=jobstore) if job.next_run_time < datetime.now(timezone.utc)
@@ -19,13 +19,13 @@ Created on Oct 17, 2015
19
19
  @author: Patrik Dufresne <patrik@ikus-soft.com>
20
20
  """
21
21
  import os
22
- from unittest import mock, skipUnless
22
+ from unittest import TestCase, mock, skipUnless
23
23
 
24
24
  import cherrypy
25
25
  import ldap3
26
26
  from cherrypy.test import helper
27
27
 
28
- from .. import ldap # noqa
28
+ from ..ldap import all_attribute, first_attribute # noqa
29
29
 
30
30
  original_connection = ldap3.Connection
31
31
 
@@ -35,6 +35,79 @@ def mock_ldap_connection(*args, **kwargs):
35
35
  return original_connection(*args, client_strategy=ldap3.MOCK_ASYNC, **kwargs)
36
36
 
37
37
 
38
+ class LdapFirstAttributeTest(TestCase):
39
+
40
+ def test_no_keys_returns_default(self):
41
+ attributes = {"cn": ["John Doe"]}
42
+ self.assertIsNone(first_attribute(attributes, None))
43
+ self.assertEqual(first_attribute(attributes, [], default="fallback"), "fallback")
44
+
45
+ def test_single_key_with_scalar_value(self):
46
+ attributes = {"uid": "jdoe"}
47
+ self.assertEqual(first_attribute(attributes, "uid"), "jdoe")
48
+
49
+ def test_single_key_with_list_value(self):
50
+ attributes = {"mail": ["john@example.com", "alt@example.com"]}
51
+ self.assertEqual(first_attribute(attributes, "mail"), "john@example.com")
52
+
53
+ def test_empty_list_value_is_skipped(self):
54
+ attributes = {
55
+ "mail": [],
56
+ "uid": ["jdoe"],
57
+ }
58
+ self.assertEqual(first_attribute(attributes, ["mail", "uid"]), "jdoe")
59
+
60
+ def test_missing_first_key_uses_next_key(self):
61
+ attributes = {"cn": ["John Doe"]}
62
+ self.assertEqual(first_attribute(attributes, ["sn", "cn"]), "John Doe")
63
+
64
+ def test_all_keys_missing_returns_default(self):
65
+ attributes = {"cn": ["John Doe"]}
66
+ self.assertEqual(first_attribute(attributes, ["sn", "uid"], default="unknown"), "unknown")
67
+
68
+ def test_key_not_found_without_default_returns_none(self):
69
+ attributes = {"cn": ["John Doe"]}
70
+ self.assertIsNone(first_attribute(attributes, "uid"))
71
+
72
+
73
+ class LdapAllAttributeTest(TestCase):
74
+
75
+ def test_no_keys_returns_default(self):
76
+ attributes = {"cn": ["John Doe"]}
77
+ self.assertIsNone(all_attribute(attributes, None))
78
+ self.assertEqual(all_attribute(attributes, [], default="fallback"), "fallback")
79
+
80
+ def test_single_key_with_scalar_value(self):
81
+ attributes = {"uid": "jdoe"}
82
+ self.assertEqual(all_attribute(attributes, "uid"), ["jdoe"])
83
+
84
+ def test_single_key_with_list_value(self):
85
+ attributes = {"mail": ["john@example.com", "alt@example.com"]}
86
+ self.assertEqual(all_attribute(attributes, "mail"), ["john@example.com"])
87
+
88
+ def test_multiple_keys_collect_values(self):
89
+ attributes = {
90
+ "uid": "jdoe",
91
+ "cn": ["John Doe"],
92
+ }
93
+ self.assertEqual(all_attribute(attributes, ["uid", "cn"]), ["jdoe", "John Doe"])
94
+
95
+ def test_missing_keys_are_skipped(self):
96
+ attributes = {"cn": ["John Doe"]}
97
+ self.assertEqual(all_attribute(attributes, ["sn", "cn", "uid"]), ["John Doe"])
98
+
99
+ def test_all_keys_missing_returns_default(self):
100
+ attributes = {"cn": ["John Doe"]}
101
+ self.assertEqual(all_attribute(attributes, ["sn", "uid"], default=[]), [])
102
+
103
+ def test_empty_list_value_is_appended_as_empty_list(self):
104
+ attributes = {
105
+ "mail": [],
106
+ "uid": "jdoe",
107
+ }
108
+ self.assertEqual(all_attribute(attributes, ["mail", "uid"]), [[], "jdoe"])
109
+
110
+
38
111
  class LdapPluginTest(helper.CPWebCase):
39
112
 
40
113
  @classmethod
@@ -0,0 +1,107 @@
1
+ # Scheduler plugins for Cherrypy
2
+ # Copyright (C) 2025 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
+ import importlib
17
+ import tempfile
18
+ from threading import Event
19
+
20
+ import cherrypy
21
+ from cherrypy.test import helper
22
+
23
+ from .. import scheduler # noqa
24
+
25
+ done = Event()
26
+
27
+ HAS_SQLALCHEMY = importlib.util.find_spec("sqlalchemy") is not None
28
+ if not HAS_SQLALCHEMY:
29
+ pass
30
+ else:
31
+ from sqlalchemy import Column, Integer, String
32
+ from sqlalchemy.exc import IntegrityError
33
+
34
+ from .. import db # noqa
35
+
36
+ Base = cherrypy.db.get_base()
37
+
38
+ class User2(Base):
39
+ __tablename__ = 'users2'
40
+ id = Column(Integer, primary_key=True)
41
+ username = Column(String)
42
+
43
+ def __repr__(self):
44
+ return f"User2(id={self.id}, username='{self.username}')"
45
+
46
+ class Root:
47
+
48
+ @cherrypy.expose
49
+ def index(self):
50
+ return str(User2.query.all())
51
+
52
+ @cherrypy.expose
53
+ def add(self, username):
54
+ try:
55
+ User2(username=username).add().commit()
56
+ return "OK"
57
+ except IntegrityError as e:
58
+ return str(e)
59
+
60
+ def create_user(*args, **kwargs):
61
+ user = User2(*args, **kwargs).add()
62
+ user.commit()
63
+ done.set()
64
+
65
+
66
+ class DbSchedulerPluginTest(helper.CPWebCase):
67
+ interactive = False
68
+
69
+ @classmethod
70
+ def setup_class(cls):
71
+ cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-db-test')
72
+ super().setup_class()
73
+
74
+ @classmethod
75
+ def teardown_class(cls):
76
+ cls.tempdir.cleanup()
77
+ super().teardown_class()
78
+
79
+ @classmethod
80
+ def setup_server(cls):
81
+ cherrypy.config.update(
82
+ {
83
+ 'db.uri': f"sqlite:///{cls.tempdir.name}/data.db",
84
+ }
85
+ )
86
+ cherrypy.tree.mount(Root(), '/')
87
+
88
+ def setUp(self) -> None:
89
+ done.clear()
90
+ cherrypy.db.create_all()
91
+ return super().setUp()
92
+
93
+ def tearDown(self):
94
+ cherrypy.db.drop_all()
95
+ return super().tearDown()
96
+
97
+ def test_add_job_now(self):
98
+ # Given a task
99
+ # When scheduling that task
100
+ scheduled = cherrypy.engine.publish('scheduler:add_job_now', create_user, username='myuser')
101
+ self.assertTrue(scheduled)
102
+ # When waiting for all tasks
103
+ cherrypy.scheduler.wait_for_jobs()
104
+ # Then the task get called
105
+ self.assertTrue(done.is_set())
106
+ # Then database was updated
107
+ User2.query.filter(User2.username == 'myuser').one()
@@ -186,10 +186,13 @@ def _search_translation(dirname, domain, *locales):
186
186
  # Assign prefered local to this translation to know the current locale.
187
187
  trans_locale = Locale.parse(t.files[0].split('/')[-3])
188
188
  for locale in locales:
189
- locale = Locale.parse(locale)
190
- if trans_locale == locale or trans_locale.language == locale.language:
191
- t.locale = locale
192
- break
189
+ try:
190
+ locale = Locale.parse(locale)
191
+ if trans_locale == locale or trans_locale.language == locale.language:
192
+ t.locale = locale
193
+ break
194
+ except ValueError:
195
+ continue # Invalid locale identifier
193
196
  return t
194
197
 
195
198
 
@@ -150,7 +150,7 @@ class TestI18nWebCase(AbstractI18nTest):
150
150
  with i18n.preferred_lang('de_CH'):
151
151
  self.assertEqual('29. März 2023, 17:40:11 UTC', i18n.format_datetime(date, format='long'))
152
152
 
153
- with i18n.preferred_timezone('CET'):
153
+ with i18n.preferred_timezone('Europe/Paris'):
154
154
  with i18n.preferred_lang('fr'):
155
155
  self.assertEqual('29 mars 2023, 19:40:11 +0200', i18n.format_datetime(date, format='long'))
156
156
  with i18n.preferred_lang('fr_CH'):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cherrypy-foundation
3
- Version: 1.0.0a10
3
+ Version: 1.0.0a12
4
4
  Summary: CherryPy Foundation
5
5
  Author-email: Patrik Dufresne <patrik@ikus-soft.com>
6
6
  License: GPLv3
@@ -81,14 +81,15 @@ cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.css,sha256=
81
81
  cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.js,sha256=eFBtgouYdW587kYINWsjkN-UR8GVWAG_fQe1fpJfjOw,47828
82
82
  cherrypy_foundation/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  cherrypy_foundation/plugins/db.py,sha256=46S-2kR9BHVYRN9cxWwYfzY2WkpEVBCpx1ubsJolYfA,10108
84
- cherrypy_foundation/plugins/ldap.py,sha256=N-bJT7daJcLybEigA3P_5iuDr4PFwmLt8YzcMrWafAY,9870
84
+ cherrypy_foundation/plugins/ldap.py,sha256=PxZp-2tHy73CegiIV5pKnfWe0WOCqNRIYQR8aIh1UEs,9913
85
85
  cherrypy_foundation/plugins/restapi.py,sha256=S5GIxHL0M3Wcs33fyVOb0RfX1FXbp2fqUxJ_ufqBrD4,2843
86
- cherrypy_foundation/plugins/scheduler.py,sha256=H6ExfmAlTPt2ypK5lBMynNfcUzcWK_n1EP2_K5x2bx8,10400
86
+ cherrypy_foundation/plugins/scheduler.py,sha256=McTR72GGTuW7UZKco_Czmp09PfqJ-aHXJxh7h9BFZvA,10777
87
87
  cherrypy_foundation/plugins/smtp.py,sha256=yTCUj2XzrJEJ84CIbYcrCUAP6Mg59aZfu6OZDGQyJbQ,7479
88
88
  cherrypy_foundation/plugins/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  cherrypy_foundation/plugins/tests/test_db.py,sha256=PELi5ojj8VqcXyPbpaig2Ac4ZqlR_gEPGUYrWJPANWg,3921
90
- cherrypy_foundation/plugins/tests/test_ldap.py,sha256=7EhFvhxwDCCBoNlAD5XpZxLtKvkrjpQxrtCHMT9wkQ4,14393
90
+ cherrypy_foundation/plugins/tests/test_ldap.py,sha256=j0QUIxqeEBgN4VLvczUlw0xMM_ldPWVHCceAJzU71Jg,17269
91
91
  cherrypy_foundation/plugins/tests/test_scheduler.py,sha256=I-ZuQhMvCCvqFDwukwsyz_UkdJJ8JSLTkAanUo24GCE,3564
92
+ cherrypy_foundation/plugins/tests/test_scheduler_db.py,sha256=-iZnLgb3qsJzs0p7JAw6TQswRtExCmHAtYXqRNPZn7U,3101
92
93
  cherrypy_foundation/plugins/tests/test_smtp.py,sha256=qs5yezIpSXkBmLmFlqckfPW7NmntHZxQjDSkdQG_dNE,4183
93
94
  cherrypy_foundation/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
95
  cherrypy_foundation/tests/test_error_page.py,sha256=8yLK8OGbJIdUjilFIHMNBZadLKHrXnD6KSmQ3Da4LaQ,2399
@@ -103,7 +104,7 @@ cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
103
104
  cherrypy_foundation/tools/auth.py,sha256=lTSajxCiReMzm-Fl-xhTByi4yFnInEWOoNsmUMnHQhs,9761
104
105
  cherrypy_foundation/tools/auth_mfa.py,sha256=VaLvBz9wo6jTx-2mCGqFXPxl-z14f8UMWvd6_xeXd40,9212
105
106
  cherrypy_foundation/tools/errors.py,sha256=ELpAj0N9kIxC22QW5xDQJz60zMpCwgm-Twu2WpELM1A,1005
106
- cherrypy_foundation/tools/i18n.py,sha256=TpyYYCtgXblGcnB9rybaoiH1y7IEKFH6jvbitrKtxfk,14535
107
+ cherrypy_foundation/tools/i18n.py,sha256=VQJs7hIv-BM9_FIU6-pwRp65eeroHoQLU7QrV5V_rbI,14641
107
108
  cherrypy_foundation/tools/jinja2.py,sha256=nppYnk2ASDsyfNHF9m83W4foiN3MhcwDJvo5baEgnGU,5520
108
109
  cherrypy_foundation/tools/ratelimit.py,sha256=pT7vZRmjltNeuiQpdyXOmnpG9BcXjLaj-AXJ0e2x_zw,8300
109
110
  cherrypy_foundation/tools/secure_headers.py,sha256=Yh-iA_Js4MUsx5nq4ilbc-iWy90ZC0oMb3TJJD_UwYo,3921
@@ -111,7 +112,7 @@ cherrypy_foundation/tools/sessions_timeout.py,sha256=6iBWJntPMk_Qt94fBSfBISf1IXI
111
112
  cherrypy_foundation/tools/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
113
  cherrypy_foundation/tools/tests/test_auth.py,sha256=oeM5t38M8DUC9dYn59dcf00jdGY6Ry0jZWhQd_PYQUk,2847
113
114
  cherrypy_foundation/tools/tests/test_auth_mfa.py,sha256=911hnBbdg5CKb613uIBrlggoTAyBU9SoL7Sxd-tIKS0,15008
114
- cherrypy_foundation/tools/tests/test_i18n.py,sha256=urkOnXYQDxoxeBOm_Ud5IK0n50rJM59X6tEtEjQqRUM,9082
115
+ cherrypy_foundation/tools/tests/test_i18n.py,sha256=T9j9fF7AwH_H4TJnC4CbAOS1a1D2Z4xPxaOmiP0By34,9091
115
116
  cherrypy_foundation/tools/tests/test_jinja2.py,sha256=_dkRJpjB0ybDV6YO0uEFFO8LAcWgVu3VBB8_vWthQ48,4296
116
117
  cherrypy_foundation/tools/tests/test_ratelimit.py,sha256=rrqybwMbh1GFlF2-Ut57zHPAc1uqX88aqea6VS_6p5E,3449
117
118
  cherrypy_foundation/tools/tests/components/Button.jinja,sha256=uSLp1GpEIgZNXK_GWglu0E_a1c3jHpDLI66MRfMqGhE,95
@@ -125,8 +126,8 @@ cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9I
125
126
  cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=v9AHxksbBvzE7sesPqE61HMhsvU4juXt3E0ZQo-zXVQ,190
126
127
  cherrypy_foundation/tools/tests/templates/test_jinja2_i18n.html,sha256=98S51dgG7Vb4rvMZNZvomw1D9pBiM4g6pdlxAgvrxXA,373
127
128
  cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NT19UaUzm8FRKOIc6H6HNGPDJU6KATnakd8zf3BCeAs,153
128
- cherrypy_foundation-1.0.0a10.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
129
- cherrypy_foundation-1.0.0a10.dist-info/METADATA,sha256=VR5Yovc0oxdiIFxJkrU7GWZRoi6h5mWUge-vUPcEhsc,2023
130
- cherrypy_foundation-1.0.0a10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
131
- cherrypy_foundation-1.0.0a10.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
132
- cherrypy_foundation-1.0.0a10.dist-info/RECORD,,
129
+ cherrypy_foundation-1.0.0a12.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
130
+ cherrypy_foundation-1.0.0a12.dist-info/METADATA,sha256=bVj-ivyQaks7eDS4cxGDbq0I1OhKXejr5Rq9EUo81xc,2023
131
+ cherrypy_foundation-1.0.0a12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
+ cherrypy_foundation-1.0.0a12.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
133
+ cherrypy_foundation-1.0.0a12.dist-info/RECORD,,