yhttp 9.1.0__tar.gz → 9.4.2__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 (66) hide show
  1. yhttp-9.4.2/PKG-INFO +289 -0
  2. yhttp-9.4.2/README.md +246 -0
  3. {yhttp-9.1.0 → yhttp-9.4.2}/setup.py +5 -1
  4. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_applicationcli.py +15 -2
  5. yhttp-9.4.2/tests/test_logging.py +102 -0
  6. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_request.py +1 -0
  7. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_rewritecli.py +3 -1
  8. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/application.py +7 -3
  9. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/cli.py +61 -7
  10. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/fswatcher.py +2 -1
  11. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/guard.py +8 -8
  12. yhttp-9.4.2/yhttp/core/logging.py +25 -0
  13. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/request.py +15 -0
  14. yhttp-9.4.2/yhttp/core/version.py +1 -0
  15. yhttp-9.4.2/yhttp.egg-info/PKG-INFO +289 -0
  16. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp.egg-info/SOURCES.txt +2 -0
  17. yhttp-9.1.0/PKG-INFO +0 -157
  18. yhttp-9.1.0/README.md +0 -118
  19. yhttp-9.1.0/yhttp/core/version.py +0 -1
  20. yhttp-9.1.0/yhttp.egg-info/PKG-INFO +0 -157
  21. {yhttp-9.1.0 → yhttp-9.4.2}/LICENSE +0 -0
  22. {yhttp-9.1.0 → yhttp-9.4.2}/setup.cfg +0 -0
  23. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_application.py +0 -0
  24. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_builtincli_serve.py +0 -0
  25. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_builtincli_version.py +0 -0
  26. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_builtincli_yhttp_version.py +0 -0
  27. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_cookie.py +0 -0
  28. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_cookieset.py +0 -0
  29. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_extension.py +0 -0
  30. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_form.py +0 -0
  31. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_form_json.py +0 -0
  32. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_form_multipart.py +0 -0
  33. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_form_urlencoded.py +0 -0
  34. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_fswatcher.py +0 -0
  35. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_guard.py +0 -0
  36. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_guard_body.py +0 -0
  37. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_guard_contenttypes.py +0 -0
  38. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_guard_query.py +0 -0
  39. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_headerset.py +0 -0
  40. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_jsonencoding.py +0 -0
  41. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_locale.py +0 -0
  42. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_middleware.py +0 -0
  43. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_multidict.py +0 -0
  44. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_multipart.py +0 -0
  45. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_querystring.py +0 -0
  46. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_responseheaders.py +0 -0
  47. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_rewrite.py +0 -0
  48. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_routing.py +0 -0
  49. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_static.py +0 -0
  50. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_statuses.py +0 -0
  51. {yhttp-9.1.0 → yhttp-9.4.2}/tests/test_statushandler.py +0 -0
  52. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/__init__.py +0 -0
  53. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/contenttypes.py +0 -0
  54. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/cookieset.py +0 -0
  55. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/headerset.py +0 -0
  56. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/main.py +0 -0
  57. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/multidict.py +0 -0
  58. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/multipart.py +0 -0
  59. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/response.py +0 -0
  60. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/rewrite.py +0 -0
  61. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/static.py +0 -0
  62. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp/core/statuses.py +0 -0
  63. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp.egg-info/dependency_links.txt +0 -0
  64. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp.egg-info/entry_points.txt +0 -0
  65. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp.egg-info/requires.txt +0 -0
  66. {yhttp-9.1.0 → yhttp-9.4.2}/yhttp.egg-info/top_level.txt +0 -0
yhttp-9.4.2/PKG-INFO ADDED
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: yhttp
3
+ Version: 9.4.2
4
+ Summary: A very micro http framework.
5
+ Home-page: http://github.com/yhttp/yhttp
6
+ Author: Vahid Mardani
7
+ Author-email: vahid.mardani@gmail.com
8
+ License: MIT
9
+ Classifier: Environment :: Console
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: License :: Other/Proprietary License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
24
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: snam>=1.0.2
30
+ Requires-Dist: easycli<2,>=1.10.1
31
+ Requires-Dist: ujson
32
+ Requires-Dist: inotify-simple
33
+ Dynamic: author
34
+ Dynamic: author-email
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: license
40
+ Dynamic: license-file
41
+ Dynamic: requires-dist
42
+ Dynamic: summary
43
+
44
+ # YHTTP
45
+
46
+ [![PyPI](https://img.shields.io/pypi/v/yhttp.svg)](https://pypi.org/project/yhttp/)
47
+ [![Build](https://github.com/yhttp/yhttp/actions/workflows/build.yml/badge.svg)](https://github.com/yhttp/yhttp/actions/workflows/build.yml)
48
+ [![Coverage](https://coveralls.io/repos/github/yhttp/yhttp/badge.svg?branch=master)](https://coveralls.io/github/yhttp/yhttp?branch=master)
49
+ [![Documentation](https://img.shields.io/badge/docs-yhttp.github.io-blue)](https://yhttp.github.io/yhttp/)
50
+ [![Python](https://img.shields.io/badge/Python-%3E%3D3.10-blue)](https://www.python.org/)
51
+
52
+ YHTTP is a small, extensible WSGI framework for building HTTP services in
53
+ Python. It provides regex routing, request guards, form parsing, settings,
54
+ lifecycle hooks, static file serving, and a built-in development CLI while
55
+ leaving features such as templates, authentication, localization, and database
56
+ access to focused extensions.
57
+
58
+ - [Documentation](https://yhttp.github.io/yhttp/)
59
+ - [PyPI](https://pypi.org/project/yhttp/)
60
+ - [Source](https://github.com/yhttp/yhttp)
61
+
62
+ ## Installation
63
+
64
+ YHTTP requires Python 3.10 or newer.
65
+
66
+ ```bash
67
+ python -m pip install yhttp
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ Create `hello.py`:
73
+
74
+ ```python
75
+ import sys
76
+
77
+ from yhttp.core import Application, text
78
+
79
+
80
+ app = Application('0.1.0', 'hello')
81
+
82
+
83
+ @app.route('/')
84
+ @text
85
+ def get(req):
86
+ return 'Hello, world!'
87
+
88
+
89
+ if __name__ == '__main__':
90
+ sys.exit(app.climain())
91
+
92
+
93
+ app.ready()
94
+ ```
95
+
96
+ Start the built-in development server and make a request:
97
+
98
+ ```bash
99
+ python hello.py serve --bind 8080
100
+ curl http://localhost:8080/
101
+ ```
102
+
103
+ The application is WSGI-compatible, so it can also be served by a WSGI server
104
+ such as Gunicorn:
105
+
106
+ ```bash
107
+ python -m pip install gunicorn
108
+ gunicorn hello:app
109
+ ```
110
+
111
+ The handler name selects the HTTP verb when `verb` is not passed explicitly to
112
+ `app.route()`. Routes are regular expressions, and captured groups are passed
113
+ to the handler after `req`.
114
+
115
+ ## Features
116
+
117
+ - Regex routes with captured path parameters and explicit verb overrides
118
+ - Strict query-string and request-body validation through guards
119
+ - URL-encoded, multipart, and JSON form parsing
120
+ - Layered settings and application lifecycle hooks
121
+ - Static files, WSGI rewriting, middleware, and status handlers
122
+ - An extensible command-line interface with a development server
123
+ - A small core that can be composed with YHTTP extensions
124
+
125
+ ## SSR development
126
+
127
+ YHTTP keeps server-side rendering outside the core. Use an extension such as
128
+ [`yhttp-mako`](https://github.com/yhttp/yhttp-mako) to render Mako templates,
129
+ then compose localization, authentication, assets, and persistence through the
130
+ extensions already used by your application.
131
+
132
+ Install the Mako extension:
133
+
134
+ ```bash
135
+ python -m pip install yhttp-mako
136
+ mkdir -p templates makomodules
137
+ ```
138
+
139
+ A minimal rendered page consists of an application module and a template:
140
+
141
+ ```python
142
+ # app.py
143
+ from yhttp.core import Application
144
+ from yhttp.ext import mako
145
+
146
+
147
+ app = Application('0.1.0', 'pages')
148
+ mako.install(app)
149
+
150
+ app.settings.mako.lookup = 'templates'
151
+ app.settings.mako.modules = 'makomodules'
152
+
153
+
154
+ @app.route('/')
155
+ @app.template('index.mako')
156
+ def get(req):
157
+ return {'title': 'Hello from YHTTP'}
158
+
159
+
160
+ app.ready()
161
+ ```
162
+
163
+ ```mako
164
+ <!-- templates/index.mako -->
165
+ <!doctype html>
166
+ <html lang="en">
167
+ <head>
168
+ <meta charset="utf-8">
169
+ <meta name="viewport" content="width=device-width, initial-scale=1">
170
+ <title>${title | h}</title>
171
+ </head>
172
+ <body>
173
+ <h1>${title | h}</h1>
174
+ </body>
175
+ </html>
176
+ ```
177
+
178
+ Run it with `gunicorn app:app`. The `makomodules` directory stores compiled
179
+ templates and must be writable by the application process.
180
+
181
+ For a maintainable SSR application, keep the rendering path explicit:
182
+
183
+ 1. Create the `Application`, install extensions, and merge settings before
184
+ readiness. Import model modules before route modules, and call `app.ready()`
185
+ only after registration is complete; an unimported model or route module is
186
+ inactive.
187
+ 2. Keep handlers responsible for request guards, authorization, database
188
+ access, and status responses. A page handler should return a context
189
+ dictionary to `@app.template(...)`, not build HTML itself.
190
+ 3. Put shared document structure in inherited Mako layouts. Keep templates
191
+ presentational, reuse template-provided helpers, and escape user-controlled
192
+ values. Render stored HTML unescaped only when the application explicitly
193
+ treats it as trusted.
194
+ 4. Localize visible text with the installed translation helpers, preserve
195
+ locale-prefixed links, and test both LTR and RTL output when the application
196
+ supports both directions.
197
+ 5. Keep pages useful without JavaScript where practical. Add focused browser
198
+ behavior through the project's existing asset pipeline, and keep endpoint
199
+ calls in browser-side service modules rather than templates.
200
+ 6. Extend the nearest bddrest page test. Assert meaningful rendered behavior,
201
+ including routes, localized text, direction-sensitive markup, resolved
202
+ assets, authentication states, and persisted effects relevant to the page.
203
+
204
+ A typical project keeps its composition root, route registration, page
205
+ handlers, templates, browser assets, and page tests separate:
206
+
207
+ ```text
208
+ app.py
209
+ models/
210
+ routes.py
211
+ pages.py
212
+ templates/
213
+ www/
214
+ tests/test_pages.py
215
+ ```
216
+
217
+ Treat this as a responsibility map rather than a required package layout;
218
+ follow the nearest complete feature in an existing application.
219
+
220
+ ## Contributing
221
+
222
+ This repository uses
223
+ [`python-makelib`](https://github.com/pylover/python-makelib). Install it first,
224
+ then create and populate the development environment:
225
+
226
+ ```bash
227
+ make venv
228
+ source ./activate.sh
229
+ make env
230
+ ```
231
+
232
+ Run the test suite:
233
+
234
+ ```bash
235
+ make test
236
+ ```
237
+
238
+ Run a focused test or coverage target with `F`:
239
+
240
+ ```bash
241
+ make test F=tests/test_form.py::test_getform_force
242
+ make cover F=tests/test_static.py
243
+ ```
244
+
245
+ Run all coverage checks or generate the HTML coverage report:
246
+
247
+ ```bash
248
+ make cover
249
+ make cover-html
250
+ ```
251
+
252
+ Lint the project:
253
+
254
+ ```bash
255
+ make lint
256
+ ```
257
+
258
+ Delete the virtual environment with `make venv-delete`.
259
+
260
+ ## Documentation
261
+
262
+ Build and test the Sphinx documentation from the repository root:
263
+
264
+ ```bash
265
+ source ./activate.sh
266
+ make doc
267
+ make doctest
268
+ make doclive
269
+ ```
270
+
271
+ The equivalent commands from `sphinx/` are `make html`, `make doctest`, and
272
+ `make livehtml`.
273
+
274
+ ## Distribution
275
+
276
+ Build the source and wheel distributions in `dist/`:
277
+
278
+ ```bash
279
+ make clean
280
+ make sdist
281
+ make wheel
282
+ ```
283
+
284
+ Publishing is reserved for project maintainers. Maintainers can upload both
285
+ artifacts with `make pypi`.
286
+
287
+ ## License
288
+
289
+ YHTTP is released under the [MIT License](LICENSE).
yhttp-9.4.2/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # YHTTP
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/yhttp.svg)](https://pypi.org/project/yhttp/)
4
+ [![Build](https://github.com/yhttp/yhttp/actions/workflows/build.yml/badge.svg)](https://github.com/yhttp/yhttp/actions/workflows/build.yml)
5
+ [![Coverage](https://coveralls.io/repos/github/yhttp/yhttp/badge.svg?branch=master)](https://coveralls.io/github/yhttp/yhttp?branch=master)
6
+ [![Documentation](https://img.shields.io/badge/docs-yhttp.github.io-blue)](https://yhttp.github.io/yhttp/)
7
+ [![Python](https://img.shields.io/badge/Python-%3E%3D3.10-blue)](https://www.python.org/)
8
+
9
+ YHTTP is a small, extensible WSGI framework for building HTTP services in
10
+ Python. It provides regex routing, request guards, form parsing, settings,
11
+ lifecycle hooks, static file serving, and a built-in development CLI while
12
+ leaving features such as templates, authentication, localization, and database
13
+ access to focused extensions.
14
+
15
+ - [Documentation](https://yhttp.github.io/yhttp/)
16
+ - [PyPI](https://pypi.org/project/yhttp/)
17
+ - [Source](https://github.com/yhttp/yhttp)
18
+
19
+ ## Installation
20
+
21
+ YHTTP requires Python 3.10 or newer.
22
+
23
+ ```bash
24
+ python -m pip install yhttp
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ Create `hello.py`:
30
+
31
+ ```python
32
+ import sys
33
+
34
+ from yhttp.core import Application, text
35
+
36
+
37
+ app = Application('0.1.0', 'hello')
38
+
39
+
40
+ @app.route('/')
41
+ @text
42
+ def get(req):
43
+ return 'Hello, world!'
44
+
45
+
46
+ if __name__ == '__main__':
47
+ sys.exit(app.climain())
48
+
49
+
50
+ app.ready()
51
+ ```
52
+
53
+ Start the built-in development server and make a request:
54
+
55
+ ```bash
56
+ python hello.py serve --bind 8080
57
+ curl http://localhost:8080/
58
+ ```
59
+
60
+ The application is WSGI-compatible, so it can also be served by a WSGI server
61
+ such as Gunicorn:
62
+
63
+ ```bash
64
+ python -m pip install gunicorn
65
+ gunicorn hello:app
66
+ ```
67
+
68
+ The handler name selects the HTTP verb when `verb` is not passed explicitly to
69
+ `app.route()`. Routes are regular expressions, and captured groups are passed
70
+ to the handler after `req`.
71
+
72
+ ## Features
73
+
74
+ - Regex routes with captured path parameters and explicit verb overrides
75
+ - Strict query-string and request-body validation through guards
76
+ - URL-encoded, multipart, and JSON form parsing
77
+ - Layered settings and application lifecycle hooks
78
+ - Static files, WSGI rewriting, middleware, and status handlers
79
+ - An extensible command-line interface with a development server
80
+ - A small core that can be composed with YHTTP extensions
81
+
82
+ ## SSR development
83
+
84
+ YHTTP keeps server-side rendering outside the core. Use an extension such as
85
+ [`yhttp-mako`](https://github.com/yhttp/yhttp-mako) to render Mako templates,
86
+ then compose localization, authentication, assets, and persistence through the
87
+ extensions already used by your application.
88
+
89
+ Install the Mako extension:
90
+
91
+ ```bash
92
+ python -m pip install yhttp-mako
93
+ mkdir -p templates makomodules
94
+ ```
95
+
96
+ A minimal rendered page consists of an application module and a template:
97
+
98
+ ```python
99
+ # app.py
100
+ from yhttp.core import Application
101
+ from yhttp.ext import mako
102
+
103
+
104
+ app = Application('0.1.0', 'pages')
105
+ mako.install(app)
106
+
107
+ app.settings.mako.lookup = 'templates'
108
+ app.settings.mako.modules = 'makomodules'
109
+
110
+
111
+ @app.route('/')
112
+ @app.template('index.mako')
113
+ def get(req):
114
+ return {'title': 'Hello from YHTTP'}
115
+
116
+
117
+ app.ready()
118
+ ```
119
+
120
+ ```mako
121
+ <!-- templates/index.mako -->
122
+ <!doctype html>
123
+ <html lang="en">
124
+ <head>
125
+ <meta charset="utf-8">
126
+ <meta name="viewport" content="width=device-width, initial-scale=1">
127
+ <title>${title | h}</title>
128
+ </head>
129
+ <body>
130
+ <h1>${title | h}</h1>
131
+ </body>
132
+ </html>
133
+ ```
134
+
135
+ Run it with `gunicorn app:app`. The `makomodules` directory stores compiled
136
+ templates and must be writable by the application process.
137
+
138
+ For a maintainable SSR application, keep the rendering path explicit:
139
+
140
+ 1. Create the `Application`, install extensions, and merge settings before
141
+ readiness. Import model modules before route modules, and call `app.ready()`
142
+ only after registration is complete; an unimported model or route module is
143
+ inactive.
144
+ 2. Keep handlers responsible for request guards, authorization, database
145
+ access, and status responses. A page handler should return a context
146
+ dictionary to `@app.template(...)`, not build HTML itself.
147
+ 3. Put shared document structure in inherited Mako layouts. Keep templates
148
+ presentational, reuse template-provided helpers, and escape user-controlled
149
+ values. Render stored HTML unescaped only when the application explicitly
150
+ treats it as trusted.
151
+ 4. Localize visible text with the installed translation helpers, preserve
152
+ locale-prefixed links, and test both LTR and RTL output when the application
153
+ supports both directions.
154
+ 5. Keep pages useful without JavaScript where practical. Add focused browser
155
+ behavior through the project's existing asset pipeline, and keep endpoint
156
+ calls in browser-side service modules rather than templates.
157
+ 6. Extend the nearest bddrest page test. Assert meaningful rendered behavior,
158
+ including routes, localized text, direction-sensitive markup, resolved
159
+ assets, authentication states, and persisted effects relevant to the page.
160
+
161
+ A typical project keeps its composition root, route registration, page
162
+ handlers, templates, browser assets, and page tests separate:
163
+
164
+ ```text
165
+ app.py
166
+ models/
167
+ routes.py
168
+ pages.py
169
+ templates/
170
+ www/
171
+ tests/test_pages.py
172
+ ```
173
+
174
+ Treat this as a responsibility map rather than a required package layout;
175
+ follow the nearest complete feature in an existing application.
176
+
177
+ ## Contributing
178
+
179
+ This repository uses
180
+ [`python-makelib`](https://github.com/pylover/python-makelib). Install it first,
181
+ then create and populate the development environment:
182
+
183
+ ```bash
184
+ make venv
185
+ source ./activate.sh
186
+ make env
187
+ ```
188
+
189
+ Run the test suite:
190
+
191
+ ```bash
192
+ make test
193
+ ```
194
+
195
+ Run a focused test or coverage target with `F`:
196
+
197
+ ```bash
198
+ make test F=tests/test_form.py::test_getform_force
199
+ make cover F=tests/test_static.py
200
+ ```
201
+
202
+ Run all coverage checks or generate the HTML coverage report:
203
+
204
+ ```bash
205
+ make cover
206
+ make cover-html
207
+ ```
208
+
209
+ Lint the project:
210
+
211
+ ```bash
212
+ make lint
213
+ ```
214
+
215
+ Delete the virtual environment with `make venv-delete`.
216
+
217
+ ## Documentation
218
+
219
+ Build and test the Sphinx documentation from the repository root:
220
+
221
+ ```bash
222
+ source ./activate.sh
223
+ make doc
224
+ make doctest
225
+ make doclive
226
+ ```
227
+
228
+ The equivalent commands from `sphinx/` are `make html`, `make doctest`, and
229
+ `make livehtml`.
230
+
231
+ ## Distribution
232
+
233
+ Build the source and wheel distributions in `dist/`:
234
+
235
+ ```bash
236
+ make clean
237
+ make sdist
238
+ make wheel
239
+ ```
240
+
241
+ Publishing is reserved for project maintainers. Maintainers can upload both
242
+ artifacts with `make pypi`.
243
+
244
+ ## License
245
+
246
+ YHTTP is released under the [MIT License](LICENSE).
@@ -51,7 +51,11 @@ setup(
51
51
  'Development Status :: 5 - Production/Stable',
52
52
  'License :: Other/Proprietary License',
53
53
  'Operating System :: OS Independent',
54
- 'Programming Language :: Python :: 3.6',
54
+ 'Programming Language :: Python :: 3.10',
55
+ 'Programming Language :: Python :: 3.11',
56
+ 'Programming Language :: Python :: 3.12',
57
+ 'Programming Language :: Python :: 3.13',
58
+ 'Programming Language :: Python :: 3.14',
55
59
  'Topic :: Software Development',
56
60
  'Topic :: Internet :: WWW/HTTP',
57
61
  'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
@@ -53,10 +53,15 @@ foo:
53
53
  '''
54
54
 
55
55
 
56
- def test_applicationcli(mktmpfile):
56
+ def test_applicationcli(mktmpfile, bddcli_bootpatch):
57
57
  cliapp = CLIApplication('foo', 'tests.test_applicationcli:app.climain')
58
58
  configfile = mktmpfile(content='title: bar')
59
- with Given(cliapp, '--help'):
59
+ freezetime = \
60
+ 'import time_machine;' \
61
+ 'time_machine.travel("2012-02-14 16:00:01", tick=False).start()\n'
62
+
63
+ with bddcli_bootpatch(freezetime), Given(cliapp, '--help'):
64
+ assert stderr == ''
60
65
  assert status == 0
61
66
 
62
67
  when('foo')
@@ -68,6 +73,14 @@ def test_applicationcli(mktmpfile):
68
73
  assert status == 73
69
74
  assert stdout == 'bar\n.\n'
70
75
 
76
+ when(f'--verbose --configuration-file {configfile} foo')
77
+ assert stderr == ''
78
+ assert status == 73
79
+ assert stdout == (
80
+ '2012-02-14 16:00:01.000 INFO yhttp: loading config file: '
81
+ f'{configfile}\nbar\n.\n'
82
+ )
83
+
71
84
  when('--directory /tmp foo')
72
85
  assert status == 0
73
86
  assert stdout == 'foo\n/tmp\n'
@@ -0,0 +1,102 @@
1
+ from bddcli import Application as CLIApplication, Given, stdout, status, \
2
+ when, stderr
3
+ from easycli import SubCommand
4
+
5
+ from yhttp.core import Application, logging
6
+
7
+
8
+ logger = logging.getlogger(__name__)
9
+
10
+
11
+ class Foo(SubCommand):
12
+ __command__ = 'foo'
13
+
14
+ def __call__(self, args):
15
+ logger.debug('foo')
16
+ logger.info('foo')
17
+ logger.warning('foo')
18
+ logger.error('foo')
19
+ logger.critical('foo')
20
+ print(f'verbosity: {args.verbosity}')
21
+
22
+
23
+ app = Application('0.1.0', 'foo')
24
+ app.cliarguments.append(Foo)
25
+
26
+
27
+ def test_logging(bddcli_bootpatch):
28
+ cliapp = CLIApplication('foo', f'{__name__}:app.climain')
29
+ freezetime = \
30
+ 'import time_machine;' \
31
+ 'time_machine.travel("2012-02-14 16:00:01", tick=False).start()\n'
32
+
33
+ with bddcli_bootpatch(freezetime), Given(cliapp, 'foo'):
34
+ assert stderr == ''
35
+ assert status == 0
36
+ assert stdout == (
37
+ '2012-02-14 16:00:01.000 WARNING tests.test_logging: foo\n'
38
+ '2012-02-14 16:00:01.000 ERROR tests.test_logging: foo\n'
39
+ '2012-02-14 16:00:01.000 CRITICAL tests.test_logging: foo\n'
40
+ 'verbosity: 30\n'
41
+ )
42
+
43
+ when('-v foo')
44
+ assert stderr == ''
45
+ assert status == 0
46
+ assert stdout == (
47
+ '2012-02-14 16:00:01.000 INFO tests.test_logging: foo\n'
48
+ '2012-02-14 16:00:01.000 WARNING tests.test_logging: foo\n'
49
+ '2012-02-14 16:00:01.000 ERROR tests.test_logging: foo\n'
50
+ '2012-02-14 16:00:01.000 CRITICAL tests.test_logging: foo\n'
51
+ 'verbosity: 20\n'
52
+ )
53
+
54
+ when('-vv foo')
55
+ assert stderr == ''
56
+ assert status == 0
57
+ assert stdout == (
58
+ '2012-02-14 16:00:01.000 DEBUG tests.test_logging: foo\n'
59
+ '2012-02-14 16:00:01.000 INFO tests.test_logging: foo\n'
60
+ '2012-02-14 16:00:01.000 WARNING tests.test_logging: foo\n'
61
+ '2012-02-14 16:00:01.000 ERROR tests.test_logging: foo\n'
62
+ '2012-02-14 16:00:01.000 CRITICAL tests.test_logging: foo\n'
63
+ 'verbosity: 10\n'
64
+ )
65
+
66
+ when('-q foo')
67
+ assert stderr == ''
68
+ assert status == 0
69
+ assert stdout == (
70
+ '2012-02-14 16:00:01.000 ERROR tests.test_logging: foo\n'
71
+ '2012-02-14 16:00:01.000 CRITICAL tests.test_logging: foo\n'
72
+ 'verbosity: 40\n'
73
+ )
74
+
75
+ when('-qq foo')
76
+ assert stderr == ''
77
+ assert status == 0
78
+ assert stdout == (
79
+ '2012-02-14 16:00:01.000 CRITICAL tests.test_logging: foo\n'
80
+ 'verbosity: 50\n'
81
+ )
82
+
83
+ when('-qqq foo')
84
+ assert stderr == ''
85
+ assert status == 0
86
+ assert stdout == 'verbosity: 60\n'
87
+
88
+ when('-vvv foo')
89
+ assert stderr.endswith(
90
+ 'foo: error: option -v only allowed up to twice\n'
91
+ )
92
+
93
+ when('-qqqq foo')
94
+ assert stderr.endswith(
95
+ 'foo: error: option -q only allowed up to 3 times\n'
96
+ )
97
+
98
+ when('-q -v foo')
99
+ assert stderr.endswith(
100
+ 'foo: error: argument -v/--verbose: not allowed with argument '
101
+ '-q/--quiet\n'
102
+ )
@@ -9,6 +9,7 @@ def test_request(app, httpreq):
9
9
  assert req.headers.get('foo-bar') == 'baz'
10
10
  assert req.contenttype is None
11
11
  assert req.language == 'fa'
12
+ assert req.locales_oldstyle == ['fa_IR']
12
13
 
13
14
  with httpreq('/foo?bar=baz', headers={
14
15
  'Foo-Bar': 'baz',
@@ -1,6 +1,7 @@
1
1
  import tempfile
2
2
 
3
- from bddcli import Application as CLIApplication, Given, stdout, status, when
3
+ from bddcli import Application as CLIApplication, Given, stdout, status, \
4
+ when, stderr
4
5
  from easycli import SubCommand
5
6
 
6
7
  from yhttp.core import Rewrite
@@ -36,5 +37,6 @@ def test_rewritecli_default():
36
37
  f.write(b'title: bar')
37
38
  f.flush()
38
39
  when(f'--configuration-file {f.name} foo')
40
+ assert stderr == ''
39
41
  assert status == 73
40
42
  assert stdout == 'bar\n'