Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/publish-to-pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,5 @@ jobs:
path: dist
merge-multiple: true

- run: ls -la dist

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
13 changes: 13 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ Changelog

All notable changes to this project will be documented in this file.

[0.5.4] - 2025-05-24
--------------------

Added
^^^^^
- Added ``ArrayElementFilter`` to filter elements in an array against specific filter.

Changed
^^^^^^^
- Updated ``ArrayElementValidator`` to support validators directly.
- Updated ``IsDataclassValidator`` to also check against their types, including nested dataclasses, lists, and dictionaries.


[0.5.3] - 2025-04-28
--------------------

Expand Down
5 changes: 0 additions & 5 deletions docs/source/guides/frontend_validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ Example implementation
.. code-block:: python

from flask import Response, Flask
from flask_inputfilter import InputFilter
from flask_inputfilter.conditions import ExactlyOneOfCondition
from flask_inputfilter.enums import RegexEnum
from flask_inputfilter.filters import StringTrimFilter, ToIntegerFilter, ToNullFilter
from flask_inputfilter.validators import IsIntegerValidator, IsStringValidator, RegexValidator

app = Flask(__name__)

Expand Down
6 changes: 0 additions & 6 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ Definition

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.conditions import ExactlyOneOfCondition
from flask_inputfilter.enums import RegexEnum
from flask_inputfilter.filters import StringTrimFilter, ToIntegerFilter, ToNullFilter
from flask_inputfilter.validators import IsIntegerValidator, IsStringValidator, RegexValidator

class UpdateZipcodeInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand Down
5 changes: 0 additions & 5 deletions docs/source/options/condition.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ Example

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.conditions import OneOfCondition
from flask_inputfilter.filters import StringTrimFilter
from flask_inputfilter.validators import IsStringValidator

class TestInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand Down
23 changes: 16 additions & 7 deletions docs/source/options/copy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ Basic Copy Integration

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.filters import StringSlugifyFilter

class MyInputFilter(InputFilter):
def __init__(self):
super().__init__()

self.add(
"username"
"username",
validator=[
IsStringValidator()
]
)

self.add(
Expand All @@ -52,9 +52,6 @@ The coping can also be used as a chain.

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.filters import StringSlugifyFilter, ToUpperFilter, ToLowerFilter

class MyInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand All @@ -80,3 +77,15 @@ The coping can also be used as a chain.
copy="escapedUsername"
filters=[ToLowerFilter()]
)

# Example usage
# Body: {"username": "Very Important User"}

@app.route("/test", methods=["GET"])
@MyInputFilter.validate()
def test_route():
validated_data = g.validated_data

# Contains the same value as username but escaped eg. "very-important-user"
# and in upper-case eg. "VERY-IMPORTANT-USER"
print(validated_data["upperEscapedUsername"])
3 changes: 0 additions & 3 deletions docs/source/options/deserialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ into an instance of the model class, if there is a model class set.

.. code-block:: python

from flask_inputfilter import InputFilter
from dataclasses import dataclass


Expand All @@ -50,7 +49,6 @@ You can also use deserialization in your Flask routes:
.. code-block:: python

from flask import Flask, jsonify, g
from flask_inputfilter import InputFilter


class User:
Expand Down Expand Up @@ -82,7 +80,6 @@ You can also use deserialization outside of Flask routes:
.. code-block:: python

from flask import Flask, jsonify, g
from flask_inputfilter import InputFilter


class User:
Expand Down
2 changes: 0 additions & 2 deletions docs/source/options/external_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ Basic External API Integration

.. code-block:: python

from flask_inputfilter import InputFilter

class MyInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand Down
4 changes: 1 addition & 3 deletions docs/source/options/filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ Example

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.filters import StringTrimFilter, ToLowerFilter

class TestInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand All @@ -39,6 +36,7 @@ Example
Available Filters
-----------------

- `ArrayElementFilter <#flask_inputfilter.filters.ArrayElementFilter>`_
- `ArrayExplodeFilter <#flask_inputfilter.filters.ArrayExplodeFilter>`_
- `Base64ImageDownscaleFilter <#flask_inputfilter.filters.Base64ImageDownscaleFilter>`_
- `Base64ImageResizeFilter <#flask_inputfilter.filters.Base64ImageResizeFilter>`_
Expand Down
3 changes: 0 additions & 3 deletions docs/source/options/special_validator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ Example

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.validators import NotValidator, IsIntegerValidator

class NotIntegerInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand Down
3 changes: 0 additions & 3 deletions docs/source/options/validator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ Example

.. code-block:: python

from flask_inputfilter import InputFilter
from flask_inputfilter.validators import IsIntegerValidator, RangeValidator

class UpdatePointsInputFilter(InputFilter):
def __init__(self):
super().__init__()
Expand Down
149 changes: 149 additions & 0 deletions examples/example 1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Flask-InputFilter Example Application

This example demonstrates various use cases of the Flask-InputFilter package, showing how to implement input validation and filtering in a Flask application.

## Project Structure

```
example 1/
├── app.py # Main Flask application
├── filters/ # InputFilter classes
│ ├── __init__.py # Package exports
│ ├── product_inputfilter.py # Product input validation
│ ├── profile_inputfilter.py # Profile input validation
│ └── user_inputfilter.py # User input validation
├── test.http # HTTP test requests
└── README.md # This file
```

## Features Demonstrated

1. Basic filtering with required fields
2. Nested filtering with complex objects
3. List filtering with type validation
4. Using the `@validate()` decorator for automatic validation
5. Modular filter organization

## Setup

1. Make sure you have Flask and Flask-InputFilter installed:
```bash
pip install flask flask-inputfilter
```

2. Run the example application:
```bash
python app.py
```

The server will start on `http://localhost:5000`.

## Testing the Endpoints

You can use the provided `test.http` file to test the endpoints. This file contains example requests for both successful and error cases.

### Available Endpoints

1. `POST /api/user`
- Creates a new user
- Required fields: name, age, email
- Uses StringTrimFilter and ToIntegerFilter for data cleaning

2. `POST /api/profile`
- Creates a new profile with nested user and address information
- Demonstrates nested filtering with multiple InputFilter classes
- Optional phone number field

3. `POST /api/product`
- Creates a new product
- Demonstrates list validation for tags
- Required fields: name, price
- Optional tags field as a list of strings

## Example Requests

### Successful User Creation
```json
{
"name": "John Doe",
"age": 30,
"email": "[email protected]"
}
```

### Successful Profile Creation
```json
{
"user": {
"name": "John Doe",
"age": 30,
"email": "[email protected]"
},
"address": {
"street": "123 Main St",
"city": "New York",
"zip_code": 10001
},
"phone": "+1234567890"
}
```

### Successful Product Creation
```json
{
"name": "Laptop",
"price": 999,
"tags": [
"electronics",
"sports"
]
}
```

## Key Features

1. **Modular Organization**
- Each InputFilter class in its own file
- Easy to maintain and reuse
- Clear separation of concerns

2. **Decorator-based Validation**
- Use `@InputFilter.validate()` to automatically validate request data
- Access validated data through Flask's `g.validated_data`

3. **Field Configuration**
- Add fields in `__init__` using `self.add()`
- Configure required fields and filters
- Chain multiple filters for complex transformations

4. **Built-in Filters**
- StringTrimFilter: Removes leading/trailing whitespace
- ToIntegerFilter: Converts to integer
- ToFloatFilter: Converts to float
- ToStringFilter: Converts to string

5. **Error Handling**
- Automatic 400 responses for validation errors
- Detailed error messages in JSON format

## Best Practices

1. Organize InputFilter classes in separate files
2. Use `__init__.py` to expose filter classes
3. Always call `super().__init__()` in your InputFilter classes
4. Use appropriate filters for data type conversion
5. Chain filters when multiple transformations are needed
6. Use the `@validate()` decorator for automatic validation
7. Access validated data through `g.validated_data`

## Error Handling

The application demonstrates various validation errors:
- Missing required fields
- Invalid email format
- Invalid age format (must be integer)
- Invalid price format
- Invalid tags format (must be a list of strings)
- Invalid nested data structures

Each error will return a 400 status code with a descriptive error message.
Empty file added examples/example 1/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions examples/example 1/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from flask import Flask, Response, g

from .filters import ProductInputFilter, ProfileInputFilter, UserInputFilter

app = Flask(__name__)


@app.route("/api/user", methods=["POST"], endpoint="create-user")
@UserInputFilter.validate()
def create_user():
return Response(
{"message": "User created successfully", "data": g.validated_data},
201,
)


@app.route("/api/profile", methods=["POST"], endpoint="create-profile")
@ProfileInputFilter.validate()
def create_profile():
return Response(
{
"message": "Profile created successfully",
"data": g.validated_data,
},
201,
)


@app.route("/api/product", methods=["POST"], endpoint="create-product")
@ProductInputFilter.validate()
def create_products():
return Response(
{
"message": "Products created successfully",
"data": g.validated_data,
},
201,
)


if __name__ == "__main__":
app.run(debug=True)
3 changes: 3 additions & 0 deletions examples/example 1/filters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .product_inputfilter import ProductInputFilter
from .profile_inputfilter import ProfileInputFilter
from .user_inputfilter import UserInputFilter
Loading