Compare commits
7 Commits
3001caf7ce
...
master
Author | SHA1 | Date | |
---|---|---|---|
6b241feda5 | |||
87c1960cd4 | |||
8ae860f29f | |||
c34a074bfc | |||
8bd14228d7 | |||
e6c5076e39 | |||
2f506d7afd |
26
README.md
26
README.md
@ -6,11 +6,11 @@ This module allows creating simple interfaces to forms/payloads for use in HTTP
|
||||
|
||||
A form is defined by its fields.
|
||||
|
||||
A field is defined by an **alias** (for internal use) and the field's **name**, which is the parameter name in the payload sent during a `POST` request, and which typically corresponds to the `name` attribute of a `<select>` or `<input>` HTML tag.
|
||||
A field is defined by its _name_, which is the parameter name in the payload sent during a `POST` request, and which typically corresponds to the `name` attribute of a `<select>` or `<input>` HTML tag.
|
||||
|
||||
Optionally, a field can have a **default** value, value **options** (as `<select>` tags do), and may be declared **required** (i.e. non-optional).
|
||||
Optionally, a field can have an _alias_ (for internal use), a _default_ value, value _options_ (as `<select>` tags do), and may be declared _required_.
|
||||
|
||||
A form definition in YAML format will consist of the field aliases as top-level keys, and the corresponding fields' definitions as key-value-pairs below them.
|
||||
A form definition in YAML format will consist of the field names as top-level keys, and either nothing/`null` or the corresponding fields' definitions as key-value-pairs below them.
|
||||
|
||||
## Example
|
||||
|
||||
@ -18,19 +18,16 @@ A form definition in YAML format will consist of the field aliases as top-level
|
||||
```yaml
|
||||
# definition.yaml
|
||||
|
||||
field1:
|
||||
name: payload_param_1
|
||||
field2:
|
||||
name: payload_param_2
|
||||
default: foo
|
||||
way_too_long_field_name:
|
||||
alias: short_name
|
||||
foo:
|
||||
choice_field:
|
||||
name: payload_param_3
|
||||
options:
|
||||
value1: text for option 1
|
||||
value2: text for option 2
|
||||
default: value1
|
||||
mandatory_field:
|
||||
name: payload_param_0
|
||||
alias: special
|
||||
required: true
|
||||
```
|
||||
|
||||
@ -38,10 +35,11 @@ mandatory_field:
|
||||
```
|
||||
>>> from yamlhttpforms import load_form
|
||||
>>> form_interface = load_form('definition.yaml')
|
||||
>>> form_interface.get_payload(field1='abc', field2='bar', mandatory_field='420')
|
||||
{'payload_param_1': 'abc', 'payload_param_2': 'bar', 'payload_param_3': 'value1', 'payload_param_0': '420'}
|
||||
>>> form_interface.get_payload(field1='abc', choice_field='baz', mandatory_field='420')
|
||||
>>> form_interface.get_payload(short_name='abc', foo='bar', special='420')
|
||||
{'way_too_long_field_name': 'abc', 'foo': 'bar', 'choice_field': 'value1', 'mandatory_field': '420'}
|
||||
|
||||
>>> form_interface.get_payload(short_name='abc', choice_field='baz', special='420')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: "baz" is not a valid option for <SelectField: name="payload_param_3", default="value1", options={'value1': 'text for option 1', 'value2': 'text for option 2'}>
|
||||
ValueError: "baz" is not a valid option for <SelectField: name="choice_field", default="value1", options={'value1': 'text for option 1', 'value2': 'text for option 2'}>
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = yamlhttpforms
|
||||
version = 0.0.6
|
||||
version = 0.1.2
|
||||
author = Daniil F.
|
||||
author_email = mail@placeholder123.to
|
||||
description = HTTP forms defined in YAML
|
||||
@ -26,6 +26,7 @@ req =
|
||||
requests
|
||||
aio =
|
||||
aiohttp
|
||||
webutils-df @ git+https://git.fajnberg.de/daniil/webutils-df.git
|
||||
html =
|
||||
beautifulsoup4
|
||||
|
||||
|
@ -2,8 +2,8 @@ from importlib import import_module
|
||||
from typing import Dict, Callable, Union, Optional, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp import ClientSession as AioSession, ClientResponse as AioResponse
|
||||
from requests import Session as ReqSession, Response as ReqResponse
|
||||
from aiohttp import ClientSession as AioSession
|
||||
from requests import Session as ReqSession
|
||||
from bs4.element import Tag as BS4Tag
|
||||
|
||||
from .utils import PathT, yaml_overload
|
||||
@ -17,21 +17,7 @@ OptionsT = Dict[str, str]
|
||||
class FormField:
|
||||
def __init__(self, name: str, default: DefaultInitT = None, options: OptionsT = None, required: bool = False):
|
||||
self.name: str = name
|
||||
self._default: Union[str, CallableDefaultT, None]
|
||||
if isinstance(default, dict):
|
||||
try:
|
||||
module, function = default['module'], default['function']
|
||||
except KeyError:
|
||||
raise TypeError(f"Default for field '{name}' is invalid. The default must be either a string or a "
|
||||
f"dictionary with the special keys 'module' and 'function'.")
|
||||
obj = import_module(module)
|
||||
for attr in function.split('.'):
|
||||
obj = getattr(obj, attr)
|
||||
self._default = obj
|
||||
elif default is None:
|
||||
self._default = None
|
||||
else:
|
||||
self._default = str(default)
|
||||
self.default = default
|
||||
self.options: Optional[OptionsT] = None
|
||||
if options is not None:
|
||||
self.options = {str(k): str(v) for k, v in options.items()}
|
||||
@ -55,10 +41,28 @@ class FormField:
|
||||
def default(self) -> Optional[str]:
|
||||
return self._default() if callable(self._default) else self._default
|
||||
|
||||
@default.setter
|
||||
def default(self, default: DefaultInitT) -> None:
|
||||
if isinstance(default, dict):
|
||||
try:
|
||||
module, function = default['module'], default['function']
|
||||
except KeyError:
|
||||
raise TypeError(f"Default for field '{self.name}' is invalid. The default must be either a string or "
|
||||
f"`None` or a dictionary with the special keys 'module' and 'function'.")
|
||||
obj = import_module(module)
|
||||
for attr in function.split('.'):
|
||||
obj = getattr(obj, attr)
|
||||
self._default = obj
|
||||
elif default is None:
|
||||
self._default = None
|
||||
else:
|
||||
self._default = str(default)
|
||||
|
||||
def valid_option(self, option: str) -> bool:
|
||||
return self.options is None or option in self.options.keys() or option in self.options.values()
|
||||
|
||||
def clean(self, value: str) -> str:
|
||||
def clean(self, value: Any) -> str:
|
||||
value = str(value)
|
||||
if self.options is None or value in self.options.keys():
|
||||
return value
|
||||
# Try to find an unambiguous match in the visible option texts:
|
||||
@ -171,7 +175,7 @@ class Form:
|
||||
payload[field.name] = field.default
|
||||
return payload
|
||||
|
||||
async def post_aio(self, _aiohttp_session_obj: 'AioSession' = None, **kwargs: str) -> 'AioResponse':
|
||||
async def post_aio(self, _aiohttp_session_obj: 'AioSession' = None, **kwargs: str) -> str:
|
||||
"""
|
||||
Uses `aiohttp` to perform a POST request to `.url` with the form's payload generated using `kwargs`.
|
||||
|
||||
@ -182,20 +186,20 @@ class Form:
|
||||
Passed directly into `.get_payload`.
|
||||
|
||||
Returns:
|
||||
The `aiohttp.ClientResponse` object from the request.
|
||||
The response text from the request.
|
||||
"""
|
||||
if self.url is None:
|
||||
raise AttributeError("`url` attribute not set")
|
||||
from aiohttp import ClientSession, ClientResponse
|
||||
from webutils import in_async_session
|
||||
from aiohttp import ClientSession
|
||||
from webutils_df import in_async_session
|
||||
|
||||
@in_async_session
|
||||
async def post(url: str, data: dict, session: ClientSession = None) -> ClientResponse:
|
||||
async def post(url: str, data: dict, session: ClientSession = None) -> str:
|
||||
async with session.post(url, data=data) as response:
|
||||
return response
|
||||
return await response.text()
|
||||
return await post(self.url, self.get_payload(**kwargs), session=_aiohttp_session_obj)
|
||||
|
||||
def post_req(self, _requests_session_obj: 'ReqSession' = None, **kwargs: str) -> 'ReqResponse':
|
||||
def post_req(self, _requests_session_obj: 'ReqSession' = None, **kwargs: str) -> str:
|
||||
"""
|
||||
Uses `requests` to perform a POST request to `.url` with the form's payload generated using `kwargs`.
|
||||
|
||||
@ -206,14 +210,14 @@ class Form:
|
||||
Passed directly into `.get_payload`.
|
||||
|
||||
Returns:
|
||||
The `requests.Response` object from the request.
|
||||
The response text from the request.
|
||||
"""
|
||||
if self.url is None:
|
||||
raise AttributeError("`url` attribute not set")
|
||||
if _requests_session_obj is not None:
|
||||
return _requests_session_obj.post(self.url, data=self.get_payload(**kwargs))
|
||||
return _requests_session_obj.post(self.url, data=self.get_payload(**kwargs)).text
|
||||
from requests import post
|
||||
return post(self.url, data=self.get_payload(**kwargs))
|
||||
return post(self.url, data=self.get_payload(**kwargs)).text
|
||||
|
||||
def check_with_html(self, form_tag: 'BS4Tag', check_defaults: bool = True) -> None:
|
||||
from .html import check_form_interface
|
||||
|
Reference in New Issue
Block a user