Compare commits
No commits in common. "c27a7b28aa9c66d82274314b08853cf0fe8df396" and "bd7ace7a66c967503c32517867cea0f6f411d259" have entirely different histories.
c27a7b28aa
...
bd7ace7a66
12
conftest.py
12
conftest.py
@ -1,12 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from pytest import fixture
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(autouse=True, scope="session")
|
|
||||||
def db():
|
|
||||||
if os.path.exists("spiderweb-tests.db"):
|
|
||||||
os.remove("spiderweb-tests.db")
|
|
||||||
yield
|
|
||||||
if os.path.exists("spiderweb-tests.db"):
|
|
||||||
os.remove("spiderweb-tests.db")
|
|
@ -1,44 +0,0 @@
|
|||||||
# spiderweb
|
|
||||||
|
|
||||||
As a professional web developer focusing on arcane uses of Django for arcane purposes, it occurred to me a little while ago that I didn't actually know how a web framework _worked_.
|
|
||||||
|
|
||||||
> So I built one.
|
|
||||||
|
|
||||||
This is `spiderweb`, a web framework that's just big enough to hold a spider. When building it, my goals were simple:
|
|
||||||
|
|
||||||
- Learn a lot
|
|
||||||
- Create an unholy blend of Django and Flask
|
|
||||||
- Not look at any existing code. Go off of vibes alone and try to solve all the problems I could think of in my own way
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> This is a learning project. It should not be used for production without heavy auditing. It's not secure. It's not fast. It's not well-tested. It's not well-documented. It's not well-anything. It's a learning project.
|
|
||||||
>
|
|
||||||
> That being said, it's fun and it works, so I'm counting that as a win.
|
|
||||||
|
|
||||||
|
|
||||||
Here's a non-exhaustive list of things this can do:
|
|
||||||
|
|
||||||
* Function-based views
|
|
||||||
* Optional Flask-style URL routing
|
|
||||||
* Optional Django-style URL routing
|
|
||||||
* URLs with variables in them a lá Django
|
|
||||||
* Full middleware implementation
|
|
||||||
* Limit routes by HTTP verbs
|
|
||||||
* (Only GET and POST are implemented right now)
|
|
||||||
* Custom error routes
|
|
||||||
* Built-in dev server
|
|
||||||
* Gunicorn support
|
|
||||||
* HTML templates with Jinja2
|
|
||||||
* Static files support
|
|
||||||
* Cookies (reading and setting)
|
|
||||||
* Optional append_slash (with automatic redirects!)
|
|
||||||
* ~~CSRF middleware implementation~~ (it's there, but it's crappy and unsafe. This might be beyond my skillset.)
|
|
||||||
* Optional POST data validation middleware with Pydantic
|
|
||||||
* Database support (using Peewee, but the end user can use whatever they want as long as there's a Peewee driver for it)
|
|
||||||
* Session middleware with built-in session store
|
|
||||||
* Tests (currently a little over 80% coverage)
|
|
||||||
|
|
||||||
The TODO list:
|
|
||||||
|
|
||||||
* Fix CSRF middleware
|
|
||||||
* Add more HTTP verbs
|
|
@ -1,9 +0,0 @@
|
|||||||
![logo](_media/spiderweb_logo.png)
|
|
||||||
|
|
||||||
> the web framework just big enough for a spider
|
|
||||||
|
|
||||||
[GitHub](https://github.com/itsthejoker/spiderweb/)
|
|
||||||
[Get Started](#spiderweb)
|
|
||||||
|
|
||||||
|
|
||||||
![color](#222)
|
|
@ -1 +0,0 @@
|
|||||||
https://www.hubspot.com/hubfs/brand-kit-generator/prototype/fonts/DMSans-Bold.ttf
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 45 KiB |
@ -1,6 +0,0 @@
|
|||||||
- [home](README.md)
|
|
||||||
- [quickstart](quickstart.md)
|
|
||||||
- [responses](responses.md)
|
|
||||||
- Middleware
|
|
||||||
- [middleware](middleware/test.md)
|
|
||||||
- [middleware2](middleware/test2.md)
|
|
@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
> [!ATTENTION]
|
|
||||||
> An alert of type 'attention' using global style 'callout'.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> An alert of type 'tip' using global style 'callout'.
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> An alert of type 'warning' using global style 'callout'.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> An alert of type 'note' using global style 'callout'.
|
|
@ -1,44 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Document</title>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
||||||
<meta name="description" content="Description">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/_media/Favicon-32x32.png">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: "DMSans";
|
|
||||||
src: url('_media/DMSans-Medium.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: "DMSans", sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script>
|
|
||||||
window.$docsify = {
|
|
||||||
name: 'Spiderweb',
|
|
||||||
loadSidebar: true,
|
|
||||||
repo: 'https://github.com/itsthejoker/spiderweb',
|
|
||||||
maxLevel: 3,
|
|
||||||
coverpage: true,
|
|
||||||
'flexible-alerts': {
|
|
||||||
style: 'callout' // or 'flat'
|
|
||||||
},
|
|
||||||
auto2top: true,
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<!-- Docsify v4 -->
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
|
||||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js"></script>
|
|
||||||
<!-- admonitions -->
|
|
||||||
<script src="https://unpkg.com/docsify-plugin-flexible-alerts"></script>
|
|
||||||
<script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1 +0,0 @@
|
|||||||
asdf
|
|
@ -1 +0,0 @@
|
|||||||
asdfawaasdf
|
|
@ -1,93 +0,0 @@
|
|||||||
# quickstart
|
|
||||||
|
|
||||||
Start by installing the package with your favorite package manager:
|
|
||||||
|
|
||||||
<!-- tabs:start -->
|
|
||||||
|
|
||||||
<!-- tab:poetry -->
|
|
||||||
|
|
||||||
```shell
|
|
||||||
poetry add spiderweb-framework
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- tab:pip -->
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pip install spiderweb-framework
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- tab:pipenv -->
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pipenv install spiderweb-framework
|
|
||||||
```
|
|
||||||
|
|
||||||
<!-- tabs:end -->
|
|
||||||
|
|
||||||
Then, create a new file and drop this in it:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from spiderweb import SpiderwebRouter
|
|
||||||
from spiderweb.response import HttpResponse
|
|
||||||
|
|
||||||
app = SpiderwebRouter()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("HELLO, WORLD!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.start()
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the dev server by running `python {yourfile.py}` and navigating to `http://localhost:8000/` in your browser. You should see `HELLO, WORLD!` displayed on the page. Press `Ctrl+C` to stop the server.
|
|
||||||
|
|
||||||
That's it! You've got a working web app. Let's take a look at what these few lines of code are doing:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from spiderweb import SpiderwebRouter
|
|
||||||
```
|
|
||||||
|
|
||||||
The `SpiderwebRouter` class is the main object that everything stems from in `spiderweb`. It's where you'll set your options, your routes, and more.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from spiderweb.response import HttpResponse
|
|
||||||
```
|
|
||||||
|
|
||||||
Rather than trying to infer what you want, spiderweb wants you to be specific about what you want it to do. Part of that is the One Response Rule:
|
|
||||||
|
|
||||||
> Every view must return a Response, and each Response must be a specific type.
|
|
||||||
|
|
||||||
There are four different types of responses; if you want to skip ahead, hop over to [the responses page](responses.md) to learn more. For this example, we'll focus on `HttpResponse`, which is the base response.
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = SpiderwebRouter()
|
|
||||||
```
|
|
||||||
|
|
||||||
This line creates a new instance of the `SpiderwebRouter` class and assigns it to the variable `app`. This is the object that will handle all of your requests and responses. If you need to pass any options into spiderweb, you'll do that here.
|
|
||||||
|
|
||||||
```python
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("HELLO, WORLD!")
|
|
||||||
```
|
|
||||||
|
|
||||||
This is an example view. There are a few things to note here:
|
|
||||||
|
|
||||||
- The `@app.route("/")` decorator tells spiderweb that this view should be called when the user navigates to the root of the site.
|
|
||||||
- The `def index(request):` function is the view itself. It takes a single argument, `request`, which is a `Request` object that contains all the information about the incoming request.
|
|
||||||
- The `return HttpResponse("HELLO, WORLD!")` line is the response. In this case, it's a simple `HttpResponse` object that contains the string `HELLO, WORLD!`. This will be sent back to the user's browser.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> Every view must accept a `request` object as its first argument. This object contains all the information about the incoming request, including headers, cookies, and more.
|
|
||||||
>
|
|
||||||
> There's more that we can pass in, but for now, we'll keep it simple.
|
|
||||||
|
|
||||||
```python
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.start()
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you finish setting up your app, it's time to start it! You can start the dev server by just calling `app.start()` (and its counterpart `app.stop()` to stop it). This will start a simple server on `localhost:8000` that you can access in your browser. It's not a secure server; don't even think about using it in production. It's just good enough for development.
|
|
||||||
|
|
||||||
Now that your app is done, you can also run it with Gunicorn by running `gunicorn --workers=2 {yourfile}:app` in your terminal. This will start a Gunicorn server on `localhost:8000` that you can access in your browser and is a little more robust than the dev server.
|
|
@ -1,3 +0,0 @@
|
|||||||
# responses
|
|
||||||
|
|
||||||
...
|
|
16
example2.py
16
example2.py
@ -55,15 +55,15 @@ app = SpiderwebRouter(
|
|||||||
],
|
],
|
||||||
staticfiles_dirs=["static_files"],
|
staticfiles_dirs=["static_files"],
|
||||||
routes=[
|
routes=[
|
||||||
("/", index),
|
["/", index],
|
||||||
("/redirect", redirect),
|
["/redirect", redirect],
|
||||||
("/json", json),
|
["/json", json],
|
||||||
("/error", error),
|
["/error", error],
|
||||||
("/middleware", middleware),
|
["/middleware", middleware],
|
||||||
("/example/<int:id>", example),
|
["/example/<int:id>", example],
|
||||||
("/form", form, {"allowed_methods": ["GET", "POST"], "csrf_exempt": True}),
|
["/form", form, {"allowed_methods": ["GET", "POST"], "csrf_exempt": True}],
|
||||||
],
|
],
|
||||||
error_routes={405: http405},
|
error_routes={"405": http405},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
148
poetry.lock
generated
148
poetry.lock
generated
@ -11,25 +11,6 @@ files = [
|
|||||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "attrs"
|
|
||||||
version = "24.2.0"
|
|
||||||
description = "Classes Without Boilerplate"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
|
|
||||||
{file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
|
||||||
cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
|
||||||
dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
|
||||||
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
|
||||||
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
|
||||||
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "24.8.0"
|
version = "24.8.0"
|
||||||
@ -178,90 +159,6 @@ files = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "coverage"
|
|
||||||
version = "7.6.1"
|
|
||||||
description = "Code coverage measurement for Python"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
files = [
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"},
|
|
||||||
{file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"},
|
|
||||||
{file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"},
|
|
||||||
{file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"},
|
|
||||||
{file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"},
|
|
||||||
{file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"},
|
|
||||||
{file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"},
|
|
||||||
{file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"},
|
|
||||||
{file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
toml = ["tomli"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "43.0.0"
|
version = "43.0.0"
|
||||||
@ -367,38 +264,6 @@ setproctitle = ["setproctitle"]
|
|||||||
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
|
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
|
||||||
tornado = ["tornado (>=0.2)"]
|
tornado = ["tornado (>=0.2)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hypothesis"
|
|
||||||
version = "6.111.2"
|
|
||||||
description = "A library for property-based testing"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
files = [
|
|
||||||
{file = "hypothesis-6.111.2-py3-none-any.whl", hash = "sha256:055e8228958e22178d6077e455fd86a72044d02dac130dbf9c8b31e161b9809c"},
|
|
||||||
{file = "hypothesis-6.111.2.tar.gz", hash = "sha256:0496ad28c7240ee9ba89fcc7fb1dc74e89f3e40fbcbbb5f73c0091558dec8e6e"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
attrs = ">=22.2.0"
|
|
||||||
sortedcontainers = ">=2.1.0,<3.0.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.70)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.13)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"]
|
|
||||||
cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
|
|
||||||
codemods = ["libcst (>=0.3.16)"]
|
|
||||||
crosshair = ["crosshair-tool (>=0.0.70)", "hypothesis-crosshair (>=0.0.13)"]
|
|
||||||
dateutil = ["python-dateutil (>=1.4)"]
|
|
||||||
django = ["django (>=3.2)"]
|
|
||||||
dpcontracts = ["dpcontracts (>=0.4)"]
|
|
||||||
ghostwriter = ["black (>=19.10b0)"]
|
|
||||||
lark = ["lark (>=0.10.1)"]
|
|
||||||
numpy = ["numpy (>=1.17.3)"]
|
|
||||||
pandas = ["pandas (>=1.1)"]
|
|
||||||
pytest = ["pytest (>=4.6)"]
|
|
||||||
pytz = ["pytz (>=2014.1)"]
|
|
||||||
redis = ["redis (>=3.0.0)"]
|
|
||||||
zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.7"
|
version = "3.7"
|
||||||
@ -762,17 +627,6 @@ files = [
|
|||||||
{file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
|
{file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sortedcontainers"
|
|
||||||
version = "2.4.0"
|
|
||||||
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
|
|
||||||
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.12.2"
|
version = "4.12.2"
|
||||||
@ -787,4 +641,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "17f5dc4b157da57ad75a6f6aa3feb7adfa07500b805d5e79d1f09d640964949f"
|
content-hash = "84633fc94c48c2a05b5ec77367ad29f327be1dc249a6e4cb76b50ebbe14739b5"
|
||||||
|
@ -41,8 +41,6 @@ ruff = "^0.5.5"
|
|||||||
pytest = "^8.3.2"
|
pytest = "^8.3.2"
|
||||||
black = "^24.8.0"
|
black = "^24.8.0"
|
||||||
gunicorn = "^23.0.0"
|
gunicorn = "^23.0.0"
|
||||||
hypothesis = "^6.111.2"
|
|
||||||
coverage = "^7.6.1"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
@ -55,32 +53,3 @@ Homepage = "https://github.com/itsthejoker/spiderweb"
|
|||||||
Documentation = "https://github.com/itsthejoker/spiderweb"
|
Documentation = "https://github.com/itsthejoker/spiderweb"
|
||||||
Repository = "https://git.joekaufeld.com/jkaufeld/spiderweb"
|
Repository = "https://git.joekaufeld.com/jkaufeld/spiderweb"
|
||||||
"Bug Tracker" = "https://github.com/itsthejoker/spiderweb/issues"
|
"Bug Tracker" = "https://github.com/itsthejoker/spiderweb/issues"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
minversion = "6.0"
|
|
||||||
addopts = ["--maxfail=2", "-rf"]
|
|
||||||
|
|
||||||
[tool.coverage.run]
|
|
||||||
branch = true
|
|
||||||
omit = ["conftest.py"]
|
|
||||||
|
|
||||||
[tool.coverage.report]
|
|
||||||
# Regexes for lines to exclude from consideration
|
|
||||||
exclude_also = [
|
|
||||||
# Don't complain about missing debug-only code:
|
|
||||||
"def __repr__",
|
|
||||||
"if self\\.debug",
|
|
||||||
|
|
||||||
# Don't complain if tests don't hit defensive assertion code:
|
|
||||||
"raise AssertionError",
|
|
||||||
"raise NotImplementedError",
|
|
||||||
|
|
||||||
# Don't complain if non-runnable code isn't run:
|
|
||||||
"if 0:",
|
|
||||||
"if __name__ == .__main__.:",
|
|
||||||
|
|
||||||
# Don't complain about abstract methods, they aren't run:
|
|
||||||
"@(abc\\.)?abstractmethod",
|
|
||||||
]
|
|
||||||
|
|
||||||
ignore_errors = true
|
|
@ -1,7 +1,7 @@
|
|||||||
from peewee import DatabaseProxy
|
from peewee import DatabaseProxy
|
||||||
|
|
||||||
DEFAULT_ALLOWED_METHODS = ["GET"]
|
DEFAULT_ALLOWED_METHODS = ["GET"]
|
||||||
DEFAULT_ENCODING = "UTF-8"
|
DEFAULT_ENCODING = "ISO-8859-1"
|
||||||
__version__ = "0.10.0"
|
__version__ = "0.10.0"
|
||||||
|
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||||
|
@ -5,6 +5,9 @@ class IntConverter:
|
|||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
class StrConverter:
|
class StrConverter:
|
||||||
regex = r"[^/]+"
|
regex = r"[^/]+"
|
||||||
@ -13,6 +16,9 @@ class StrConverter:
|
|||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
class FloatConverter:
|
class FloatConverter:
|
||||||
regex = r"\d+\.\d+"
|
regex = r"\d+\.\d+"
|
||||||
@ -20,3 +26,6 @@ class FloatConverter:
|
|||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
||||||
|
@ -11,7 +11,7 @@ def http403(request):
|
|||||||
|
|
||||||
def http404(request):
|
def http404(request):
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
data={"error": f"Route `{request.path}` not found"}, status_code=404
|
data={"error": f"Route {request.url} not found"}, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from threading import Thread
|
|||||||
from typing import Optional, Callable
|
from typing import Optional, Callable
|
||||||
from wsgiref.simple_server import WSGIServer
|
from wsgiref.simple_server import WSGIServer
|
||||||
|
|
||||||
from jinja2 import BaseLoader, Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from peewee import Database, SqliteDatabase
|
from peewee import Database, SqliteDatabase
|
||||||
|
|
||||||
from spiderweb.middleware import MiddlewareMixin
|
from spiderweb.middleware import MiddlewareMixin
|
||||||
@ -46,8 +46,8 @@ class SpiderwebRouter(LocalServerMixin, MiddlewareMixin, RoutesMixin, FernetMixi
|
|||||||
middleware: list[str] = None,
|
middleware: list[str] = None,
|
||||||
append_slash: bool = False,
|
append_slash: bool = False,
|
||||||
staticfiles_dirs: list[str] = None,
|
staticfiles_dirs: list[str] = None,
|
||||||
routes: list[tuple[str, Callable] | tuple[str, Callable, dict]] = None,
|
routes: list[list[str | Callable | dict]] = None,
|
||||||
error_routes: dict[int, Callable] = None,
|
error_routes: dict[str, Callable] = None,
|
||||||
secret_key: str = None,
|
secret_key: str = None,
|
||||||
session_max_age=60 * 60 * 24 * 14, # 2 weeks
|
session_max_age=60 * 60 * 24 * 14, # 2 weeks
|
||||||
session_cookie_name="swsession",
|
session_cookie_name="swsession",
|
||||||
@ -100,14 +100,10 @@ class SpiderwebRouter(LocalServerMixin, MiddlewareMixin, RoutesMixin, FernetMixi
|
|||||||
if self.routes:
|
if self.routes:
|
||||||
self.add_routes()
|
self.add_routes()
|
||||||
|
|
||||||
if self.error_routes:
|
|
||||||
self.add_error_routes()
|
|
||||||
|
|
||||||
if self.templates_dirs:
|
if self.templates_dirs:
|
||||||
self.template_loader = Environment(loader=FileSystemLoader(self.templates_dirs))
|
self.env = Environment(loader=FileSystemLoader(self.templates_dirs))
|
||||||
else:
|
else:
|
||||||
self.template_loader = None
|
self.env = None
|
||||||
self.string_loader = Environment(loader=BaseLoader())
|
|
||||||
|
|
||||||
if self.staticfiles_dirs:
|
if self.staticfiles_dirs:
|
||||||
for static_dir in self.staticfiles_dirs:
|
for static_dir in self.staticfiles_dirs:
|
||||||
@ -135,6 +131,7 @@ class SpiderwebRouter(LocalServerMixin, MiddlewareMixin, RoutesMixin, FernetMixi
|
|||||||
rendered_output = resp.render()
|
rendered_output = resp.render()
|
||||||
if not isinstance(rendered_output, list):
|
if not isinstance(rendered_output, list):
|
||||||
rendered_output = [rendered_output]
|
rendered_output = [rendered_output]
|
||||||
|
|
||||||
encoded_resp = [
|
encoded_resp = [
|
||||||
chunk.encode(DEFAULT_ENCODING) if isinstance(chunk, str) else chunk
|
chunk.encode(DEFAULT_ENCODING) if isinstance(chunk, str) else chunk
|
||||||
for chunk in rendered_output
|
for chunk in rendered_output
|
||||||
@ -186,12 +183,12 @@ class SpiderwebRouter(LocalServerMixin, MiddlewareMixin, RoutesMixin, FernetMixi
|
|||||||
def prepare_and_fire_response(self, start_response, request, resp) -> list[bytes]:
|
def prepare_and_fire_response(self, start_response, request, resp) -> list[bytes]:
|
||||||
try:
|
try:
|
||||||
if isinstance(resp, dict):
|
if isinstance(resp, dict):
|
||||||
return self.fire_response(start_response, request, JsonResponse(data=resp))
|
self.fire_response(start_response, request, JsonResponse(data=resp))
|
||||||
if isinstance(resp, TemplateResponse):
|
if isinstance(resp, TemplateResponse):
|
||||||
resp.set_template_loader(self.template_loader)
|
resp.set_template_loader(self.env)
|
||||||
resp.set_string_loader(self.string_loader)
|
|
||||||
|
|
||||||
self.process_response_middleware(request, resp)
|
for middleware in self.middleware:
|
||||||
|
middleware.process_response(request, resp)
|
||||||
|
|
||||||
return self.fire_response(start_response, request, resp)
|
return self.fire_response(start_response, request, resp)
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Session(SpiderwebModel):
|
|||||||
user_agent = TextField()
|
user_agent = TextField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table_name = "spiderweb_sessions"
|
table_name = 'spiderweb_sessions'
|
||||||
|
|
||||||
|
|
||||||
class SessionMiddleware(SpiderwebMiddleware):
|
class SessionMiddleware(SpiderwebMiddleware):
|
||||||
@ -68,7 +68,7 @@ class SessionMiddleware(SpiderwebMiddleware):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# if a new session has been requested, ignore everything else and make that happen
|
# if a new session has been requested, ignore everything else and make that happen
|
||||||
if request._session["new_session"] is True:
|
if request._session["new_session"]:
|
||||||
# we generated a new one earlier, so we can use it now
|
# we generated a new one earlier, so we can use it now
|
||||||
session_key = request._session["id"]
|
session_key = request._session["id"]
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
@ -76,8 +76,6 @@ class SessionMiddleware(SpiderwebMiddleware):
|
|||||||
session_key,
|
session_key,
|
||||||
**cookie_settings,
|
**cookie_settings,
|
||||||
)
|
)
|
||||||
if not is_jsonable(request.SESSION):
|
|
||||||
raise ValueError("Session data is not JSON serializable.")
|
|
||||||
session = Session(
|
session = Session(
|
||||||
session_key=session_key,
|
session_key=session_key,
|
||||||
session_data=json.dumps(request.SESSION),
|
session_data=json.dumps(request.SESSION),
|
||||||
@ -99,6 +97,18 @@ class SessionMiddleware(SpiderwebMiddleware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
session = request.META["SESSION"]
|
session = request.META["SESSION"]
|
||||||
|
if not session:
|
||||||
|
if not is_jsonable(request.SESSION):
|
||||||
|
raise ValueError("Session data is not JSON serializable.")
|
||||||
|
session = Session(
|
||||||
|
session_key=session_key,
|
||||||
|
session_data=json.dumps(request.SESSION),
|
||||||
|
created_at=datetime.now(),
|
||||||
|
last_active=datetime.now(),
|
||||||
|
ip_address=request.META.get("client_address"),
|
||||||
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
|
)
|
||||||
|
else:
|
||||||
session.session_data = json.dumps(request.SESSION)
|
session.session_data = json.dumps(request.SESSION)
|
||||||
session.last_active = datetime.now()
|
session.last_active = datetime.now()
|
||||||
session.save()
|
session.save()
|
||||||
|
@ -10,6 +10,7 @@ from spiderweb.constants import REGEX_COOKIE_NAME
|
|||||||
from spiderweb.exceptions import GeneralException
|
from spiderweb.exceptions import GeneralException
|
||||||
from spiderweb.request import Request
|
from spiderweb.request import Request
|
||||||
|
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
|
|
||||||
@ -127,39 +128,20 @@ class RedirectResponse(HttpResponse):
|
|||||||
|
|
||||||
|
|
||||||
class TemplateResponse(HttpResponse):
|
class TemplateResponse(HttpResponse):
|
||||||
def __init__(
|
def __init__(self, request: Request, template=None, *args, **kwargs):
|
||||||
self,
|
|
||||||
request: Request,
|
|
||||||
template_path: str = None,
|
|
||||||
template_string: str = None,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.context["request"] = request
|
self.context["request"] = request
|
||||||
self.template_path = template_path
|
self.template = template
|
||||||
self.template_string = template_string
|
self.loader = None
|
||||||
self.template_loader = None
|
|
||||||
self.string_loader = None
|
|
||||||
self._template = None
|
self._template = None
|
||||||
if not template_path and not template_string:
|
if not template:
|
||||||
raise GeneralException("TemplateResponse requires a template.")
|
raise GeneralException("TemplateResponse requires a template.")
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
if self.template_loader is None:
|
if self.loader is None:
|
||||||
if not self.template_string:
|
raise GeneralException("TemplateResponse requires a template loader.")
|
||||||
raise GeneralException(
|
self._template = self.loader.get_template(self.template)
|
||||||
"TemplateResponse has no loader. Did you set templates_dirs?"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._template = self.string_loader.from_string(self.template_string)
|
|
||||||
else:
|
|
||||||
self._template = self.template_loader.get_template(self.template_path)
|
|
||||||
|
|
||||||
return self._template.render(**self.context)
|
return self._template.render(**self.context)
|
||||||
|
|
||||||
def set_template_loader(self, loader):
|
def set_template_loader(self, env):
|
||||||
self.template_loader = loader
|
self.loader = env
|
||||||
|
|
||||||
def set_string_loader(self, loader):
|
|
||||||
self.string_loader = loader
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Callable, Any, Optional
|
from typing import Callable, Any
|
||||||
|
|
||||||
from spiderweb.constants import DEFAULT_ALLOWED_METHODS
|
from spiderweb.constants import DEFAULT_ALLOWED_METHODS
|
||||||
from spiderweb.converters import * # noqa: F403
|
from spiderweb.converters import * # noqa: F403
|
||||||
@ -30,9 +30,9 @@ class RoutesMixin:
|
|||||||
# ones that start with underscores are the compiled versions, non-underscores
|
# ones that start with underscores are the compiled versions, non-underscores
|
||||||
# are the user-supplied versions
|
# are the user-supplied versions
|
||||||
_routes: dict
|
_routes: dict
|
||||||
routes: list[tuple[str, Callable] | tuple[str, Callable, dict]] = None,
|
routes: list[list[str | Callable | dict]]
|
||||||
_error_routes: dict
|
_error_routes: dict
|
||||||
error_routes: dict[int, Callable]
|
error_routes: dict[str, Callable]
|
||||||
append_slash: bool
|
append_slash: bool
|
||||||
|
|
||||||
def route(self, path, allowed_methods=None) -> Callable:
|
def route(self, path, allowed_methods=None) -> Callable:
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from spiderweb.tests.middleware import ExplodingResponseMiddleware, ExplodingRequestMiddleware
|
|
@ -1,28 +0,0 @@
|
|||||||
from wsgiref.util import setup_testing_defaults
|
|
||||||
|
|
||||||
from peewee import SqliteDatabase
|
|
||||||
|
|
||||||
from spiderweb import SpiderwebRouter
|
|
||||||
|
|
||||||
|
|
||||||
class StartResponse:
|
|
||||||
def __init__(self):
|
|
||||||
self.status = None
|
|
||||||
self.headers = None
|
|
||||||
|
|
||||||
def __call__(self, status, headers):
|
|
||||||
self.status = status
|
|
||||||
self.headers = headers
|
|
||||||
|
|
||||||
def get_headers(self):
|
|
||||||
return {h[0]: h[1] for h in self.headers}
|
|
||||||
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
environ = {}
|
|
||||||
setup_testing_defaults(environ)
|
|
||||||
return (
|
|
||||||
SpiderwebRouter(db=SqliteDatabase("spiderweb-tests.db")),
|
|
||||||
environ,
|
|
||||||
StartResponse(),
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
from spiderweb import SpiderwebMiddleware, Request, HttpResponse, UnusedMiddleware
|
|
||||||
|
|
||||||
|
|
||||||
class ExplodingRequestMiddleware(SpiderwebMiddleware):
|
|
||||||
def process_request(self, request: Request) -> HttpResponse | None:
|
|
||||||
raise UnusedMiddleware("Boom!")
|
|
||||||
|
|
||||||
|
|
||||||
class ExplodingResponseMiddleware(SpiderwebMiddleware):
|
|
||||||
def process_response(self, request: Request, response: HttpResponse) -> HttpResponse | None:
|
|
||||||
raise UnusedMiddleware("Unfinished!")
|
|
@ -1 +0,0 @@
|
|||||||
TEMPLATE! {{ message }}
|
|
@ -1,88 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from peewee import SqliteDatabase
|
|
||||||
|
|
||||||
from spiderweb import SpiderwebRouter, HttpResponse
|
|
||||||
from spiderweb.constants import DEFAULT_ENCODING
|
|
||||||
from spiderweb.middleware.sessions import Session
|
|
||||||
from spiderweb.tests.helpers import setup
|
|
||||||
|
|
||||||
# app = SpiderwebRouter(
|
|
||||||
# middleware=[
|
|
||||||
# "spiderweb.middleware.sessions.SessionMiddleware",
|
|
||||||
# "spiderweb.middleware.csrf.CSRFMiddleware",
|
|
||||||
# "example_middleware.TestMiddleware",
|
|
||||||
# "example_middleware.RedirectMiddleware",
|
|
||||||
# "spiderweb.middleware.pydantic.PydanticMiddleware",
|
|
||||||
# "example_middleware.ExplodingMiddleware",
|
|
||||||
# ],
|
|
||||||
# )
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
if "value" in request.SESSION:
|
|
||||||
request.SESSION['value'] += 1
|
|
||||||
else:
|
|
||||||
request.SESSION['value'] = 0
|
|
||||||
return HttpResponse(body=str(request.SESSION['value']))
|
|
||||||
|
|
||||||
|
|
||||||
def test_session_middleware():
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
app = SpiderwebRouter(
|
|
||||||
middleware=["spiderweb.middleware.sessions.SessionMiddleware"],
|
|
||||||
db=SqliteDatabase("spiderweb-tests.db")
|
|
||||||
)
|
|
||||||
|
|
||||||
app.add_route("/", index)
|
|
||||||
|
|
||||||
environ["HTTP_USER_AGENT"] = "hi"
|
|
||||||
environ["REMOTE_ADDR"] = "1.1.1.1"
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes(str(0), DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
session_key = Session.select().first().session_key
|
|
||||||
environ["HTTP_COOKIE"] = f"swsession={session_key}"
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes(str(1), DEFAULT_ENCODING)]
|
|
||||||
assert app(environ, start_response) == [bytes(str(2), DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
def test_expired_session():
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
app = SpiderwebRouter(
|
|
||||||
middleware=["spiderweb.middleware.sessions.SessionMiddleware"],
|
|
||||||
db=SqliteDatabase("spiderweb-tests.db")
|
|
||||||
)
|
|
||||||
|
|
||||||
app.add_route("/", index)
|
|
||||||
|
|
||||||
environ["HTTP_USER_AGENT"] = "hi"
|
|
||||||
environ["REMOTE_ADDR"] = "1.1.1.1"
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes(str(0), DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
session = Session.select().first()
|
|
||||||
session.created_at = session.created_at - timedelta(seconds=app.session_max_age)
|
|
||||||
session.save()
|
|
||||||
|
|
||||||
environ["HTTP_COOKIE"] = f"swsession={session.session_key}"
|
|
||||||
|
|
||||||
# it shouldn't increment because we get a new session
|
|
||||||
assert app(environ, start_response) == [bytes(str(0), DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
session2 = list(Session.select())[-1]
|
|
||||||
assert session2.session_key != session.session_key
|
|
||||||
|
|
||||||
|
|
||||||
def test_exploding_middleware():
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
app = SpiderwebRouter(
|
|
||||||
middleware=[
|
|
||||||
"spiderweb.tests.middleware.ExplodingRequestMiddleware",
|
|
||||||
"spiderweb.tests.middleware.ExplodingResponseMiddleware",
|
|
||||||
],
|
|
||||||
db=SqliteDatabase("spiderweb-tests.db")
|
|
||||||
)
|
|
||||||
|
|
||||||
app.add_route("/", index)
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes(str(0), DEFAULT_ENCODING)]
|
|
@ -1,173 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from spiderweb import SpiderwebRouter, ConfigError
|
|
||||||
from spiderweb.constants import DEFAULT_ENCODING
|
|
||||||
from spiderweb.exceptions import NoResponseError, SpiderwebNetworkException
|
|
||||||
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
|
||||||
from hypothesis import given, strategies as st
|
|
||||||
|
|
||||||
from spiderweb.tests.helpers import setup
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.text())
|
|
||||||
def test_http_response(text):
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse(text)
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes(text, DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_json_response():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return JsonResponse(data={"message": "text"})
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [bytes('{"message": "text"}', DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_dict_response():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return {"message": "Hello, World!"}
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'{"message": "Hello, World!"}']
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.text())
|
|
||||||
def test_template_response(text):
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
template = "MESSAGE: {{ message }}"
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return TemplateResponse(
|
|
||||||
request, template_string=template, context={"message": text}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b"MESSAGE: " + bytes(text, DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_redirect_response():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return RedirectResponse(location="/redirected")
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'None']
|
|
||||||
assert start_response.get_headers()["Location"] == "/redirected"
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_route_at_server_start():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return RedirectResponse(location="/redirected")
|
|
||||||
|
|
||||||
def view2(request):
|
|
||||||
return HttpResponse("View 2")
|
|
||||||
|
|
||||||
app = SpiderwebRouter(routes=[
|
|
||||||
("/", index, {"allowed_methods": ["GET", "POST"], "csrf_exempt": True}),
|
|
||||||
("/view2", view2),
|
|
||||||
])
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'None']
|
|
||||||
assert start_response.get_headers()["Location"] == "/redirected"
|
|
||||||
|
|
||||||
|
|
||||||
def test_redirect_on_append_slash():
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
app = SpiderwebRouter(append_slash=True)
|
|
||||||
|
|
||||||
@app.route("/hello")
|
|
||||||
def index(request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
environ["PATH_INFO"] = f"/hello"
|
|
||||||
assert app(environ, start_response) == [b'None']
|
|
||||||
assert start_response.get_headers()["Location"] == "/hello/"
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.text())
|
|
||||||
def test_template_response_with_template(text):
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
|
|
||||||
app = SpiderwebRouter(templates_dirs=["spiderweb/tests"])
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
return TemplateResponse(
|
|
||||||
request, "test.html", context={"message": text}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b"TEMPLATE! " + bytes(text, DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_view_returns_none():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
with pytest.raises(NoResponseError):
|
|
||||||
assert app(environ, start_response) == [b'None']
|
|
||||||
|
|
||||||
|
|
||||||
def test_exploding_view():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
raise SpiderwebNetworkException("Boom!")
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [
|
|
||||||
b'Something went wrong.\n\nCode: Boom!\n\nMsg: None\n\nDesc: None'
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_missing_view():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'{"error": "Route `/` not found"}']
|
|
||||||
|
|
||||||
|
|
||||||
def test_missing_view_with_custom_404():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.error(404)
|
|
||||||
def custom_404(request):
|
|
||||||
return HttpResponse("Custom 404")
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'Custom 404']
|
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_error_view():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.error(404)
|
|
||||||
def custom_404(request):
|
|
||||||
...
|
|
||||||
|
|
||||||
with pytest.raises(ConfigError):
|
|
||||||
@app.error(404)
|
|
||||||
def custom_404(request):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
def test_missing_view_with_custom_404_alt():
|
|
||||||
_, environ, start_response = setup()
|
|
||||||
|
|
||||||
def custom_404(request):
|
|
||||||
return HttpResponse("Custom 404 2")
|
|
||||||
|
|
||||||
app = SpiderwebRouter(error_routes={404: custom_404})
|
|
||||||
|
|
||||||
assert app(environ, start_response) == [b'Custom 404 2']
|
|
@ -1,102 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from spiderweb import SpiderwebRouter
|
|
||||||
from spiderweb.constants import DEFAULT_ENCODING
|
|
||||||
from spiderweb.exceptions import ParseError, ConfigError
|
|
||||||
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
|
||||||
from hypothesis import given, strategies as st, assume
|
|
||||||
|
|
||||||
from peewee import SqliteDatabase
|
|
||||||
|
|
||||||
from spiderweb.tests.helpers import setup
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.text())
|
|
||||||
def test_str_converter(text):
|
|
||||||
assume(len(text) > 0)
|
|
||||||
assume("/" not in text)
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/<str:test_input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
environ["PATH_INFO"] = f"/{text}"
|
|
||||||
assert app(environ, start_response) == [bytes(text, DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.text())
|
|
||||||
def test_default_str_converter(text):
|
|
||||||
assume(len(text) > 0)
|
|
||||||
assume("/" not in text)
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/<test_input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
environ["PATH_INFO"] = f"/{text}"
|
|
||||||
assert app(environ, start_response) == [bytes(text, DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_converter():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
with pytest.raises(ParseError):
|
|
||||||
@app.route("/<asdf:test_input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_route():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
...
|
|
||||||
|
|
||||||
with pytest.raises(ConfigError):
|
|
||||||
@app.route("/")
|
|
||||||
def index(request):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
def test_url_with_double_underscore():
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
with pytest.raises(ConfigError):
|
|
||||||
@app.route("/<asdf:test__input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
|
|
||||||
@given(st.integers())
|
|
||||||
def test_int_converter(integer):
|
|
||||||
assume(integer > 0)
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/<int:test_input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
environ["PATH_INFO"] = f"/{integer}"
|
|
||||||
assert app(environ, start_response) == [bytes(str(integer), DEFAULT_ENCODING)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"number",
|
|
||||||
[
|
|
||||||
1.0000000000000002,
|
|
||||||
294744.2324,
|
|
||||||
0000.3,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_float_converter(number):
|
|
||||||
app, environ, start_response = setup()
|
|
||||||
|
|
||||||
@app.route("/<float:test_input>")
|
|
||||||
def index(request, test_input: str):
|
|
||||||
return HttpResponse(test_input)
|
|
||||||
|
|
||||||
environ["PATH_INFO"] = f"/{number}"
|
|
||||||
assert app(environ, start_response) == [bytes(str(number), DEFAULT_ENCODING)]
|
|
40
test.py
40
test.py
@ -1,40 +0,0 @@
|
|||||||
from peewee import *
|
|
||||||
from playhouse.migrate import SqliteMigrator, migrate
|
|
||||||
|
|
||||||
from spiderweb.db import SpiderwebModel
|
|
||||||
|
|
||||||
db = SqliteDatabase("people.db")
|
|
||||||
migrator = SqliteMigrator(db)
|
|
||||||
|
|
||||||
|
|
||||||
class Person(SpiderwebModel):
|
|
||||||
name = CharField()
|
|
||||||
birthday = DateField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db # This model uses the "people.db" database.
|
|
||||||
|
|
||||||
|
|
||||||
class Pet(SpiderwebModel):
|
|
||||||
owner = ForeignKeyField(Person, backref="pets")
|
|
||||||
name = CharField(max_length=40)
|
|
||||||
animal_type = CharField()
|
|
||||||
age = IntegerField(null=True)
|
|
||||||
favorite_color = CharField(null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db # this model uses the "people.db" database
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
db.connect()
|
|
||||||
Pet.check_for_needed_migration()
|
|
||||||
# try:
|
|
||||||
# Pet.check_for_needed_migration()
|
|
||||||
# except:
|
|
||||||
# migrate(
|
|
||||||
# migrator.add_column(
|
|
||||||
# Pet._meta.table_name, 'favorite_color', CharField(null=True)
|
|
||||||
# ),
|
|
||||||
# )
|
|
||||||
db.create_tables([Person, Pet])
|
|
Loading…
Reference in New Issue
Block a user