%global _empty_manifest_terminate_build 0 Name: python-Starlette-WTF Version: 0.4.3 Release: 1 Summary: Simple integration of Starlette and WTForms. License: MIT URL: https://github.com/muicss/starlette-wtf Source0: https://mirrors.nju.edu.cn/pypi/web/packages/ce/46/00eda53b774e9a5b2ef4edeb7e69f849d49492ee26e97504a06e782ebb0c/Starlette-WTF-0.4.3.tar.gz BuildArch: noarch %description # Starlette-WTF Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. ## Table of Contents - [Installation](#installation) - [Quickstart](#quickstart) - [Creating Forms](#creating-forms) * [The StarletteForm Class](#the-starletteform-class) * [Validation](#validation) * [Async Custom Validators](#async-custom-validators) - [CSRF Protection](#csrf-protection) * [Setup](#setup) * [Protect Views](#protect-views) * [HTML Forms](#html-forms) * [JavaScript Requests](#javascript-requests) * [Disable in Unit Tests](#disable-in-unit-tests) * [Configuration](#configuration) - [Development](#development) * [Get the code](#get-the-code) * [Run unit tests](#run-unit-tests) ## Installation Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): ```bash $ pip install starlette-wtf ``` ## Quickstart The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. First, install the dependencies for this quickstart: ```bash $ pip install starlette starlette-wtf jinja2 uvicorn ``` Next, create a Python file (app.py) with the following code: ```python from jinja2 import Template from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette.responses import PlainTextResponse, HTMLResponse from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect from wtforms import StringField from wtforms.validators import DataRequired class MyForm(StarletteForm): name = StringField('name', validators=[DataRequired()]) template = Template('''
{{ form.csrf_token }}
{{ form.name(placeholder='Name') }} {% if form.name.errors -%} {{ form.name.errors[0] }} {%- endif %}
''') app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) @app.route('/', methods=['GET', 'POST']) @csrf_protect async def index(request): """GET|POST /: form handler """ form = await MyForm.from_formdata(request) if await form.validate_on_submit(): return PlainTextResponse('SUCCESS') html = template.render(form=form) return HTMLResponse(html) ``` Finally, run the app using the following command: ```bash $ uvicorn app:app ``` ## Creating Forms ### The StarletteForm Class Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired, Email, EqualTo from wtforms.widgets import PasswordInput class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) ``` Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: ```python @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ form = await CreateAccountForm.from_formdata(request) return PlainTextResponse() ``` ### Validation The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): ```python from jinja2 import Template from starlette.applications import Starlette from starlette.responses import (PlainTextResponse, RedirectResponse, HTMLResponse) template = Template('''

Create Account

{{ form.email(placeholder='Email address', autofocus='true', type='email', spellcheck='false') }} {% if form.email.errors -%} {{ form.email.errors[0] }} {%- endif %}
{{ form.password(placeholder="Password") }} {% if form.password.errors -%} {{ form.password.errors[0] }} {%- endif %}
{{ form.password_confirm(placeholder="Confirm password") }} {% if form.password_confirm.errors -%} {{ form.password_confirm.errors[0] }} {%- endif %}
''') app = Starlette() @app.route('/', methods=['GET']) async def index(request): """GET /: Return home page """ return PlainTextResponse() @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ # initialize form form = await CreateAccountForm.from_formdata(request) # validate form if await form.validate_on_submit(): # TODO: Save account credentials before returning redirect response return RedirectResponse(url='/', status_code=303) # generate html html = template.render(form=form) # return response status_code = 422 if form.errors else 200 return HTMLResponse(html, status_code=status_code) ``` ### Async Custom Validators The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField, ValidationError from wtforms.validators import DataRequired, Email, EqualTo class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) async def async_validate_email(self, field): """Asynchronous validator to check if email is already in-use """ # replace this with your own code if await make_database_request_here(): raise ValidationError('Email is already in use') ``` ## CSRF Protection In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. ### Setup To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. ```python from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) ``` ### Protect Views Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: ```python from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @app.route('/form-handler', methods=['GET', 'POST']) @csrf_protect async def form_handler(request): """GET|POST /form-handler: Form handler """ # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() @csrf_protect async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` ### HTML Forms When using `StarletteForm` you can render the form's CSRF token field like this: ```html
{{ form.csrf_token }}
``` ### JavaScript Requests When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: ```html ``` ### Disable in Unit Tests To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: ```python from starlette.applications import Starlette from starlette.config import environ from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, enable=!environ.get('TESTING', False), csrf_secret='***REPLACEME2***') ]) ``` ### Configuration `CSRFProtectMiddleware` accepts the following options: | Argument | Description | ----------------- | ----------- | enabled | If true, enables CSRF protection. Default to True. | csrf_secret | The CSRF token signing key. | csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" | csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. | csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] | csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. ## Development ### Get the code Starlette-WTF is actively developed on GitHub. You can clone the repository using git: ```bash $ git clone git@github.com:muicss/starlette-wtf.git ``` Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: ```bash $ python setup.py develop ``` ### Run unit tests To install unit test dependencies: ```bash $ pip install -e .[test] ``` To run unit tests: ```bash $ pytest ``` %package -n python3-Starlette-WTF Summary: Simple integration of Starlette and WTForms. Provides: python-Starlette-WTF BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: python3-pip %description -n python3-Starlette-WTF # Starlette-WTF Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. ## Table of Contents - [Installation](#installation) - [Quickstart](#quickstart) - [Creating Forms](#creating-forms) * [The StarletteForm Class](#the-starletteform-class) * [Validation](#validation) * [Async Custom Validators](#async-custom-validators) - [CSRF Protection](#csrf-protection) * [Setup](#setup) * [Protect Views](#protect-views) * [HTML Forms](#html-forms) * [JavaScript Requests](#javascript-requests) * [Disable in Unit Tests](#disable-in-unit-tests) * [Configuration](#configuration) - [Development](#development) * [Get the code](#get-the-code) * [Run unit tests](#run-unit-tests) ## Installation Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): ```bash $ pip install starlette-wtf ``` ## Quickstart The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. First, install the dependencies for this quickstart: ```bash $ pip install starlette starlette-wtf jinja2 uvicorn ``` Next, create a Python file (app.py) with the following code: ```python from jinja2 import Template from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette.responses import PlainTextResponse, HTMLResponse from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect from wtforms import StringField from wtforms.validators import DataRequired class MyForm(StarletteForm): name = StringField('name', validators=[DataRequired()]) template = Template('''
{{ form.csrf_token }}
{{ form.name(placeholder='Name') }} {% if form.name.errors -%} {{ form.name.errors[0] }} {%- endif %}
''') app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) @app.route('/', methods=['GET', 'POST']) @csrf_protect async def index(request): """GET|POST /: form handler """ form = await MyForm.from_formdata(request) if await form.validate_on_submit(): return PlainTextResponse('SUCCESS') html = template.render(form=form) return HTMLResponse(html) ``` Finally, run the app using the following command: ```bash $ uvicorn app:app ``` ## Creating Forms ### The StarletteForm Class Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired, Email, EqualTo from wtforms.widgets import PasswordInput class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) ``` Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: ```python @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ form = await CreateAccountForm.from_formdata(request) return PlainTextResponse() ``` ### Validation The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): ```python from jinja2 import Template from starlette.applications import Starlette from starlette.responses import (PlainTextResponse, RedirectResponse, HTMLResponse) template = Template('''

Create Account

{{ form.email(placeholder='Email address', autofocus='true', type='email', spellcheck='false') }} {% if form.email.errors -%} {{ form.email.errors[0] }} {%- endif %}
{{ form.password(placeholder="Password") }} {% if form.password.errors -%} {{ form.password.errors[0] }} {%- endif %}
{{ form.password_confirm(placeholder="Confirm password") }} {% if form.password_confirm.errors -%} {{ form.password_confirm.errors[0] }} {%- endif %}
''') app = Starlette() @app.route('/', methods=['GET']) async def index(request): """GET /: Return home page """ return PlainTextResponse() @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ # initialize form form = await CreateAccountForm.from_formdata(request) # validate form if await form.validate_on_submit(): # TODO: Save account credentials before returning redirect response return RedirectResponse(url='/', status_code=303) # generate html html = template.render(form=form) # return response status_code = 422 if form.errors else 200 return HTMLResponse(html, status_code=status_code) ``` ### Async Custom Validators The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField, ValidationError from wtforms.validators import DataRequired, Email, EqualTo class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) async def async_validate_email(self, field): """Asynchronous validator to check if email is already in-use """ # replace this with your own code if await make_database_request_here(): raise ValidationError('Email is already in use') ``` ## CSRF Protection In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. ### Setup To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. ```python from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) ``` ### Protect Views Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: ```python from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @app.route('/form-handler', methods=['GET', 'POST']) @csrf_protect async def form_handler(request): """GET|POST /form-handler: Form handler """ # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() @csrf_protect async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` ### HTML Forms When using `StarletteForm` you can render the form's CSRF token field like this: ```html
{{ form.csrf_token }}
``` ### JavaScript Requests When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: ```html ``` ### Disable in Unit Tests To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: ```python from starlette.applications import Starlette from starlette.config import environ from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, enable=!environ.get('TESTING', False), csrf_secret='***REPLACEME2***') ]) ``` ### Configuration `CSRFProtectMiddleware` accepts the following options: | Argument | Description | ----------------- | ----------- | enabled | If true, enables CSRF protection. Default to True. | csrf_secret | The CSRF token signing key. | csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" | csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. | csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] | csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. ## Development ### Get the code Starlette-WTF is actively developed on GitHub. You can clone the repository using git: ```bash $ git clone git@github.com:muicss/starlette-wtf.git ``` Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: ```bash $ python setup.py develop ``` ### Run unit tests To install unit test dependencies: ```bash $ pip install -e .[test] ``` To run unit tests: ```bash $ pytest ``` %package help Summary: Development documents and examples for Starlette-WTF Provides: python3-Starlette-WTF-doc %description help # Starlette-WTF Starlette-WTF is a simple tool for integrating [Starlette](https://www.starlette.io/) and [WTForms](https://wtforms.readthedocs.io/en/stable/). It is modeled on the excellent [Flask-WTF](https://flask-wtf.readthedocs.io) library. ## Table of Contents - [Installation](#installation) - [Quickstart](#quickstart) - [Creating Forms](#creating-forms) * [The StarletteForm Class](#the-starletteform-class) * [Validation](#validation) * [Async Custom Validators](#async-custom-validators) - [CSRF Protection](#csrf-protection) * [Setup](#setup) * [Protect Views](#protect-views) * [HTML Forms](#html-forms) * [JavaScript Requests](#javascript-requests) * [Disable in Unit Tests](#disable-in-unit-tests) * [Configuration](#configuration) - [Development](#development) * [Get the code](#get-the-code) * [Run unit tests](#run-unit-tests) ## Installation Installing Starlette-WTF is simple with [pip](https://pip.pypa.io/en/stable/): ```bash $ pip install starlette-wtf ``` ## Quickstart The following code implements a simple form handler with CSRF protection. The form has a required string field and validation errors are handled by the html template. Note that CSRF protection requires `SessionMiddleware`, `CSRFProtectMiddleware`, `@csrf_protect` and the `csrf_token` field to be added to the HTML form. First, install the dependencies for this quickstart: ```bash $ pip install starlette starlette-wtf jinja2 uvicorn ``` Next, create a Python file (app.py) with the following code: ```python from jinja2 import Template from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette.responses import PlainTextResponse, HTMLResponse from starlette_wtf import StarletteForm, CSRFProtectMiddleware, csrf_protect from wtforms import StringField from wtforms.validators import DataRequired class MyForm(StarletteForm): name = StringField('name', validators=[DataRequired()]) template = Template('''
{{ form.csrf_token }}
{{ form.name(placeholder='Name') }} {% if form.name.errors -%} {{ form.name.errors[0] }} {%- endif %}
''') app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) @app.route('/', methods=['GET', 'POST']) @csrf_protect async def index(request): """GET|POST /: form handler """ form = await MyForm.from_formdata(request) if await form.validate_on_submit(): return PlainTextResponse('SUCCESS') html = template.render(form=form) return HTMLResponse(html) ``` Finally, run the app using the following command: ```bash $ uvicorn app:app ``` ## Creating Forms ### The StarletteForm Class Starlette-WTF provides a form class that makes it easy to add form validation and CSRF protection to Starlette apps. To make a form, subclass the `StarletteForm` class and use [WTForms](https://wtforms.readthedocs.io/) fields, validators and widgets to define the inputs. The `StarletteForm` class inherits from the WTForms `Form` class so you can use WTForms features and methods to add more advanced functionality to your app: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired, Email, EqualTo from wtforms.widgets import PasswordInput class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) ``` Often you will want to initialize form objects using default values on GET requests and from submitted formdata on POST requests. To make this easier you can use the `.from_formdata()` async class method which does this for you automatically: ```python @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ form = await CreateAccountForm.from_formdata(request) return PlainTextResponse() ``` ### Validation The `StarletteForm` class has a useful `.validate_on_submit()` method that performs input validation for POST, PUT, PATCH and DELETE requests and returns a boolean indicating whether or not there were any errors. After validation, errors are available via the `.errors` attribute attached to each input field instance. Note that validation is asynchronous to handle async field validators (see below): ```python from jinja2 import Template from starlette.applications import Starlette from starlette.responses import (PlainTextResponse, RedirectResponse, HTMLResponse) template = Template('''

Create Account

{{ form.email(placeholder='Email address', autofocus='true', type='email', spellcheck='false') }} {% if form.email.errors -%} {{ form.email.errors[0] }} {%- endif %}
{{ form.password(placeholder="Password") }} {% if form.password.errors -%} {{ form.password.errors[0] }} {%- endif %}
{{ form.password_confirm(placeholder="Confirm password") }} {% if form.password_confirm.errors -%} {{ form.password_confirm.errors[0] }} {%- endif %}
''') app = Starlette() @app.route('/', methods=['GET']) async def index(request): """GET /: Return home page """ return PlainTextResponse() @app.route('/create-account', methods=['GET', 'POST']) async def create_account(request): """GET|POST /create-account: Create account form handler """ # initialize form form = await CreateAccountForm.from_formdata(request) # validate form if await form.validate_on_submit(): # TODO: Save account credentials before returning redirect response return RedirectResponse(url='/', status_code=303) # generate html html = template.render(form=form) # return response status_code = 422 if form.errors else 200 return HTMLResponse(html, status_code=status_code) ``` ### Async Custom Validators The `StarletteForm` class allows you to implement asynchronous [WTForms-like custom validators](https://wtforms.readthedocs.io/en/stable/validators/#custom-validators) by adding `async_validate_{fieldname}` methods to your form classes: ```python from starlette_wtf import StarletteForm from wtforms import TextField, PasswordField, ValidationError from wtforms.validators import DataRequired, Email, EqualTo class CreateAccountForm(StarletteForm): email = TextField( 'Email address', validators=[ DataRequired('Please enter your email address'), Email() ] ) password = PasswordField( 'Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please enter your password'), EqualTo('password_confirm', message='Passwords must match') ] ) password_confirm = PasswordField( 'Confirm Password', widget=PasswordInput(hide_value=False), validators=[ DataRequired('Please confirm your password') ] ) async def async_validate_email(self, field): """Asynchronous validator to check if email is already in-use """ # replace this with your own code if await make_database_request_here(): raise ValidationError('Email is already in use') ``` ## CSRF Protection In order to add CSRF protection to your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, second you must configure Starlette-WTF using `CSRFProtectMiddleware`, third you must use the `@csrf_protect` decorator to protect individual endpoints, and fourth you must add the CSRF token to your HTML forms or JavaScript requests. ### Setup To enable CSRF protection for your app, first you must ensure that Starlette's `SessionMiddleware` is enabled, and second you must configure Starlette-WTF using `CSRFProtectMiddleware`. ```python from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, csrf_secret='***REPLACEME2***') ]) ``` ### Protect Views Once Starlette-WTF has been configured using `CSRFProtectMiddleware` you can enable CSRF protection for individual endpoints using the `@csrf_protect` decorator. The `@csrf_protect` decorator will automatically look for `csrf_token` in the form data or in the request headers (`X-CSRFToken`) and it will raise an `HTTPException` if the token is missing or invalid. CSRF token validation will only be performed on submission requests (POST, PUT, PATCH, DELETE). Note that the `@csrf_protect` must run after `@app.route()`: ```python from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @app.route('/form-handler', methods=['GET', 'POST']) @csrf_protect async def form_handler(request): """GET|POST /form-handler: Form handler """ # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with class-based views (e.g. [HTTPEndpoint](https://www.starlette.io/endpoints/)): ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect @csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` The `@csrf_protect` decorator can also be used with bound methods attached to class-based views: ```python from starlette.endpoints import HTTPEndpoint from starlette.responses import PlainTextResponse from starlette_wtf import csrf_protect class Endpoint(HTTPEndpoint): async def get(self, request): # this code will run without a CSRF check return PlainTextResponse() @csrf_protect async def post(self, request): # this code won't run unless the CSRF token has been validated return PlainTextResponse() ``` ### HTML Forms When using `StarletteForm` you can render the form's CSRF token field like this: ```html
{{ form.csrf_token }}
``` ### JavaScript Requests When sending an AJAX request, add the `X-CSRFToken` header to allow Starlette-WTF to perform CSRF validation. For example, in jQuery you can configure all requests to send the token: ```html ``` ### Disable in Unit Tests To disable CSRF protection in unit tests you can toggle the `enabled` attribute in `CSRFProtectionMiddleware`: ```python from starlette.applications import Starlette from starlette.config import environ from starlette.middleware import Middleware from starlette.middleware.sessions import SessionMiddleware from starlette_wtf import CSRFProtectMiddleware app = Starlette(middleware=[ Middleware(SessionMiddleware, secret_key='***REPLACEME1***'), Middleware(CSRFProtectMiddleware, enable=!environ.get('TESTING', False), csrf_secret='***REPLACEME2***') ]) ``` ### Configuration `CSRFProtectMiddleware` accepts the following options: | Argument | Description | ----------------- | ----------- | enabled | If true, enables CSRF protection. Default to True. | csrf_secret | The CSRF token signing key. | csrf_field_name | The CSRF token's field name in the session. Defaults to "csrf_token" | csrf_time_limit | The time limit for each signed token in seconds. Defaults to 3600. | csrf_headers | List of CSRF HTTP header field names. Defaults to ["X-CSRFToken", "X-CSRF-Token"] | csrf_ssl_strict | If enabled, ensures same origin policy on https requests. Defaults to True. ## Development ### Get the code Starlette-WTF is actively developed on GitHub. You can clone the repository using git: ```bash $ git clone git@github.com:muicss/starlette-wtf.git ``` Once you have a copy of the source, you can install it into your site-packages in development mode so you can modify and execute the code: ```bash $ python setup.py develop ``` ### Run unit tests To install unit test dependencies: ```bash $ pip install -e .[test] ``` To run unit tests: ```bash $ pytest ``` %prep %autosetup -n Starlette-WTF-0.4.3 %build %py3_build %install %py3_install install -d -m755 %{buildroot}/%{_pkgdocdir} if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi pushd %{buildroot} if [ -d usr/lib ]; then find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/lib64 ]; then find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/bin ]; then find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/sbin ]; then find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst fi touch doclist.lst if [ -d usr/share/man ]; then find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst fi popd mv %{buildroot}/filelist.lst . mv %{buildroot}/doclist.lst . %files -n python3-Starlette-WTF -f filelist.lst %dir %{python3_sitelib}/* %files help -f doclist.lst %{_docdir}/* %changelog * Fri May 05 2023 Python_Bot - 0.4.3-1 - Package Spec generated