cherrypy-foundation 1.0.0a3__py3-none-any.whl → 1.0.0a5__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.
@@ -0,0 +1,14 @@
1
+ <html>
2
+ <head>
3
+ <title>test-url</title>
4
+ </head>
5
+ <body>
6
+ Empty: {{ url_for("", **kwargs) }}<br/>
7
+ Dot: {{ url_for(".", **kwargs) }}<br/>
8
+ Dot page: {{ url_for(".", "my-page", **kwargs) }}<br/>
9
+ Slash: {{ url_for("/", **kwargs) }}<br/>
10
+ Page: {{ url_for("my-page", **kwargs) }}<br/>
11
+ Slash page: {{ url_for("/my-page", **kwargs) }}<br/>
12
+ Query: {{ url_for("my-page", foo='1', bar='test with space', **kwargs) }}<br/>
13
+ </body>
14
+ </html>
@@ -29,9 +29,6 @@ HAS_JINJAX = importlib.util.find_spec("jinjax") is not None
29
29
 
30
30
  env = cherrypy.tools.jinja2.create_env(
31
31
  package_name=__package__,
32
- globals={
33
- 'const1': 'STATIC VALUE',
34
- },
35
32
  )
36
33
 
37
34
 
@@ -0,0 +1,165 @@
1
+ # CherryPy Foundation
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
+
17
+ import cherrypy
18
+ from cherrypy.test import helper
19
+
20
+ import cherrypy_foundation.tools.jinja2 # noqa
21
+
22
+ from ..url import url_for
23
+
24
+ env = cherrypy.tools.jinja2.create_env(
25
+ package_name=__package__,
26
+ globals={
27
+ 'url_for': url_for,
28
+ },
29
+ )
30
+
31
+
32
+ class SubPage:
33
+
34
+ @cherrypy.expose
35
+ @cherrypy.tools.jinja2(template='test_url.html')
36
+ def index(self, **kwargs):
37
+ _relative = cherrypy.request.headers.get('-Relative')
38
+ return {'kwargs': {'_relative': _relative or None}}
39
+
40
+
41
+ @cherrypy.tools.proxy(base='https://www.example.com')
42
+ class ProxiedPage:
43
+
44
+ @cherrypy.expose
45
+ @cherrypy.tools.jinja2(template='test_url.html')
46
+ def index(self, **kwargs):
47
+ _relative = cherrypy.request.headers.get('-Relative')
48
+ return {'kwargs': {'_relative': _relative or None}}
49
+
50
+
51
+ @cherrypy.tools.jinja2(env=env)
52
+ class Root:
53
+ sub_page = SubPage()
54
+ proxied = ProxiedPage()
55
+
56
+ @cherrypy.expose
57
+ @cherrypy.tools.jinja2(template='test_url.html')
58
+ def index(self, **kwargs):
59
+ _relative = cherrypy.request.headers.get('-Relative')
60
+ return {'kwargs': {'_relative': _relative or None}}
61
+
62
+
63
+ class UrlTest(helper.CPWebCase):
64
+ default_lang = None
65
+ interactive = False
66
+
67
+ @classmethod
68
+ def setup_server(cls):
69
+ cherrypy.tree.mount(Root(), '/')
70
+
71
+ def test_url_for(self):
72
+ self.assertEqual(url_for("foo", "bar"), 'http://127.0.0.1:54583/foo/bar')
73
+ self.assertEqual(url_for("foo", "bar", _relative='server'), '/foo/bar')
74
+ # Outside a request, relative url doesn't make alot of sens.
75
+ self.assertEqual(url_for("foo", "bar", _relative=1), '127.0.0.1:54583/foo/bar')
76
+ self.assertEqual(url_for("foo", "bar", _base='http://test.com'), 'http://test.com/foo/bar')
77
+ self.assertEqual(
78
+ url_for("mailto:myuser@test.com", subject='Enter you subject', _base=''),
79
+ 'mailto:myuser@test.com?subject=Enter%20you%20subject',
80
+ )
81
+
82
+ def test_get_page(self):
83
+ # Given a form
84
+ # When querying the page that include this form
85
+ self.getPage("/")
86
+ self.assertStatus(200)
87
+ # Then each field is render properly.
88
+ # 1. Check title
89
+ self.assertInBody('test-url')
90
+ # 2. Check user field
91
+ self.assertInBody(f'Empty: http://{self.HOST}:{self.PORT}/')
92
+ self.assertInBody(f'Dot: http://{self.HOST}:{self.PORT}/')
93
+ self.assertInBody(f'Dot page: http://{self.HOST}:{self.PORT}/my-page')
94
+ self.assertInBody(f'Slash: http://{self.HOST}:{self.PORT}/')
95
+ self.assertInBody(f'Page: http://{self.HOST}:{self.PORT}/my-page')
96
+ self.assertInBody(f'Slash page: http://{self.HOST}:{self.PORT}/my-page')
97
+ self.assertInBody(f'Query: http://{self.HOST}:{self.PORT}/my-page?bar=test+with+space&amp;foo=1')
98
+
99
+ def test_get_page_relative_true(self):
100
+ # Given a form
101
+ # When querying the page that include this form
102
+ self.getPage("/", headers=[('_relative', '1')])
103
+ self.assertStatus(200)
104
+ # Then each field is render properly.
105
+ # 1. Check title
106
+ self.assertInBody('test-url')
107
+ # 2. Check user field
108
+ self.assertInBody('Empty: <br/>')
109
+ self.assertInBody('Dot: <br/>')
110
+ self.assertInBody('Dot page: my-page<br/>')
111
+ self.assertInBody('Slash: <br/>')
112
+ self.assertInBody('Page: my-page<br/>')
113
+ self.assertInBody('Slash page: my-page<br/>')
114
+ self.assertInBody('Query: my-page?bar=test+with+space&amp;foo=1<br/>')
115
+
116
+ def test_get_page_relative_server(self):
117
+ # Given a form
118
+ # When querying the page that include this form
119
+ self.getPage("/", headers=[('_relative', 'server')])
120
+ self.assertStatus(200)
121
+ # Then each field is render properly.
122
+ # 1. Check title
123
+ self.assertInBody('test-url')
124
+ # 2. Check user field
125
+ self.assertInBody('Empty: /<br/>')
126
+ self.assertInBody('Dot: /<br/>')
127
+ self.assertInBody('Dot page: /my-page<br/>')
128
+ self.assertInBody('Slash: /<br/>')
129
+ self.assertInBody('Page: /my-page<br/>')
130
+ self.assertInBody('Slash page: /my-page<br/>')
131
+ self.assertInBody('Query: /my-page?bar=test+with+space&amp;foo=1<br/>')
132
+
133
+ def test_get_page_proxied(self):
134
+ # Given a form
135
+ # When querying the page that include this form
136
+ self.getPage("/proxied/")
137
+ self.assertStatus(200)
138
+ # Then each field is render properly.
139
+ # 1. Check title
140
+ self.assertInBody('test-url')
141
+ # 2. Check user field
142
+ self.assertInBody('Empty: https://www.example.com/')
143
+ self.assertInBody('Dot: https://www.example.com/proxied/')
144
+ self.assertInBody('Dot page: https://www.example.com/proxied/my-page<br/>')
145
+ self.assertInBody('Slash: https://www.example.com/')
146
+ self.assertInBody('Page: https://www.example.com/my-page')
147
+ self.assertInBody('Slash page: https://www.example.com/my-page')
148
+ self.assertInBody('Query: https://www.example.com/my-page?bar=test+with+space&amp;foo=1')
149
+
150
+ def test_get_sub_page(self):
151
+ # Given a form
152
+ # When querying the page that include this form
153
+ self.getPage("/sub-page/")
154
+ self.assertStatus(200)
155
+ # Then each field is render properly.
156
+ # 1. Check title
157
+ self.assertInBody('test-url')
158
+ # 2. Check user field
159
+ self.assertInBody(f'Empty: http://{self.HOST}:{self.PORT}/sub-page/')
160
+ self.assertInBody(f'Dot: http://{self.HOST}:{self.PORT}/sub-page/')
161
+ self.assertInBody(f'Dot page: http://{self.HOST}:{self.PORT}/sub-page/my-page')
162
+ self.assertInBody(f'Slash: http://{self.HOST}:{self.PORT}/')
163
+ self.assertInBody(f'Page: http://{self.HOST}:{self.PORT}/my-page')
164
+ self.assertInBody(f'Slash page: http://{self.HOST}:{self.PORT}/my-page')
165
+ self.assertInBody(f'Query: http://{self.HOST}:{self.PORT}/my-page?bar=test+with+space&amp;foo=1')
@@ -14,10 +14,12 @@
14
14
  # You should have received a copy of the GNU General Public License
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
+ from urllib.parse import quote, urlencode, urljoin
18
+
17
19
  import cherrypy
18
20
 
19
21
 
20
- def url_for(*args, relative=None, **kwargs):
22
+ def url_for(*args, _relative=None, _base=None, **kwargs):
21
23
  """
22
24
  Generate a URL for the given endpoint/path (*args) with query params (**kwargs).
23
25
 
@@ -33,34 +35,39 @@ def url_for(*args, relative=None, **kwargs):
33
35
  - Integers are appended as path segments.
34
36
  - When path == "", existing request query parameters are merged (kwargs win).
35
37
  """
36
- path = ""
38
+ # Handle query-string
39
+ qs = [(k, v) for k, v in sorted(kwargs.items()) if v is not None]
40
+
41
+ path = []
37
42
  for chunk in args:
38
- if isinstance(chunk, str):
39
- if not chunk.startswith('.'):
40
- path += "/"
41
- path += chunk.rstrip("/")
42
- elif isinstance(chunk, int):
43
- path += "/"
44
- path += str(chunk)
45
- elif hasattr(chunk, '__url_for__') and callable(chunk.__url_for__):
46
- path += "/"
47
- path += str(chunk.__url_for__())
43
+ if hasattr(chunk, '__url_for__') and callable(chunk.__url_for__):
44
+ path.append(str(chunk.__url_for__()))
48
45
  elif hasattr(chunk, 'url_for'):
49
- path += "/"
50
- path += str(chunk.url_for)
46
+ path.append(str(chunk.url_for))
47
+ elif isinstance(chunk, str):
48
+ path.append(chunk)
49
+ elif isinstance(chunk, int):
50
+ path.append(str(chunk))
51
51
  else:
52
52
  raise ValueError('invalid positional arguments, url_for accept str, bytes, int: %r' % chunk)
53
+ path = '/'.join(path)
53
54
  # When path is empty, we are browsing the same page.
54
55
  # Let keep the original query_string to avoid loosing it.
55
- if path == "":
56
+ if not path:
56
57
  params = cherrypy.request.params.copy()
57
58
  params.update(kwargs)
58
59
  qs = [(k, v) for k, v in sorted(params.items()) if v is not None]
59
- else:
60
- qs = [(k, v) for k, v in sorted(kwargs.items()) if v is not None]
61
- # Outside a request, use the external_url as base if defined
62
- base = None
60
+ elif not path.startswith('.'):
61
+ path = urljoin('/', path)
62
+ # Outside a request, use cherrypy.tools.proxy config
63
63
  if not cherrypy.request.app:
64
- cfg = cherrypy.tree.apps[''].cfg
65
- base = cfg.external_url
66
- return cherrypy.url(path=path, qs=qs, relative=relative, base=base)
64
+ if _base is None:
65
+ _base = cherrypy.config.get('tools.proxy.base', None)
66
+ # Use cherrypy to build the URL
67
+ url = cherrypy.url(path=path, relative=_relative, base=_base)
68
+ # Append the query string
69
+ if qs and url.startswith('mailto:'):
70
+ return url + '?' + urlencode(qs, quote_via=quote)
71
+ elif qs:
72
+ return url + '?' + urlencode(qs)
73
+ return url
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cherrypy-foundation
3
- Version: 1.0.0a3
3
+ Version: 1.0.0a5
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=fFRbutUX6c1lVHqjehmO9y98dJgmfNCjhd76t2mth2s,
4
4
  cherrypy_foundation/form.py,sha256=8c9dO0o47sK3CBosTkGXoVRtzNQwY0aw0vNZfTqmhvo,3994
5
5
  cherrypy_foundation/logging.py,sha256=YIOK5ZAZLCv52YDdP66yBYpEX1C336JnI3wnrTKl1Lw,3468
6
6
  cherrypy_foundation/passwd.py,sha256=ZGdrBNKtLP75l01W6VEd8cIjSQ3guJ_YVPEfbSew7T0,2144
7
- cherrypy_foundation/url.py,sha256=n12rU2R3VoPbGJ01-iXIMlZL4ohpVhk3vOc-__KkPzE,2814
7
+ cherrypy_foundation/url.py,sha256=kpmZfsYEaj94Kdw82DADNReaV65RJsH21mWDO2UOd5w,3074
8
8
  cherrypy_foundation/widgets.py,sha256=0B5Y2V6x5Ufl6ExR3tc0Olrzj7N4TAAOtqGq_MUxBG0,1549
9
9
  cherrypy_foundation/components/ColorModes.jinja,sha256=8MzkeZsra1wtIdiaQKc7UQUbMfRMUlmM6e9X7V1vfq0,3501
10
10
  cherrypy_foundation/components/Datatable.css,sha256=7wSwgdA61vYCdEuQ0bp2o0oSvu5mGLN1c6ovCUSe718,947
@@ -91,9 +91,11 @@ cherrypy_foundation/plugins/tests/test_scheduler.py,sha256=I-ZuQhMvCCvqFDwukwsyz
91
91
  cherrypy_foundation/plugins/tests/test_smtp.py,sha256=qs5yezIpSXkBmLmFlqckfPW7NmntHZxQjDSkdQG_dNE,4183
92
92
  cherrypy_foundation/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  cherrypy_foundation/tests/test_error_page.py,sha256=8u5p6lv_I4XvfGipjJXNFwW7G1N8AK8IOnc_Th-Ebto,2181
94
- cherrypy_foundation/tests/test_form.py,sha256=urYi0qC8JqelopdeLBHvtndEv0QJ0hwlv3JkpAvdCYQ,4324
94
+ cherrypy_foundation/tests/test_form.py,sha256=KvVA7OEqaDR-A-1EJdA-WlXkqyK9YJkRHhoUvGx1zr8,4269
95
95
  cherrypy_foundation/tests/test_passwd.py,sha256=gC5O4yhHyU1YRYuDc0pG0T_5zvrG2qrr6P822iyK3Rg,1956
96
+ cherrypy_foundation/tests/test_url.py,sha256=YXhCYBeOgX35lTkFYSimL4qaF_0ZLKLN3tarQhmx0rU,6624
96
97
  cherrypy_foundation/tests/templates/test_form.html,sha256=sm-n2cYvih2vbDE4Y8kkERSoulnKAbwoefbzBggMMnA,189
98
+ cherrypy_foundation/tests/templates/test_url.html,sha256=Rb6NokHEduMHAXO8P6EduMMHXuNzJGN5Of2OF4fSWns,502
97
99
  cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
100
  cherrypy_foundation/tools/auth.py,sha256=lTSajxCiReMzm-Fl-xhTByi4yFnInEWOoNsmUMnHQhs,9761
99
101
  cherrypy_foundation/tools/auth_mfa.py,sha256=VaLvBz9wo6jTx-2mCGqFXPxl-z14f8UMWvd6_xeXd40,9212
@@ -118,8 +120,8 @@ cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9I
118
120
  cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=v9AHxksbBvzE7sesPqE61HMhsvU4juXt3E0ZQo-zXVQ,190
119
121
  cherrypy_foundation/tools/tests/templates/test_jinja2_i18n.html,sha256=98S51dgG7Vb4rvMZNZvomw1D9pBiM4g6pdlxAgvrxXA,373
120
122
  cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NT19UaUzm8FRKOIc6H6HNGPDJU6KATnakd8zf3BCeAs,153
121
- cherrypy_foundation-1.0.0a3.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
122
- cherrypy_foundation-1.0.0a3.dist-info/METADATA,sha256=rnu-JnpVHWQcZ5ykeEYGGYYQB1eOeg5Uv9YfMou5yvA,2022
123
- cherrypy_foundation-1.0.0a3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
- cherrypy_foundation-1.0.0a3.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
125
- cherrypy_foundation-1.0.0a3.dist-info/RECORD,,
123
+ cherrypy_foundation-1.0.0a5.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
124
+ cherrypy_foundation-1.0.0a5.dist-info/METADATA,sha256=r_lyZ6WWQth8Dhp0PH9gjqI75NcJjXKCbUfjUxMdpAk,2022
125
+ cherrypy_foundation-1.0.0a5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
+ cherrypy_foundation-1.0.0a5.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
127
+ cherrypy_foundation-1.0.0a5.dist-info/RECORD,,