endpoint
ASGIEndpointBase
Base class of endpoints compliant with the ASGI.
This class implements abstract methods of EndpointBase
with the ASGI.
However, this class doesn't implement some methods to structure responses.
Note
DO NOT use this class as the super class of your endpoints. Consider
to use subclasses of the class like ASGIHTTPEndpoint
.
asgi_version: str
property
readonly
ASGI version.
headers: t.Dict[str, str]
property
readonly
Request headers.
http_version: str
property
readonly
HTTP Version on communication.
path: str
property
readonly
Path of requested URI.
raw_path: bytes
property
readonly
The original HTTP path received from client.
root_path: str
property
readonly
The root path ASGI application is mounted at.
scheme: str
property
readonly
Scheme of requested URI.
scope: t.Dict[str, t.Any]
property
readonly
scope variable received from ASGI server.
scope_type: str
property
readonly
Scope type on ASGI.
spec_version: str
property
readonly
Spec version on ASGI.
__init__(self, app, scope, flexible_locs)
special
Parameters:
Name | Type | Description | Default |
---|---|---|---|
scope |
t.Dict[str, t.Any] |
scope variable received from ASGI server. |
required |
flexible_locs |
t.Tuple[str, ...] |
Flexible locations requested. |
required |
Source code in bamboo/endpoint.py
def __init__(
self,
app: App_t,
scope: t.Dict[str, t.Any],
flexible_locs: t.Tuple[str, ...],
) -> None:
"""
Args:
scope: scope variable received from ASGI server.
flexible_locs: Flexible locations requested.
"""
self._scope = scope
# TODO
# Consider not mapping in this method.
req_headers = scope.get("headers")
self._req_headers = {}
if req_headers:
req_headers = [map(codecs.decode, h) for h in req_headers]
self._req_headers.update(dict(req_headers))
super().__init__(app, flexible_locs)
get_client_addr(self)
Retrieve client address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[str]] |
Pair of IP and port of client. |
Source code in bamboo/endpoint.py
def get_client_addr(self) -> t.Tuple[t.Optional[str], t.Optional[str]]:
client = self._scope.get("client")
if client:
return tuple(client)
return (None, None)
get_header(self, name)
Retrive header value from requested headers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Header name. |
required |
Returns:
Type | Description |
---|---|
t.Optional[str] |
Value of header if existing, None otherwise. |
Source code in bamboo/endpoint.py
def get_header(self, name: str) -> t.Optional[str]:
name = name.lower().replace("_", "-")
return self._req_headers.get(name)
get_host_addr(self)
Retrive host name and port from requested headers.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of host name and port. |
Source code in bamboo/endpoint.py
def get_host_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
http_host = self.get_header("host")
if http_host:
http_host = http_host.split(":")
if len(http_host) == 1:
return (http_host[0], None)
else:
host, port = http_host
port = int(port)
return (host, port)
return (None, None)
get_server_addr(self)
Retrive server address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[str]] |
Pair of IP and port of server. |
Source code in bamboo/endpoint.py
def get_server_addr(self) -> t.Tuple[t.Optional[str], t.Optional[str]]:
server = self._scope.get("server")
if server:
return tuple(server)
return (None, None)
ASGIHTTPEndpoint
HTTP endpoint class compliant with the ASGI.
This class is a complete class of endpoints, communicating on HTTP.
This class has all attributes of ASGIEndpointBase
and HTTPMixIn
,
and you can define its subclass and use them in your response methods.
Examples:
app = ASGIHTTPApp()
@app.route("hello")
class HelloEndpoint(ASGIHTTPEndpoint):
# RECOMMEND to use `data_format` decorator
async def do_GET(self) -> None:
response = {"greeting": "Hello, Client!"}
self.send_json(response)
async def do_POST(self) -> None:
req_body = async self.body
print(req_body)
content_length: t.Optional[int]
property
readonly
Content length of request body if existing.
method: str
property
readonly
HTTP method requested from client.
__init__(self, app, scope, receive, flexible_locs)
special
Parameters:
Name | Type | Description | Default |
---|---|---|---|
app |
ASGIHTTPApp |
Application object which routes the endpoint. |
required |
scope |
t.Dict[str, t.Any] |
Scope variable received from ASGI server. |
required |
receive |
t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]] |
|
required |
flexible_locs |
t.Tuple[str, ...] |
Flexible locations requested. |
required |
Source code in bamboo/endpoint.py
def __init__(
self,
app: ASGIHTTPApp,
scope: t.Dict[str, t.Any],
receive: t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]],
flexible_locs: t.Tuple[str, ...],
) -> None:
"""
Args:
app: Application object which routes the endpoint.
scope: Scope variable received from ASGI server.
receive: `receive` method given from ASGI server.
flexible_locs: Flexible locations requested.
"""
self._receive = receive
self._is_disconnected = False
ASGIEndpointBase.__init__(self, app, scope, flexible_locs)
HTTPMixIn.__init__(self)
__init_subclass__()
classmethod
special
This method is called when a class is subclassed.
The default implementation does nothing. It may be overridden to extend subclasses.
Source code in bamboo/endpoint.py
def __init_subclass__(cls) -> None:
super().__init_subclass__()
# NOTE
# All response methods of its subclass must be awaitables.
for method in HTTPMethods:
callback = cls._get_response_method(method)
if callback and not inspect.iscoroutinefunction(callback):
raise TypeError(
f"{cls.__name__}.{callback.__name__} must be an awaitable"
", not a callable."
)
get_req_body_iter(self, bufsize=8192, cache=False)
Make an access to the request body as an iterator.
Note
If the flag cache
is True
, the request body data is to be
cached into the property body
, i.e. one always can access
to the request body even after the iteration. On the other hand,
if the cache
is False
, caching is not conducted and
access to the body
will be failed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bufsize |
int |
Chunk size of each item. |
8192 |
cache |
bool |
If the request body is to be cached or not. |
False |
Returns:
Type | Description |
---|---|
t.AsyncGenerator[bytes, None] |
Async iterator with binary of the request body. |
Source code in bamboo/endpoint.py
async def get_req_body_iter(
self,
bufsize: int = 8192,
cache: bool = False,
) -> t.AsyncGenerator[bytes, None]:
"""Make an access to the request body as an iterator.
Note:
If the flag `cache` is `True`, the request body data is to be
cached into the property `body`, i.e. one always can access
to the request body even after the iteration. On the other hand,
if the `cache` is `False`, caching is not conducted and
access to the `body` will be failed.
Args:
bufsize: Chunk size of each item.
cache: If the request body is to be cached or not.
Returns:
Async iterator with binary of the request body.
"""
buffer = io.BytesIO()
more_body = True
cacher = await self.__class__.body
while more_body:
chunk = await self._receive()
type = chunk.get("type")
if type == ASGIHTTPEvents.disconnect:
self._is_disconnected = True
return
body = chunk.get("body", b"")
buffer.write(body)
buffer.flush()
more_body = chunk.get("more_body", False)
while buffer.tell() >= bufsize:
buffer.seek(0)
item = buffer.read(bufsize)
yield item
if cache:
# TODO
# Seek more efficient ways
if cacher._has_cache(self):
item = cacher._get_cache(self) + item
cacher._set_cache(self, item)
rest = buffer.read()
buffer.close()
buffer = io.BytesIO(rest)
# TODO
# Seek more efficient ways
buffer.seek(0)
item = buffer.read()
yield item
if cache:
if cacher._has_cache(self):
item = cacher._get_cache(self) + item
cacher._set_cache(self, item)
buffer.close()
EndpointBase
Base class of Endpoint to define logic to requests.
Endpoint is one of the core concept of Bamboo, and this class defines its basic behavior. All endpoints must inherit this class.
Note
This class is an abstract class. Consider using its subclasses.
app: App_t
property
readonly
Application object handling the endpoint.
flexible_locs: t.Tuple[str, ...]
property
readonly
Flexible locations extracted from requested URI.
http_version: str
property
readonly
HTTP Version on communication.
path: str
property
readonly
Path of requested URI.
scheme: str
property
readonly
Scheme of requested URI.
__init__(self, app, flexible_locs)
special
Note
DO NOT generate its instance. Its object will be initialized by application object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
app |
App_t |
Application object which routes the endpoint. |
required |
flexible_locs |
t.Tuple[str, ...] |
Flexible locations requested. |
required |
Source code in bamboo/endpoint.py
def __init__(
self,
app: App_t,
flexible_locs: t.Tuple[str, ...],
) -> None:
"""
Note:
DO NOT generate its instance. Its object will be initialized
by application object.
Args:
app: Application object which routes the endpoint.
flexible_locs: Flexible locations requested.
"""
self._app = app
self._flexible_locs = flexible_locs
get_client_addr(self)
Retrieve client address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of IP and port of client. |
Source code in bamboo/endpoint.py
@abstractmethod
def get_client_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
"""Retrieve client address, pair of its IP address and port.
Note:
IP address and port may be None if retrieving the address from
server application would fail, so it is recommended to confirm
your using server application's spec.
Returns:
Pair of IP and port of client.
"""
pass
get_header(self, name)
Retrive header value from requested headers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Header name. |
required |
Returns:
Type | Description |
---|---|
t.Optional[str] |
Value of header if existing, None otherwise. |
Source code in bamboo/endpoint.py
@abstractmethod
def get_header(self, name: str) -> t.Optional[str]:
"""Retrive header value from requested headers.
Args:
name: Header name.
Returns:
Value of header if existing, None otherwise.
"""
pass
get_host_addr(self)
Retrive host name and port from requested headers.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of host name and port. |
Source code in bamboo/endpoint.py
@abstractmethod
def get_host_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
"""Retrive host name and port from requested headers.
Returns:
Pair of host name and port.
"""
pass
get_queries(self, name)
Get values of query parameter.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Key name of the parameter |
required |
Returns:
Type | Description |
---|---|
t.List[str] |
Value of the parameter. The value of list may have multiple items if client specifies the parameter in several times. |
Source code in bamboo/endpoint.py
def get_queries(self, name: str) -> t.List[str]:
"""Get values of query parameter.
Args:
name: Key name of the parameter
Returns:
Value of the parameter. The value of list may have multiple
items if client specifies the parameter in several times.
"""
query = self.queries.get(name)
if query:
return query
return []
get_server_addr(self)
Retrive server address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of IP and port of server. |
Source code in bamboo/endpoint.py
@abstractmethod
def get_server_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
"""Retrive server address, pair of its IP address and port.
Note:
IP address and port may be None if retrieving the address from
server application would fail, so it is recommended to confirm
your using server application's spec.
Returns:
Pair of IP and port of server.
"""
pass
setup(self, *parcel)
Execute setup of the endpoint object.
This method will execute at initialization of the object by specifying
parcel. The parcel is sent with set_parcel()
method of
the application object which has included the object as one of
the its endpoints. This method can be used as a substitute for
the __init__
method.
This method is useful in some cases like below:
- Making an endpoint class a reusable component
- Injecting environmental dependencies using something like a setting file
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*parcel |
|
Parcel sent via application object. |
() |
Examples:
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
def setup(self, server_name: str) -> None:
self._server_name = server_name
def do_GET(self) -> None:
self.send_body(f"Hello from {self._server_name}".encode())
if __name__ == "__main__":
SERVER_NAME = "awesome_server"
app.set_parcel(HelloEndpoint, SERVER_NAME)
WSGITestExecutor.debug(app, "", 8000)
Source code in bamboo/endpoint.py
def setup(self, *parcel) -> None:
"""Execute setup of the endpoint object.
This method will execute at initialization of the object by specifying
parcel. The parcel is sent with `set_parcel()` method of
the application object which has included the object as one of
the its endpoints. This method can be used as a substitute for
the `__init__` method.
This method is useful in some cases like below:
- Making an endpoint class a reusable component
- Injecting environmental dependencies using something like a
setting file
Args:
*parcel: Parcel sent via application object.
Examples:
```python
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
def setup(self, server_name: str) -> None:
self._server_name = server_name
def do_GET(self) -> None:
self.send_body(f"Hello from {self._server_name}".encode())
if __name__ == "__main__":
SERVER_NAME = "awesome_server"
app.set_parcel(HelloEndpoint, SERVER_NAME)
WSGITestExecutor.debug(app, "", 8000)
```
"""
pass
HTTPMixIn
Mixin class for HTTP endpoints.
This class assumes that endpoint classes inherit this class for HTTP. So, this class do not work alone.
Note
DO NOT use this class alone. This class work correctly by inheriting
it, implementing its abstract methods, and call its __init__()
method in the one of the subclass.
content_length: t.Optional[int]
property
readonly
Content length of request body if existing.
method: str
property
readonly
HTTP method requested from client.
__init_subclass__()
classmethod
special
This method is called when a class is subclassed.
The default implementation does nothing. It may be overridden to extend subclasses.
Source code in bamboo/endpoint.py
def __init_subclass__(cls) -> None:
super().__init_subclass__()
cls._pre_methods = {}
cls._res_methods = {}
# Check if bufsize is positive
if not (cls.bufsize > 0 and isinstance(cls.bufsize, int)):
raise ValueError(
f"{cls.__name__}.bufsize must be positive integer"
)
# Check pre & response methods
for method in _AVAILABLE_RES_METHODS:
name_pre_method = _PREFIX_PRE_RESPONSE + method
name_res_method = _PREFIX_RESPONSE + method
if hasattr(cls, name_pre_method):
pre_method = getattr(cls, name_pre_method)
cls._pre_methods[method] = pre_method
if hasattr(cls, name_res_method):
res_method = getattr(cls, name_res_method)
cls._res_methods[method] = res_method
add_content_length(self, length)
Add Content-Length header of response.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
length |
int |
Size of response body. |
required |
Source code in bamboo/endpoint.py
def add_content_length(self, length: int) -> None:
"""Add Content-Length header of response.
Args:
length: Size of response body.
"""
self.add_header("Content-Length", str(length))
add_content_length_body(self, body)
Add Content-Length header of response by response body.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
body |
bytes |
Response body. |
required |
Source code in bamboo/endpoint.py
def add_content_length_body(self, body: bytes) -> None:
"""Add Content-Length header of response by response body.
Args:
body: Response body.
"""
self.add_header("Content-Length", str(len(body)))
add_content_type(self, content_type)
Add Content-Type header of response.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
content_type |
ContentType |
Information of Content-Type header. |
required |
Source code in bamboo/endpoint.py
def add_content_type(self, content_type: ContentType) -> None:
"""Add Content-Type header of response.
Args:
content_type: Information of Content-Type header.
"""
self.add_header(*content_type.to_header())
add_header(self, name, value, **params)
Add response header with MIME parameters.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Field name of the header. |
required |
value |
str |
Value of the field. |
required |
**params |
str |
Directives added to the field. |
{} |
Source code in bamboo/endpoint.py
def add_header(
self,
name: str,
value: str,
**params: str
) -> None:
"""Add response header with MIME parameters.
Args:
name: Field name of the header.
value: Value of the field.
**params: Directives added to the field.
"""
self._res_headers.append(make_header(name, value, **params))
add_headers(self, *headers)
Add response headers at once.
Note
This method would be used as a shortcut to register multiple headers. If it requires adding MIME parameters, developers can use the 'add_header' method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
**headers |
t.Tuple[str, str] |
Header's info whose header is the field name. |
() |
Source code in bamboo/endpoint.py
def add_headers(self, *headers: t.Tuple[str, str]) -> None:
"""Add response headers at once.
Note:
This method would be used as a shortcut to register multiple
headers. If it requires adding MIME parameters, developers
can use the 'add_header' method.
Args:
**headers: Header's info whose header is the field name.
"""
for name, val in headers:
self.add_header(name, val)
send_api(self, api, status=<HTTPStatus.OK: 200>)
Set given api data to the response body.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
api |
ApiData |
ApiData object to be sent. |
required |
status |
HTTPStatus |
HTTP status of the response. |
<HTTPStatus.OK: 200> |
Exceptions:
Type | Description |
---|---|
StatusCodeAlreadySetError |
Raised if response status code has already been set. |
Source code in bamboo/endpoint.py
def send_api(
self,
api: ApiData,
status: HTTPStatus = HTTPStatus.OK,
) -> None:
"""Set given api data to the response body.
Args:
api: ApiData object to be sent.
status: HTTP status of the response.
Raises:
StatusCodeAlreadySetError: Raised if response status code
has already been set.
"""
self.send_body(
api.__extract__(),
content_type=api.__content_type__,
status=status,
)
send_body(self, body, *others, *, content_type=ContentType(media_type='text/plain', charset=None, boundary=None), status=<HTTPStatus.OK: 200>)
Set given binary to the response body.
Note
If the parameter content_type
is specified, then
the Content-Type
header is to be added.
DEFAULT_CONTENT_TYPE_PLAIN
has its MIME type of text/plain
,
and the other attributes are None
. If another value of
Content-Type
is needed, then you should generate new
ContentType
instance with attributes you want.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
body |
t.Union[bytes, t.Iterable[bytes]] |
Binary to be set to the response body. |
required |
content_type |
t.Optional[ContentType] |
|
ContentType(media_type='text/plain', charset=None, boundary=None) |
status |
HTTPStatus |
HTTP status of the response. |
<HTTPStatus.OK: 200> |
Exceptions:
Type | Description |
---|---|
StatusCodeAlreadySetError |
Raised if response status code has already been set. |
Source code in bamboo/endpoint.py
def send_body(
self,
body: t.Union[bytes, t.Iterable[bytes]],
*others: t.Union[bytes, t.Iterable[bytes]],
content_type: t.Optional[ContentType] = DEFAULT_CONTENT_TYPE_PLAIN,
status: HTTPStatus = HTTPStatus.OK
) -> None:
"""Set given binary to the response body.
Note:
If the parameter `content_type` is specified, then
the `Content-Type` header is to be added.
`DEFAULT_CONTENT_TYPE_PLAIN` has its MIME type of `text/plain`,
and the other attributes are `None`. If another value of
`Content-Type` is needed, then you should generate new
`ContentType` instance with attributes you want.
Args:
body: Binary to be set to the response body.
content_type: `Content-Type` header to be sent.
status: HTTP status of the response.
Raises:
StatusCodeAlreadySetError: Raised if response status code has
already been set.
"""
self._set_status_safely(status)
bodies = [body]
bodies.extend(others)
is_all_bytes = True
not_empty = False
for chunk in bodies:
is_all_bytes &= isinstance(chunk, bytes)
if is_all_bytes:
not_empty |= len(chunk) > 0
self._res_body.append(chunk)
if content_type:
self.add_content_type(content_type)
# Content-Length if avalidable
if is_all_bytes and not_empty:
length = sum(map(len, bodies))
self.add_content_length(length)
send_file(self, path, fname=None, content_type=ContentType(media_type='text/plain', charset=None, boundary=None), status=<HTTPStatus.OK: 200>)
Set file to be sent as response.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path |
str |
File path. |
required |
fname |
t.Optional[str] |
File name to be sent. |
None |
content_type |
str |
Content type of the response. |
ContentType(media_type='text/plain', charset=None, boundary=None) |
status |
HTTPStatus |
HTTP status of the response. |
<HTTPStatus.OK: 200> |
Source code in bamboo/endpoint.py
def send_file(
self,
path: str,
fname: t.Optional[str] = None,
content_type: str = DEFAULT_CONTENT_TYPE_PLAIN,
status: HTTPStatus = HTTPStatus.OK
) -> None:
"""Set file to be sent as response.
Args:
path: File path.
fname: File name to be sent.
content_type: Content type of the response.
status: HTTP status of the response.
"""
file_iter = BufferedFileIterator(path)
self.send_body(file_iter, content_type=content_type, status=status)
length = os.path.getsize(path)
self.add_header("Content-Length", str(length))
if fname:
self.add_header(
"Content-Disposition",
"attachment",
filename=fname
)
send_json(self, body, status=<HTTPStatus.OK: 200>, encoding='UTF-8')
Set given json data to the response body.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
body |
t.Dict[str, t.Any] |
Json data to be set to the response body. |
required |
status |
HTTPStatus |
HTTP status of the response. |
<HTTPStatus.OK: 200> |
encoding |
str |
Encoding of the Json data. |
'UTF-8' |
Exceptions:
Type | Description |
---|---|
StatusCodeAlreadySetError |
Raised if response status code has already been set. |
Source code in bamboo/endpoint.py
def send_json(
self,
body: t.Dict[str, t.Any],
status: HTTPStatus = HTTPStatus.OK,
encoding: str = "UTF-8"
) -> None:
"""Set given json data to the response body.
Args:
body: Json data to be set to the response body.
status: HTTP status of the response.
encoding: Encoding of the Json data.
Raises:
StatusCodeAlreadySetError: Raised if response status code
has already been set.
"""
body = json.dumps(body).encode(encoding=encoding)
content_type = ContentType(MediaTypes.json, encoding)
self.send_body(body, content_type=content_type, status=status)
send_only_status(self, status=<HTTPStatus.OK: 200>)
Set specified status code to one of response.
This method can be used if a callback doesn't need to send response body.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
status |
HTTPStatus |
HTTP status of the response. |
<HTTPStatus.OK: 200> |
Source code in bamboo/endpoint.py
def send_only_status(self, status: HTTPStatus = HTTPStatus.OK) -> None:
"""Set specified status code to one of response.
This method can be used if a callback doesn't need to send response
body.
Args:
status: HTTP status of the response.
"""
self._set_status_safely(status)
StaticEndpoint
content_type: ContentType
property
readonly
Content type of request body.
Returns:
Type | Description |
---|---|
ContentType |
Content type if existing, None otherwise. |
setup(self, doc_root)
Execute setup of the endpoint object.
This method will execute at initialization of the object by specifying
parcel. The parcel is sent with set_parcel()
method of
the application object which has included the object as one of
the its endpoints. This method can be used as a substitute for
the __init__
method.
This method is useful in some cases like below:
- Making an endpoint class a reusable component
- Injecting environmental dependencies using something like a setting file
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*parcel |
|
Parcel sent via application object. |
required |
Examples:
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
def setup(self, server_name: str) -> None:
self._server_name = server_name
def do_GET(self) -> None:
self.send_body(f"Hello from {self._server_name}".encode())
if __name__ == "__main__":
SERVER_NAME = "awesome_server"
app.set_parcel(HelloEndpoint, SERVER_NAME)
WSGITestExecutor.debug(app, "", 8000)
Source code in bamboo/endpoint.py
def setup(self, doc_root: str) -> None:
self._filepath = os.path.join(doc_root, *self.path[1:].split("/"))
self._content_type = ContentType(file2mime(self._filepath))
if not os.path.isfile(self.filepath) and not os.path.isdir(self.filepath):
raise DEFAULT_NOT_FOUND_ERROR
StaticRedirectASGIEndpoint
setup(self, doc_root, suffix)
Execute setup of the endpoint object.
This method will execute at initialization of the object by specifying
parcel. The parcel is sent with set_parcel()
method of
the application object which has included the object as one of
the its endpoints. This method can be used as a substitute for
the __init__
method.
This method is useful in some cases like below:
- Making an endpoint class a reusable component
- Injecting environmental dependencies using something like a setting file
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*parcel |
|
Parcel sent via application object. |
required |
Examples:
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
def setup(self, server_name: str) -> None:
self._server_name = server_name
def do_GET(self) -> None:
self.send_body(f"Hello from {self._server_name}".encode())
if __name__ == "__main__":
SERVER_NAME = "awesome_server"
app.set_parcel(HelloEndpoint, SERVER_NAME)
WSGITestExecutor.debug(app, "", 8000)
Source code in bamboo/endpoint.py
def setup(self, doc_root: str, suffix: str) -> None:
super().setup(doc_root)
self._suffix = suffix
StaticRedirectWSGIEndpoint
setup(self, doc_root)
Execute setup of the endpoint object.
This method will execute at initialization of the object by specifying
parcel. The parcel is sent with set_parcel()
method of
the application object which has included the object as one of
the its endpoints. This method can be used as a substitute for
the __init__
method.
This method is useful in some cases like below:
- Making an endpoint class a reusable component
- Injecting environmental dependencies using something like a setting file
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*parcel |
|
Parcel sent via application object. |
required |
Examples:
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
def setup(self, server_name: str) -> None:
self._server_name = server_name
def do_GET(self) -> None:
self.send_body(f"Hello from {self._server_name}".encode())
if __name__ == "__main__":
SERVER_NAME = "awesome_server"
app.set_parcel(HelloEndpoint, SERVER_NAME)
WSGITestExecutor.debug(app, "", 8000)
Source code in bamboo/endpoint.py
def setup(self, doc_root: str) -> None:
super().setup(doc_root)
StatusCodeAlreadySetError
Raised if response status code has already been set.
WSGIEndpoint
HTTP endpoint class compliant with the WSGI.
This class is a complete class of endpoints, communicating on HTTP.
This class has all attributes of WSGIEndpointBase
and HTTPMixIn
,
and you can define its subclass and use them in your response methods.
Examples:
app = WSGIApp()
@app.route("hello")
class HelloEndpoint(WSGIEndpoint):
# RECOMMEND to use `data_format` decorator
def do_GET(self) -> None:
response = {"greeting": "Hello, Client!"}
self.send_json(response)
def do_POST(self) -> None:
req_body = self.body
print(req_body)
content_length: t.Optional[int]
property
readonly
Content length of request body if existing.
method: str
property
readonly
HTTP method requested from client.
__init__(self, app, environ, flexible_locs)
special
Parameters:
Name | Type | Description | Default |
---|---|---|---|
app |
WSGIApp |
Application object which routes the endpoint. |
required |
environ |
t.Dict[str, t.Any] |
Environ variable received from WSGI server. |
required |
flexible_locs |
t.Tuple[str, ...] |
Flexible locations requested. |
required |
Source code in bamboo/endpoint.py
def __init__(
self,
app: WSGIApp,
environ: t.Dict[str, t.Any],
flexible_locs: t.Tuple[str, ...],
) -> None:
"""
Args:
app: Application object which routes the endpoint.
environ: Environ variable received from WSGI server.
flexible_locs: Flexible locations requested.
"""
WSGIEndpointBase.__init__(self, app, environ, flexible_locs)
HTTPMixIn.__init__(self)
get_req_body_iter(self, bufsize=8192, cache=False)
Make an access to the request body as an iterator.
Note
If the flag cache
is True
, the request body data is to be
cached into the property body
, i.e. one always can access
to the request body even after the iteration. On the other hand,
if the cache
is False
, caching is not conducted and
access to the body
will be failed.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bufsize |
int |
Chunk size of each item. |
8192 |
cache |
bool |
If the request body is to be cached or not. |
False |
Returns:
Type | Description |
---|---|
t.Generator[bytes, None, None] |
Iterator with binary of the request body. |
Source code in bamboo/endpoint.py
def get_req_body_iter(
self,
bufsize: int = 8192,
cache: bool = False,
) -> t.Generator[bytes, None, None]:
"""Make an access to the request body as an iterator.
Note:
If the flag `cache` is `True`, the request body data is to be
cached into the property `body`, i.e. one always can access
to the request body even after the iteration. On the other hand,
if the `cache` is `False`, caching is not conducted and
access to the `body` will be failed.
Args:
bufsize: Chunk size of each item.
cache: If the request body is to be cached or not.
Returns:
Iterator with binary of the request body.
"""
stream = self.get_req_body_stream()
cacher = self.__class__.body
length = self.content_length
remain = length
while True:
if length is None:
chunk = stream.read(bufsize)
elif remain <= 0:
break
else:
counts = remain if remain < bufsize else bufsize
chunk = stream.read(counts)
remain -= len(chunk)
if not chunk:
break
yield chunk
# TODO
# Seek more efficient ways
if cache:
if cacher._has_cache(self):
chunk = cacher._get_cache(self) + chunk
cacher._set_cache(self, chunk)
get_req_body_stream(self)
Fetch the stream with which request body can be received.
Returns:
Type | Description |
---|---|
io.BufferedIOBase |
The stream with request body. |
Source code in bamboo/endpoint.py
def get_req_body_stream(self) -> io.BufferedIOBase:
"""Fetch the stream with which request body can be received.
Returns:
The stream with request body.
"""
return self._environ.get("wsgi.input")
WSGIEndpointBase
Base class of endpoints compliant with the WSGI.
This class implements abstract methods of EndpointBase
with the WSGI.
However, this class doesn't implement some methods to structure responses.
Note
DO NOT use this class as the super class of your endpoints. Consider
to use subclasses of the class like WSGIEndpoint
.
environ: t.Dict[str, t.Any]
property
readonly
environ variable received from WSGI server.
http_version: str
property
readonly
HTTP Version on communication.
path: str
property
readonly
Path of requested URI.
scheme: str
property
readonly
Scheme of requested URI.
server_software: str
property
readonly
Software name of WSGI server.
wsgi_version: str
property
readonly
WSGI version number.
__init__(self, app, environ, flexible_locs)
special
Parameters:
Name | Type | Description | Default |
---|---|---|---|
environ |
t.Dict[str, t.Any] |
environ variable received from WSGI server. |
required |
flexible_locs |
t.Tuple[str, ...] |
flexible locations requested. |
required |
Source code in bamboo/endpoint.py
def __init__(
self,
app: WSGIApp,
environ: t.Dict[str, t.Any],
flexible_locs: t.Tuple[str, ...],
) -> None:
"""
Args:
environ: environ variable received from WSGI server.
flexible_locs: flexible locations requested.
"""
self._environ = environ
super().__init__(app, flexible_locs)
get_client_addr(self)
Retrieve client address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of IP and port of client. |
Source code in bamboo/endpoint.py
def get_client_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
client = self._environ.get("REMOTE_ADDR")
port = self._environ.get("REMOTE_PORT")
if port:
port = int(port)
return (client, port)
get_header(self, name)
Retrive header value from requested headers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Header name. |
required |
Returns:
Type | Description |
---|---|
t.Optional[str] |
Value of header if existing, None otherwise. |
Source code in bamboo/endpoint.py
def get_header(self, name: str) -> t.Optional[str]:
name = name.upper().replace("-", "_")
if name == "CONTENT_TYPE":
return self.content_type
if name == "CONTENT_LENGTH":
return self._environ.get("CONTENT_LENGTH")
name = "HTTP_" + name
return self._environ.get(name)
get_host_addr(self)
Retrive host name and port from requested headers.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of host name and port. |
Source code in bamboo/endpoint.py
def get_host_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
http_host = self._environ.get("HTTP_HOST")
if http_host:
http_host = http_host.split(":")
if len(http_host) == 1:
return (http_host[0], None)
else:
host, port = http_host
port = int(port)
return (host, port)
return (None, None)
get_server_addr(self)
Retrive server address, pair of its IP address and port.
Note
IP address and port may be None if retrieving the address from server application would fail, so it is recommended to confirm your using server application's spec.
Returns:
Type | Description |
---|---|
t.Tuple[t.Optional[str], t.Optional[int]] |
Pair of IP and port of server. |
Source code in bamboo/endpoint.py
def get_server_addr(self) -> t.Tuple[t.Optional[str], t.Optional[int]]:
server = self._environ.get("SERVER_NAME")
port = self._environ.get("SERVER_PORT")
if port:
port = int(port)
return (server, port)