Add a new instance variable to subclass of http.server.BaseHTTPRequestHandler - python-3.x

I want to be able to add an instance variable to my subclass of http.server.BaseHTTPRequestHandler.
Here's my code:
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib
class Server(BaseHTTPRequestHandler):
def __init__(self, request, client_addr, server):
super().__init__(request, client_addr, server)
self.pathobj = urllib.parse.urlparse(self.path)
def do_HEAD(self):
self.send_response(200)
def do_GET(self):
print(self.pathobj)
self.send_response(200)
self.end_headers()
def do_POST(self):
print(self.pathobj)
self.send_response(405)
self.end_headers()
def run(server_class=HTTPServer, handler_class=Server, port=8080):
server_address = ("", port)
httpd = server_class(server_address, handler_class)
print("Starting httpd on port {}...".format(port))
httpd.serve_forever()
if __name__ == "__main__":
run()
I want to be able to access the ParseResult object returned by urllib.parse.urlparse in each class method without needing to rewrite self.pathobj = urllib.parse.urlparse(self.path) at the beginning of every class method.
The above code does not work -- when do_GET or do_POST are called it complains that 'Server' object has no attribute 'pathobj'.
The above docs for http.server.BaseHTTPRequestHandler say:
All of the relevant information is stored in instance variables of the handler. Subclasses should not need to override or extend the __init__() method.
But I don't see another way to do this. Is it possible?

The docs say
The handler will parse the request and the headers, then call a method specific to the request type.
As it turns out, http.server.BaseHTTPRequestHandler ultimately inherits from socketserver.BaseRequestHandler, and socketserver.BaseRequestHandler.__init__() (defined here) calls do_GET(). So the issue is that the instance variable is actually being set after do_GET() has already been called.
So in order to make this work, you'll need to move the self.pathobj line above the super() line and then rewrite it to do the parsing that BaseHTTPRequestHandler does to construct self.path.

Related

How do I integrate custom exception handling with the FastAPI exception handling?

Python version 3.9, FastAPI version 0.78.0
I have a custom function that I use for application exception handling. When requests run into internal logic problems, i.e I want to send an HTTP response of 400 for some reason, I call a utility function.
#staticmethod
def raise_error(error: str, code: int) -> None:
logger.error(error)
raise HTTPException(status_code=code, detail=error)
Not a fan of this approach. So I look at
from fastapi import FastAPI, HTTPException, status
from fastapi.respones import JSONResponse
class ExceptionCustom(HTTPException):
pass
def exception_404_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={"message": "404"})
app.add_exception_handler(ExceptionCustom, exception_404_handler)
The problem I run into with the above approach is the inability to pass in the message as an argument.
Any thoughts on the whole topic?
Your custom exception can have any custom attributes that you want. Let's say you write it this way:
class ExceptionCustom(HTTPException):
pass
in your custom handler, you can do something like
def exception_404_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={"message": exc.detail})
Then, all you need to do is to raise the exception this way:
raise ExceptionCustom(status_code=404, detail='error message')
Note that you are creating a handler for this specific ExceptionCustom. If all you need is the message, you can write something more generic:
class MyHTTPException(HTTPException):
pass
def my_http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=exc.status_code, content={"message": exc.detail})
app.add_exception_handler(MyHTTPException, my_http_exception_handler)
This way you can raise any exception, with any status code and any message and have the message in your JSON response.
There's a detailed explanation on FastAPI docs
You can add custom exception handlers, and use attributes in your Exception class (i.e., class MyException(Exception) in the example below) to pass any message/variables that you would like to do so. The exception handler (i.e., #app.exception_handler(MyException) in the case below) will handle the exception as you wish and return your custom message. For more options, please have a look at this related answer as well.
Working Example
To trigger the exception in the example below, access the following URL from your browser: http://localhost:8000/something
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
class MyException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
#app.exception_handler(MyException)
async def my_exception_handler(request: Request, exc: MyException):
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND,
content={"message": f"{exc.name} cannot be found." })
#app.get("/{name}")
def read_name(name: str):
if name == "something":
raise MyException(name=name)
return {"name": name}

Adding query parameter for every flask GET request

Trying to figure out the right mechanism to use here.
I want to modify the flask request coming in every time.
I think the request is immutable, so I am trying to figure out if this mechanism exists.
Basically, I want to append a string onto the end of the request coming in.
I can hook into the request and the right time in a before_request handler like this:
#app.before_app_request
def before_request_custom():
# Get the request
req = flask.request
method = str(req.method)
if method == "GET":
# Do stuff here
pass
But I am not sure what to actually do to add this in, and don't see a way to accomplish it...I guess i could redirect, but that seems silly in this case. Any ideas?
The request object is immutable (https://werkzeug.palletsprojects.com/en/1.0.x/wrappers/#base-wrappers), but request.args or request.form can be set from ImmutableOrderedMultiDict to just OrderedMultiDict using Subclassing on Flask (https://flask.palletsprojects.com/en/1.1.x/patterns/subclassing/). Here's an example of how you could add that filter[is_deleted]=False URL param:
from flask import Flask, request, Request
from werkzeug.datastructures import OrderedMultiDict
class MyRequest(Request):
parameter_storage_class = OrderedMultiDict
class MyApp(Flask):
def __init__(self, import_name):
super(MyApp, self).__init__(import_name)
self.before_request(self.my_before_method)
def my_before_method(self):
if "endpoint" in request.base_url:
request.args["filter[is_deleted]"] = "False"
app = MyApp(__name__)
app.request_class = MyRequest
#app.route('/endpoint/')
def endpoint():
filter = request.args.get('filter[is_deleted]')
return filter
This way you can modify request.args before you actually send the request.
How about this?
from flask import g
#app.before_request
def before_request():
# Get the request
req = flask.request
method = str(req.method)
if method == "GET":
g.my_addon = "secret sauce"
return None
Then, g.my_addon is available in every view function:
from flask import g
#app.route('/my_view')
def my_view():
if g.my_addon == "secret sauce":
print('it worked!')
Using test_request_context() you can make the trick.
Related: https://flask.palletsprojects.com/en/1.1.x/quickstart/#accessing-request-data

Call Method of another class in Flask API

I am trying to expose a data service as an API for a PHP application. I have written the API logic in a file called APILogic.py. The code in this looks like this
class APILogic(object):
def __init__(self):
# the initialization is done here
...
def APIfunction(self):
# the API logic is built here, it uses the class variables and methods
...
I have created another file for the API purpose. Its called API.py. The code in this file looks like this
import APILogic from APILogic
class MyFlask:
def __init__(self):
self.test = APILogic()
from flask import Flask
app = Flask(__name__)
my_flask = MyFlask()
#app.route("/Test")
def Test():
return my_flask.test.APIfunction
if __name__ == "__main__":
app.run(debug=True,port=9999)
When I run the code, I get the error
> TypeError: APIfunction() takes 1 positional argument but 3 were given
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
There are no arguments for the APIfunction though.
Please help.
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
Looks like you're returning the method, but it sounds like you want to return the result of that method:
#app.route("/Test")
def Test():
return my_flask.test.APIfunction()
View function should return valid response.
Sample API code
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()

http.server - send data to the client

Greatings everyone,
I have a main that runs a thread. This thread manages a HTTP server using http.server like this:
import binascii, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
class Com(threading.Thread):
def __init__(self, queue):
MyHandler.queue = queue
threading.Thread.__init__(self)
def run(self):
httpd = HTTPServer(('10.0.0.254', 8000), MyHandler)
httpd.serve_forever()
class MyHandler(BaseHTTPRequestHandler):
queue = None
def do_POST(self):
" Receive a POST and put the received frame in the queue "
def send(self, toSend):
self.send_response(200, "OK")
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(toSend)
The do_POST method works well, it receive data and send it to the main thanks to the queue.
What I want is: sometimes when I want, the main orders the server to send some data. Normally, the code to send is good. My problem is that I can't, from the main, access to the HTTP server instance to call the send method, because it is managed by http.server.
In Com class (instance in the main), I tried something like:
def sendThroughServer(self, toSend):
MyHandler.send(toSend)
Also using class method, but it doesn't seems to work, because it requieres "self".
How can I do that pls ?
Hope it is clear. Thanks in advance.

CherryPy server name tag

When running a CherryPy app it will send server name tag something like CherryPy/version.
Is it possible to rename/overwrite that from the app without modifying CherryPy so it will show something else?
Maybe something like MyAppName/version (CherryPy/version)
This can now be set on a per application basis in the config file/dict
[/]
response.headers.server = "CherryPy Dev01"
Actually asking on IRC on their official channel fumanchu gived me a more clean way to do this (using latest svn):
import cherrypy
from cherrypy import _cpwsgi_server
class HelloWorld(object):
def index(self):
return "Hello World!"
index.exposed = True
serverTag = "MyApp/%s (CherryPy/%s)" % ("1.2.3", cherrypy.__version__)
_cpwsgi_server.CPWSGIServer.environ['SERVER_SOFTWARE'] = serverTag
cherrypy.config.update({'tools.response_headers.on': True,
'tools.response_headers.headers': [('Server', serverTag)]})
cherrypy.quickstart(HelloWorld())
This string appears to be being set in the CherrPy Response class:
def __init__(self):
self.status = None
self.header_list = None
self._body = []
self.time = time.time()
self.headers = http.HeaderMap()
# Since we know all our keys are titled strings, we can
# bypass HeaderMap.update and get a big speed boost.
dict.update(self.headers, {
"Content-Type": 'text/html',
"Server": "CherryPy/" + cherrypy.__version__,
"Date": http.HTTPDate(self.time),
})
So when you're creating your Response object, you can update the "Server" header to display your desired string. From the CherrPy Response Object documentation:
headers
A dictionary containing the headers of the response. You may set values in
this dict anytime before the finalize phase, after which CherryPy switches
to using header_list ...
EDIT: To avoid needing to make this change with every response object you create, one simple way to get around this is to wrap the Response object. For example, you can create your own Response object that inherits from CherryPy's Response and updates the headers key after initializing:
class MyResponse(Response):
def __init__(self):
Response.__init__(self)
dict.update(self.headers, {
"Server": "MyServer/1.0",
})
RespObject = MyResponse()
print RespObject.headers["Server"]
Then you can can call your object for uses where you need to create a Response object, and it will always have the Server header set to your desired string.

Resources