Skip to content

Commit 0c3b09d

Browse files
authored
Merge pull request #5 from LeanderCS/4
4 | Add external api functionality
2 parents 1800ec7 + 256c6b7 commit 0c3b09d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1000
-239
lines changed

DEVELOPMENT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ docker exec -it flask-inputfilter pytest
2121

2222
### Run linting
2323
```bash
24+
docker exec -it flask-inputfilter sh -c "isort ."
25+
docker exec -it flask-inputfilter sh -c "autoflake --in-place --remove-all-unused-imports --ignore-init-module-imports --recursive ."
2426
docker exec -it flask-inputfilter black .
2527
```

EXTERNAL_API.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# External API Functionality in `InputFilter`
2+
3+
This documentation provides a comprehensive overview of the external API functionality available in the `InputFilter` class. It covers the configuration, core methods, and examples of usage for interacting with external APIs.
4+
5+
---
6+
7+
## 1. Overview
8+
9+
The `InputFilter` class includes a mechanism for fetching data from external APIs during the input validation process.
10+
This feature allows dynamic data retrieval based on user inputs, such as validating fields or fetching related data from an external service.
11+
12+
Important to know, the external api functionality runs after all other filters and validators have been executed.
13+
This means that the data fetched from the external API will not be validated or filtered.
14+
15+
---
16+
17+
## 2. Configuration
18+
19+
The external API functionality is configured via the `external_api` parameter in the `add` method. This parameter accepts a dictionary with the following structure:
20+
21+
### `ExternalApiConfig` Fields
22+
23+
| Field | Type | Description |
24+
|------------|--------------------------|-----------------------------------------------------------------------------|
25+
| `url` | `str` | The URL of the external API, with optional placeholders in `{{}}` format. |
26+
| `method` | `str` | The HTTP method to use (e.g., `GET`, `POST`). |
27+
| `params` | `Optional[Dict[str, str]]` | Query parameters for the API, with placeholders allowed. |
28+
| `data_key` | `Optional[str]` | Key in the JSON response to extract the required data. |
29+
| `api_key` | `Optional[str]` | API key for authorization, sent in the `Authorization` header. |
30+
31+
---
32+
33+
## 3. Examples
34+
35+
### 3.1 Basic External API Integration
36+
37+
```python
38+
from flask_inputfilter.InputFilter import InputFilter
39+
40+
class MyInputFilter(InputFilter):
41+
def __init__(self):
42+
super().__init__()
43+
44+
self.add(
45+
"user_id", required=True
46+
)
47+
self.add(
48+
"is_active",
49+
required=True,
50+
external_api={
51+
"url": "https://api.example.com/users/{{user_id}}/status",
52+
"method": "GET",
53+
"data_key": "is_active",
54+
},
55+
)
56+
57+
# Example usage
58+
filter_instance = MyInputFilter()
59+
validated_data = filter_instance.validateData({"user_id": 123})
60+
print(validated_data["is_active"]) # True or False based on API response
61+
```
62+
63+
### 3.2 Using Query Parameters
64+
65+
```python
66+
self.add(
67+
"is_valid",
68+
required=True,
69+
external_api={
70+
"url": "https://api.example.com/validate",
71+
"method": "GET",
72+
"params": {"user": "{{user_id}}", "hash": "{{hash}}"},
73+
"data_key": "is_valid",
74+
},
75+
)
76+
```
77+
78+
This configuration sends the `user_id` and `hash` as query parameters, replacing the placeholders with validated data.
79+
80+
---
81+
82+
### 3.3 Handling Fallback Values
83+
84+
If the external API call fails, a fallback value can be specified:
85+
86+
```python
87+
self.add(
88+
"user_info",
89+
required=True,
90+
fallback={"name": "unknown", "age": 0},
91+
external_api={
92+
"url": "https://api.example.com/user/{{user_id}}",
93+
"method": "GET",
94+
"data_key": "user",
95+
},
96+
)
97+
```
98+
99+
---
100+
101+
## 4. Error Handling
102+
103+
- `ValidationError` is raised when:
104+
- The API call returns a non-200 status code.
105+
- A required field is missing and no fallback/default is provided.
106+
- Validation of the field value fails.
107+
108+
---
109+
110+
## 7. Best Practices
111+
112+
- **Required Fields:** Clearly define required fields and provide fallback values where necessary.
113+
- **Placeholders:** Ensure placeholders in URLs and parameters match the keys in `validated_data`.
114+
- **Fallbacks:** Always provide fallback values for critical fields to avoid disruptions in case of API failure.
115+
- **Security:** Use HTTPS for API calls and secure sensitive data like API keys.
116+
- **Testing:** Mock external API calls during unit testing to avoid dependencies on external systems.
117+
118+
---

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
The `InputFilter` class is used to validate and filter input data in Flask applications.
44
It provides a modular way to clean and ensure that incoming data meets expected format and type requirements before being processed.
55

6+
---
7+
68
## Installation
79

810
```bash
911
pip install flask-inputfilter
1012
```
1113

14+
---
15+
1216
## Quickstart
1317

1418
To use the `InputFilter` class, you need to create a new class that inherits from it and define the fields you want to validate and filter.
@@ -72,6 +76,8 @@ def updateZipcode():
7276
zipcode = data.get('zipcode')
7377
```
7478

79+
---
80+
7581
## Options
7682

7783
The `add` method takes the following options:
@@ -81,16 +87,21 @@ The `add` method takes the following options:
8187
- [`Validator`](src/flask_inputfilter/Validator/README.md)
8288
- [`Default`](#default)
8389
- [`Fallback`](#fallback)
90+
- [`ExternalApi`](EXTERNAL_API.md)
8491

8592
### Required
8693

8794
The `required` option is used to specify if the field is required or not.
88-
If the field is required and not present in the input data, the `validate` method will return a 400 response with the error message.
95+
If the field is required and not present in the input data, the `validate` method will return the `ValidationError` with an error message.
8996

9097
### Default
9198

9299
The `default` option is used to specify a default value to use if the field is not present in the input data.
93100

94101
### Fallback
95102

96-
The `fallback` option is used to specify a fallback value to use if the field is not present in the input data, although it is required or the validation fails.
103+
The `fallback` option is used to specify a fallback value to use if something unexpected happens, for example if the field is required but no value where provides
104+
or the validation fails.
105+
106+
This means that if the field is not required and no value is present, the fallback value will not be used.
107+
In this case you have to use the `default` option.

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ requires = ["setuptools", "wheel"]
33
build-backend = "setuptools.build_meta"
44

55
[tool.black]
6-
line-length = 90
6+
line-length = 79
7+
8+
[tool.isort]
9+
profile = 'black'
10+
line_length = 79

requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
autoflake
2+
black
13
flake8
24
flask==0.10
5+
isort
36
pillow==2.0.0
47
pytest
8+
requests==2.12.0
59
setuptools
610
twine
7-
black

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
from setuptools import setup, find_packages
1+
from setuptools import find_packages, setup
22

33
setup(
44
name="flask_inputfilter",
55
version="0.0.3",
66
author="Leander Cain Slotosch",
77
author_email="[email protected]",
8-
description="A library to filter and validate input data in" "Flask applications",
8+
description="A library to filter and validate input data in"
9+
"Flask applications",
910
long_description=open("README.md").read(),
1011
long_description_content_type="text/markdown",
1112
url="https://github.com/LeanderCS/flask-inputfilter",
@@ -14,6 +15,7 @@
1415
install_requires=[
1516
"Flask>=0.10",
1617
"pillow>=2.0.0",
18+
"requests>=2.12.0",
1719
],
1820
classifiers=[
1921
"Programming Language :: Python :: 3",

src/flask_inputfilter/Enum/RegexEnum.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class RegexEnum(Enum):
1313

1414
ISO_DATE = r"^\d{4}-\d{2}-\d{2}$"
1515
ISO_DATETIME = (
16-
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}" r"(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$"
16+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}"
17+
r"(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$"
1718
)
1819

1920
PHONE_NUMBER = r"^\+?[\d\s\-()]{7,}$"

src/flask_inputfilter/Exception/ValidationError.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@ class ValidationError(Exception):
22
"""
33
This class is used to raise an exception when a validation error occurs.
44
"""
5-
6-
pass

src/flask_inputfilter/Filter/README.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ The `Filter` module contains the filters that can be used to filter the input da
66

77
The following filters are available in the `Filter` module:
88

9-
1. [`ArrayExplodeFilter`](src/flask_inputfilter/Filter/ArrayExplodeFilter.py) - Explodes the input string into an array.
10-
2. [`StringTrimFilter`](src/flask_inputfilter/Filter/StringTrimFilter.py) - Trims the whitespace from the beginning and end of the string.
11-
3. [`ToBooleanFilter`](src/flask_inputfilter/Filter/ToBooleanFilter.py) - Converts the string to a boolean value.
12-
4. [`ToCamelCaseFilter`](src/flask_inputfilter/Filter/ToCamelCaseFilter.py) - Converts the string to camel case.
13-
5. [`ToFloatFilter`](src/flask_inputfilter/Filter/ToFloatFilter.py) - Converts the string to a float value.
14-
5. [`ToIntegerFilter`](src/flask_inputfilter/Filter/ToIntegerFilter.py) - Converts the string to an integer value.
15-
6. [`ToLowerFilter`](src/flask_inputfilter/Filter/ToLowerFilter.py) - Converts the string to lowercase.
16-
7. [`ToNullFilter`](src/flask_inputfilter/Filter/ToNullFilter.py) - Converts the string to `None` if it is already `None` or `''` (empty string).
17-
8. [`ToPascaleCaseFilter`](src/flask_inputfilter/Filter/ToPascaleCaseFilter.py) - Converts the string to pascal case.
18-
9. [`ToSnakeCaseFilter`](src/flask_inputfilter/Filter/ToSnakeCaseFilter.py) - Converts the string to snake case.
19-
9. [`ToStringFilter`](src/flask_inputfilter/Filter/ToStringFilter.py) - Converts the input to a string value.
20-
9. [`ToUpperFilter`](src/flask_inputfilter/Filter/ToUpperFilter.py) - Converts the string to uppercase.
21-
10. [`WhitespaceCollapseFilter`](src/flask_inputfilter/Filter/WhitespaceCollapseFilter.py) - Collapses the whitespace in the string.
9+
1. [`ArrayExplodeFilter`](ArrayExplodeFilter.py) - Explodes the input string into an array.
10+
2. [`SlugifyFilter`](SlugifyFilter.py) - Converts the string to a slug.
11+
3. [`StringTrimFilter`](StringTrimFilter.py) - Trims the whitespace from the beginning and end of the string.
12+
4. [`ToAlphaNumericFilter`](ToAlphaNumericFilter.py) - Converts the string to an alphanumeric string.
13+
5. [`ToBooleanFilter`](ToBooleanFilter.py) - Converts the string to a boolean value.
14+
6. [`ToCamelCaseFilter`](ToCamelCaseFilter.py) - Converts the string to camel case.
15+
7. [`ToFloatFilter`](ToFloatFilter.py) - Converts the string to a float value.
16+
8. [`ToIntegerFilter`](ToIntegerFilter.py) - Converts the string to an integer value.
17+
9. [`ToLowerFilter`](ToLowerFilter.py) - Converts the string to lowercase.
18+
10. [`ToNormalizedUnicodeFilter`](ToNormalizedUnicodeFilter.py) - Normalizes the unicode string.
19+
11. [`ToNullFilter`](ToNullFilter.py) - Converts the string to `None` if it is already `None` or `''` (empty string).
20+
12. [`ToPascaleCaseFilter`](ToPascaleCaseFilter.py) - Converts the string to pascal case.
21+
13. [`ToSnakeCaseFilter`](ToSnakeCaseFilter.py) - Converts the string to snake case.
22+
14. [`ToStringFilter`](ToStringFilter.py) - Converts the input to a string value.
23+
15. [`ToUpperFilter`](ToUpperFilter.py) - Converts the string to uppercase.
24+
16. [`WhitespaceCollapseFilter`](WhitespaceCollapseFilter.py) - Collapses the whitespace in the string.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import re
2+
from typing import Any, Optional
3+
4+
from ..Filter.BaseFilter import BaseFilter
5+
6+
7+
class SlugifyFilter(BaseFilter):
8+
"""
9+
Filter that converts a string to a slug.
10+
"""
11+
12+
def apply(self, value: Any) -> Optional[str]:
13+
14+
if not isinstance(value, str):
15+
return None
16+
17+
value = value.lower()
18+
19+
value = re.sub(r"[^\w\s-]", "", value)
20+
value = re.sub(r"[\s]+", "-", value)
21+
22+
return value

0 commit comments

Comments
 (0)