Pular para o conteúdo

Python

Python tem tudo pra rodar QUERY hoje — http.server despacha qualquer método via do_<METHOD>, httpx e requests aceitam métodos arbitrários, e os frameworks web (FastAPI, Flask) roteiam por string. Sem monkey-patch, sem hack.


http.server chama do_QUERY() se o método existir no handler. Funciona em qualquer Python 3.8+, sem instalar nada.

"""Servidor HTTP QUERY — stdlib pura."""
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
CONTATOS = [
{"nome": "Ana", "cidade": "São Paulo", "cargo": "backend"},
{"nome": "Bruno", "cidade": "Curitiba", "cargo": "frontend"},
{"nome": "Carla", "cidade": "São Paulo", "cargo": "devops"},
{"nome": "Daniel", "cidade": "Recife", "cargo": "backend"},
]
class QueryHandler(BaseHTTPRequestHandler):
def do_QUERY(self):
# Valida Content-Type (RFC 10008 §2.1)
content_type = self.headers.get("Content-Type", "")
if "application/json" not in content_type:
self._responder(415, {"erro": "Content-Type deve ser application/json"})
return
# Lê o body
tamanho = int(self.headers.get("Content-Length", 0))
corpo = json.loads(self.rfile.read(tamanho)) if tamanho else {}
# Filtra os dados
filtros = corpo.get("filter", {})
resultados = [
c for c in CONTATOS
if all(c.get(k) == v for k, v in filtros.items())
]
# Aplica limit
limit = corpo.get("limit", len(resultados))
resultados = resultados[:limit]
self._responder(200, resultados)
def _responder(self, status, dados):
corpo = json.dumps(dados, ensure_ascii=False).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(corpo)))
# RFC 10008 §3 — anuncia formatos aceitos
self.send_header("Accept-Query", "application/json")
self.end_headers()
self.wfile.write(corpo)
if __name__ == "__main__":
servidor = HTTPServer(("localhost", 8000), QueryHandler)
print("Servidor QUERY rodando em http://localhost:8000")
servidor.serve_forever()

BaseHTTPRequestHandler usa dispatching dinâmico: qualquer método do_XYZ responde ao verbo HTTP XYZ. Não precisa registrar nada — Python resolve por introspecção. Isso funciona desde o Python 2, na verdade.

Dica: Adicione do_OPTIONS retornando Allow: GET, QUERY, OPTIONS pra ficar spec-compliant.


httpx aceita qualquer método via client.request(). É o cliente HTTP moderno do Python — async, HTTP/2, tipado.

"""Cliente QUERY com httpx."""
import httpx
filtro = {
"filter": {"cidade": "São Paulo"},
"limit": 10,
}
with httpx.Client() as client:
resp = client.request(
"QUERY",
"http://localhost:8000/contatos",
json=filtro,
)
print(resp.status_code) # 200
print(resp.json()) # [{"nome": "Ana", ...}, {"nome": "Carla", ...}]
import httpx
import asyncio
async def buscar_contatos():
async with httpx.AsyncClient() as client:
resp = await client.request(
"QUERY",
"http://localhost:8000/contatos",
json={"filter": {"cargo": "backend"}, "limit": 5},
)
return resp.json()
resultados = asyncio.run(buscar_contatos())

httpx tem atalhos para GET, POST, PUT, DELETE — mas não pra QUERY (ainda). O método request() é a porta de entrada genérica: primeiro argumento é a string do verbo. Sem restrições.


FastAPI não tem decorador @app.query(), mas add_api_route() aceita qualquer método na lista methods. Direto ao ponto:

"""FastAPI com rota QUERY."""
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
PRODUTOS = [
{"id": 1, "nome": "Teclado", "categoria": "periféricos", "preco": 350},
{"id": 2, "nome": "Monitor", "categoria": "displays", "preco": 2800},
{"id": 3, "nome": "Mouse", "categoria": "periféricos", "preco": 150},
{"id": 4, "nome": "SSD", "categoria": "storage", "preco": 480},
]
async def buscar_produtos(request: Request):
corpo = await request.json()
filtros = corpo.get("filter", {})
limit = corpo.get("limit", 50)
resultados = [
p for p in PRODUTOS
if all(p.get(k) == v for k, v in filtros.items())
]
return JSONResponse(
content=resultados[:limit],
headers={"Accept-Query": "application/json"},
)
app.add_api_route(
"/produtos",
buscar_produtos,
methods=["QUERY"],
)
Terminal window
uvicorn server:app --reload

FastAPI gera decoradores apenas para métodos HTTP padrão (@app.get, @app.post, etc.). Pra métodos custom, add_api_route() é o caminho oficial — aceita qualquer string em methods. O Starlette por baixo não valida o verbo.

Nota: O OpenAPI schema gerado pode não incluir a rota QUERY no Swagger UI até que a spec OpenAPI reconheça o método formalmente.


Flask aceita qualquer string na lista methods do decorador @app.route(). Simples assim:

"""Flask com rota QUERY."""
from flask import Flask, request, jsonify
app = Flask(__name__)
USUARIOS = [
{"id": 1, "nome": "Ana", "role": "admin", "ativo": True},
{"id": 2, "nome": "Bruno", "role": "editor", "ativo": True},
{"id": 3, "nome": "Carla", "role": "admin", "ativo": False},
{"id": 4, "nome": "Daniel","role": "viewer", "ativo": True},
]
@app.route("/usuarios", methods=["QUERY"])
def buscar_usuarios():
corpo = request.get_json()
filtros = corpo.get("filter", {})
limit = corpo.get("limit", 100)
resultados = [
u for u in USUARIOS
if all(u.get(k) == v for k, v in filtros.items())
]
resp = jsonify(resultados[:limit])
resp.headers["Accept-Query"] = "application/json"
return resp
Terminal window
flask --app server run

Werkzeug (WSGI layer do Flask) não restringe métodos HTTP — qualquer string é roteada normalmente. Flask só precisa que você declare na lista methods. Sem extensões, sem hacks.


A biblioteca requests aceita métodos arbitrários via requests.request():

"""Cliente QUERY com requests."""
import requests
resp = requests.request(
"QUERY",
"http://localhost:8000/contatos",
json={"filter": {"cidade": "Recife"}, "limit": 5},
)
print(resp.status_code) # 200
print(resp.json()) # [{"nome": "Daniel", ...}]
import requests
session = requests.Session()
session.headers.update({
"Accept": "application/json",
})
resp1 = session.request("QUERY", "http://localhost:8000/contatos",
json={"filter": {"cargo": "backend"}})
resp2 = session.request("QUERY", "http://localhost:8000/contatos",
json={"filter": {"cidade": "Curitiba"}})

requests não tem helper pra QUERY — e provavelmente não vai ter tão cedo. requests.request() é a API genérica que aceita qualquer verbo. Funciona igual a httpx.Client().request().


curl aceita qualquer método via -X. Funciona com todos os servidores acima:

Terminal window
curl -X QUERY http://localhost:8000/contatos \
-H "Content-Type: application/json" \
-d '{"filter": {"cidade": "São Paulo"}, "limit": 10}'
Terminal window
curl -s -X QUERY http://localhost:8000/contatos \
-H "Content-Type: application/json" \
-d '{"filter": {"cargo": "backend"}}' | jq .
Terminal window
curl -i -X QUERY http://localhost:8000/contatos \
-H "Content-Type: application/json" \
-d '{"filter": {}}'
Terminal window
curl -i -X QUERY http://localhost:8000/contatos \
-H "Content-Type: text/plain" \
-d 'cidade=São Paulo'
Flag O que faz
-X QUERY Define o método HTTP
-H "Content-Type: ..." Header obrigatório (RFC 10008 §2.1)
-d '{...}' Body da requisição
-i Mostra headers da resposta
-s Silencia barra de progresso (útil com jq)
-v Modo verbose — mostra a requisição completa no wire

Ferramenta Como usar QUERY
http.server do_QUERY(self) no handler
httpx client.request("QUERY", url, json=...)
FastAPI app.add_api_route(path, fn, methods=["QUERY"])
Flask @app.route(path, methods=["QUERY"])
requests requests.request("QUERY", url, json=...)
curl curl -X QUERY url -H "Content-Type: ..." -d '...'

Todas as libs Python já suportam QUERY — porque nenhuma restringe o verbo HTTP que você pode usar. A RFC 10008 só padronizou o que era tecnicamente possível o tempo todo.