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.
Servidor: http.server puro (zero dependências)
Seção intitulada “Servidor: http.server puro (zero dependências)”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 jsonfrom 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()Por que funciona
Seção intitulada “Por que funciona”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_OPTIONSretornandoAllow: GET, QUERY, OPTIONSpra ficar spec-compliant.
Cliente: httpx
Seção intitulada “Cliente: httpx”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", ...}]Versão async
Seção intitulada “Versão async”import httpximport 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())Por que request() e não um método dedicado
Seção intitulada “Por que request() e não um método dedicado”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
Seção intitulada “FastAPI”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, Requestfrom 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"],)Rodando
Seção intitulada “Rodando”uvicorn server:app --reloadPor que add_api_route e não decorador
Seção intitulada “Por que add_api_route e não decorador”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 respRodando
Seção intitulada “Rodando”flask --app server runPor que funciona
Seção intitulada “Por que funciona”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.
requests
Seção intitulada “requests”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) # 200print(resp.json()) # [{"nome": "Daniel", ...}]Com sessão (reutiliza conexão)
Seção intitulada “Com sessão (reutiliza conexão)”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"}})Por que não requests.query()
Seção intitulada “Por que não requests.query()”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().
Testando com curl
Seção intitulada “Testando com curl”curl aceita qualquer método via -X. Funciona com todos os servidores acima:
curl -X QUERY http://localhost:8000/contatos \ -H "Content-Type: application/json" \ -d '{"filter": {"cidade": "São Paulo"}, "limit": 10}'curl -s -X QUERY http://localhost:8000/contatos \ -H "Content-Type: application/json" \ -d '{"filter": {"cargo": "backend"}}' | jq .curl -i -X QUERY http://localhost:8000/contatos \ -H "Content-Type: application/json" \ -d '{"filter": {}}'curl -i -X QUERY http://localhost:8000/contatos \ -H "Content-Type: text/plain" \ -d 'cidade=São Paulo'Flags úteis
Seção intitulada “Flags úteis”| 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 |
Resumo rápido
Seção intitulada “Resumo rápido”| 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.