add tenant api
This commit is contained in:
parent
86a1e38dfc
commit
7cf05261c6
@ -16,7 +16,6 @@ dependencies = [
|
||||
"psycopg<4.0.0,>=3.1.19",
|
||||
"alembic<2.0.0,>=1.13.1",
|
||||
"pydantic<3.0.0,>=2.7.1",
|
||||
"flask-restful<1.0.0,>=0.3.10",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
120
tests/test_tenant.py
Normal file
120
tests/test_tenant.py
Normal file
@ -0,0 +1,120 @@
|
||||
from flask.testing import FlaskClient
|
||||
from sqlalchemy.sql import func, select
|
||||
|
||||
from teufa import db as dbm
|
||||
from teufa.ext import db
|
||||
|
||||
|
||||
def test_create_tenant(client: FlaskClient):
|
||||
response = client.post(
|
||||
"/api/v1/admin/tenants",
|
||||
json={
|
||||
"tenant": {
|
||||
"name": "Test Tenant",
|
||||
"hostname": "test.tenant.com",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json == {
|
||||
"tenant": {
|
||||
"id": 1,
|
||||
"name": "Test Tenant",
|
||||
"hostname": "test.tenant.com",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_tenant(client: FlaskClient):
|
||||
tenant = dbm.Tenant(
|
||||
name="Test Tenant",
|
||||
hostname="test.tenant.com",
|
||||
)
|
||||
db.session.add(tenant)
|
||||
db.session.commit()
|
||||
|
||||
response = client.get(f"/api/v1/admin/tenants/{tenant.id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json == {
|
||||
"tenant": {
|
||||
"id": tenant.id,
|
||||
"name": "Test Tenant",
|
||||
"hostname": "test.tenant.com",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_tenant_not_found(client: FlaskClient):
|
||||
response = client.get("/api/v1/admin/tenants/1")
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json == {"message": "Tenant not found"}
|
||||
|
||||
|
||||
def test_update_tenant(client: FlaskClient):
|
||||
tenant = dbm.Tenant(
|
||||
name="Test Tenant",
|
||||
hostname="test.tenant.com",
|
||||
)
|
||||
db.session.add(tenant)
|
||||
db.session.commit()
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/admin/tenants/{tenant.id}",
|
||||
json={
|
||||
"tenant": {
|
||||
"name": "Updated Tenant",
|
||||
"hostname": "updated.tenant.com",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json == {
|
||||
"tenant": {
|
||||
"id": tenant.id,
|
||||
"name": "Updated Tenant",
|
||||
"hostname": "updated.tenant.com",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_update_tenant_not_found(client: FlaskClient):
|
||||
response = client.put(
|
||||
"/api/v1/admin/tenants/1",
|
||||
json={
|
||||
"tenant": {
|
||||
"name": "Updated Tenant",
|
||||
"hostname": "updated.tenant.com",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json == {"message": "Tenant not found"}
|
||||
|
||||
|
||||
def test_delete_tenant(client: FlaskClient):
|
||||
tenant = dbm.Tenant(
|
||||
name="Test Tenant",
|
||||
hostname="test.tenant.com",
|
||||
)
|
||||
db.session.add(tenant)
|
||||
db.session.commit()
|
||||
|
||||
response = client.delete(f"/api/v1/admin/tenants/{tenant.id}")
|
||||
|
||||
assert response.status_code == 204
|
||||
assert response.data == b""
|
||||
|
||||
with db.session.begin():
|
||||
assert db.session.scalar(select(func.count(dbm.Tenant.id))) == 0
|
||||
|
||||
|
||||
def test_delete_tenant_not_found(client: FlaskClient):
|
||||
response = client.delete("/api/v1/admin/tenants/1")
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json == {"message": "Tenant not found"}
|
@ -2,7 +2,7 @@ from flask import Flask
|
||||
|
||||
from .config import Config
|
||||
from .ext import db
|
||||
from .v1_api import bp as v1_bp
|
||||
from .v1_api import v1_bp
|
||||
|
||||
|
||||
def create_app():
|
||||
|
32
teufa/dao.py
32
teufa/dao.py
@ -7,6 +7,38 @@ class Error(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class Tenant(BaseModel):
|
||||
id: int | None = None
|
||||
name: str
|
||||
hostname: str
|
||||
|
||||
|
||||
class PartialTenant(BaseModel):
|
||||
id: int | object = empty
|
||||
name: str | object = empty
|
||||
hostname: str | object = empty
|
||||
|
||||
|
||||
class CreateTenantRequest(BaseModel):
|
||||
tenant: Tenant
|
||||
|
||||
|
||||
class CreateTenantResponse(BaseModel):
|
||||
tenant: Tenant
|
||||
|
||||
|
||||
class UpdateTenantRequest(BaseModel):
|
||||
tenant: PartialTenant
|
||||
|
||||
|
||||
class UpdateTenantResponse(BaseModel):
|
||||
tenant: Tenant
|
||||
|
||||
|
||||
class GetTenantResponse(BaseModel):
|
||||
tenant: Tenant
|
||||
|
||||
|
||||
class Flight(BaseModel):
|
||||
id: int | None = None
|
||||
departure_icao: str
|
||||
|
@ -1,15 +1,22 @@
|
||||
from flask import Blueprint, Flask, g, request
|
||||
from flask_restful import Api
|
||||
from flask import Blueprint, g, request
|
||||
from sqlalchemy import select
|
||||
|
||||
from .. import db as dbm
|
||||
from ..ext import db
|
||||
from .flights import FlightCollectionResource, FlightResource
|
||||
from .flights import flights_bp
|
||||
from .tenants import tenants_bp
|
||||
|
||||
bp = Blueprint("api", __name__, url_prefix="/api")
|
||||
v1_bp = Blueprint("v1", __name__, url_prefix="/api/v1")
|
||||
|
||||
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
|
||||
|
||||
admin_bp.register_blueprint(tenants_bp)
|
||||
v1_bp.register_blueprint(admin_bp)
|
||||
|
||||
api_bp = Blueprint("api", __name__, url_prefix="/")
|
||||
|
||||
|
||||
@bp.before_request
|
||||
@api_bp.before_request
|
||||
def before_request():
|
||||
if not hasattr(g, "tenant"):
|
||||
hostname = request.host.split(":")[0]
|
||||
@ -18,7 +25,5 @@ def before_request():
|
||||
).first()
|
||||
|
||||
|
||||
api = Api(bp)
|
||||
|
||||
api.add_resource(FlightCollectionResource, "/v1/flights")
|
||||
api.add_resource(FlightResource, "/v1/flights/<int:flight_id>")
|
||||
api_bp.register_blueprint(flights_bp)
|
||||
v1_bp.register_blueprint(api_bp)
|
||||
|
@ -1,102 +1,103 @@
|
||||
from flask import g, request
|
||||
from flask_restful import Resource
|
||||
from flask import Blueprint, g, jsonify, request
|
||||
|
||||
from .. import dao
|
||||
from .. import db as dbm
|
||||
from ..ext import db
|
||||
|
||||
flights_bp = Blueprint("flights", __name__)
|
||||
|
||||
class FlightCollectionResource(Resource):
|
||||
def post(self):
|
||||
data = request.get_json()
|
||||
|
||||
req = dao.CreateFlightRequest(**data)
|
||||
@flights_bp.route("/flights", methods=["POST"])
|
||||
def create_flight():
|
||||
data = request.get_json()
|
||||
req = dao.CreateFlightRequest(**data)
|
||||
|
||||
flight = dbm.Flight(
|
||||
tenant_id=g.tenant.id,
|
||||
departure_icao=req.flight.departure_icao,
|
||||
arrival_icao=req.flight.arrival_icao,
|
||||
aircraft_id=req.flight.aircraft_id,
|
||||
)
|
||||
flight = dbm.Flight(
|
||||
tenant_id=g.tenant.id,
|
||||
departure_icao=req.flight.departure_icao,
|
||||
arrival_icao=req.flight.arrival_icao,
|
||||
aircraft_id=req.flight.aircraft_id,
|
||||
)
|
||||
|
||||
db.session.add(flight)
|
||||
db.session.commit()
|
||||
db.session.add(flight)
|
||||
db.session.commit()
|
||||
|
||||
res = dao.CreateFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
res = dao.CreateFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return res.model_dump(), 201
|
||||
return jsonify(res.model_dump()), 201
|
||||
|
||||
|
||||
class FlightResource(Resource):
|
||||
def get(self, flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
@flights_bp.route("/flights/<int:flight_id>", methods=["GET"])
|
||||
def get_flight(flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
|
||||
if not flight:
|
||||
return dao.Error(message="Flight not found").model_dump(), 404
|
||||
if not flight:
|
||||
return jsonify(dao.Error(message="Flight not found").model_dump()), 404
|
||||
|
||||
res = dao.GetFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
res = dao.GetFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return res.model_dump()
|
||||
return jsonify(res.model_dump())
|
||||
|
||||
def put(self, flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
|
||||
if not flight:
|
||||
return dao.Error(message="Flight not found").model_dump(), 404
|
||||
@flights_bp.route("/flights/<int:flight_id>", methods=["PUT"])
|
||||
def update_flight(flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
|
||||
data = request.get_json()
|
||||
if not flight:
|
||||
return jsonify(dao.Error(message="Flight not found").model_dump()), 404
|
||||
|
||||
req = dao.UpdateFlightRequest(**data)
|
||||
data = request.get_json()
|
||||
req = dao.UpdateFlightRequest(**data)
|
||||
|
||||
if req.flight.id is not dao.empty:
|
||||
flight.id = req.flight.id
|
||||
if req.flight.departure_icao is not dao.empty:
|
||||
flight.departure_icao = req.flight.departure_icao
|
||||
if req.flight.arrival_icao is not dao.empty:
|
||||
flight.arrival_icao = req.flight.arrival_icao
|
||||
if req.flight.aircraft_id is not dao.empty:
|
||||
flight.aircraft_id = req.flight.aircraft_id
|
||||
if req.flight.departure_icao is not dao.empty:
|
||||
flight.departure_icao = req.flight.departure_icao
|
||||
if req.flight.arrival_icao is not dao.empty:
|
||||
flight.arrival_icao = req.flight.arrival_icao
|
||||
if req.flight.aircraft_id is not dao.empty:
|
||||
flight.aircraft_id = req.flight.aircraft_id
|
||||
|
||||
db.session.commit()
|
||||
db.session.commit()
|
||||
|
||||
res = dao.UpdateFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
res = dao.UpdateFlightResponse(
|
||||
**{
|
||||
"flight": {
|
||||
"id": flight.id,
|
||||
"departure_icao": flight.departure_icao,
|
||||
"arrival_icao": flight.arrival_icao,
|
||||
"aircraft_id": flight.aircraft_id,
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return res.model_dump()
|
||||
return jsonify(res.model_dump())
|
||||
|
||||
def delete(self, flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
|
||||
if not flight:
|
||||
return dao.Error(message="Flight not found").model_dump(), 404
|
||||
@flights_bp.route("/flights/<int:flight_id>", methods=["DELETE"])
|
||||
def delete_flight(flight_id):
|
||||
flight = db.session.get(dbm.Flight, flight_id)
|
||||
|
||||
db.session.delete(flight)
|
||||
db.session.commit()
|
||||
if not flight:
|
||||
return jsonify(dao.Error(message="Flight not found").model_dump()), 404
|
||||
|
||||
return "", 204
|
||||
db.session.delete(flight)
|
||||
db.session.commit()
|
||||
|
||||
return "", 204
|
||||
|
99
teufa/v1_api/tenants.py
Normal file
99
teufa/v1_api/tenants.py
Normal file
@ -0,0 +1,99 @@
|
||||
from flask import Blueprint, g, jsonify, request
|
||||
|
||||
from .. import dao
|
||||
from .. import db as dbm
|
||||
from ..ext import db
|
||||
|
||||
tenants_bp = Blueprint("tenants", __name__)
|
||||
|
||||
|
||||
@tenants_bp.route("/tenants", methods=["POST"])
|
||||
def create_tenant():
|
||||
data = request.get_json()
|
||||
req = dao.CreateTenantRequest(**data)
|
||||
|
||||
tenant = dbm.Tenant(
|
||||
name=req.tenant.name,
|
||||
hostname=req.tenant.hostname,
|
||||
)
|
||||
|
||||
db.session.add(tenant)
|
||||
db.session.commit()
|
||||
|
||||
res = dao.CreateTenantResponse(
|
||||
**{
|
||||
"tenant": {
|
||||
"id": tenant.id,
|
||||
"name": tenant.name,
|
||||
"hostname": tenant.hostname,
|
||||
"created_at": tenant.created_at,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify(res.model_dump()), 201
|
||||
|
||||
|
||||
@tenants_bp.route("/tenants/<int:tenant_id>", methods=["GET"])
|
||||
def get_tenant(tenant_id):
|
||||
tenant = db.session.get(dbm.Tenant, tenant_id)
|
||||
|
||||
if not tenant:
|
||||
return jsonify(dao.Error(message="Tenant not found").model_dump()), 404
|
||||
|
||||
res = dao.GetTenantResponse(
|
||||
**{
|
||||
"tenant": {
|
||||
"id": tenant.id,
|
||||
"name": tenant.name,
|
||||
"hostname": tenant.hostname,
|
||||
"created_at": tenant.created_at,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify(res.model_dump())
|
||||
|
||||
|
||||
@tenants_bp.route("/tenants/<int:tenant_id>", methods=["PUT"])
|
||||
def update_tenant(tenant_id):
|
||||
tenant = db.session.get(dbm.Tenant, tenant_id)
|
||||
|
||||
if not tenant:
|
||||
return jsonify(dao.Error(message="Tenant not found").model_dump()), 404
|
||||
|
||||
data = request.get_json()
|
||||
req = dao.UpdateTenantRequest(**data)
|
||||
|
||||
if req.tenant.name is not dao.empty:
|
||||
tenant.name = req.tenant.name
|
||||
if req.tenant.hostname is not dao.empty:
|
||||
tenant.hostname = req.tenant.hostname
|
||||
|
||||
db.session.commit()
|
||||
|
||||
res = dao.UpdateTenantResponse(
|
||||
**{
|
||||
"tenant": {
|
||||
"id": tenant.id,
|
||||
"name": tenant.name,
|
||||
"hostname": tenant.hostname,
|
||||
"created_at": tenant.created_at,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return jsonify(res.model_dump())
|
||||
|
||||
|
||||
@tenants_bp.route("/tenants/<int:tenant_id>", methods=["DELETE"])
|
||||
def delete_tenant(tenant_id):
|
||||
tenant = db.session.get(dbm.Tenant, tenant_id)
|
||||
|
||||
if not tenant:
|
||||
return jsonify(dao.Error(message="Tenant not found").model_dump()), 404
|
||||
|
||||
db.session.delete(tenant)
|
||||
db.session.commit()
|
||||
|
||||
return "", 204
|
44
uv.lock
generated
44
uv.lock
generated
@ -15,15 +15,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/06/8b505aea3d77021b18dcbd8133aa1418f1a1e37e432a465b14c46b2c0eaa/alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25", size = 233482 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aniso8601"
|
||||
version = "10.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/3f/dc8a28fa6dc72c13d8c158b01f8975f240e9e72c336cc1ae00f424e2d7ce/aniso8601-10.0.0.tar.gz", hash = "sha256:ff1d0fc2346688c62c0151547136ac30e322896ed8af316ef7602c47da9426cf", size = 47008 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/72/bf/d5cde2cb7cdc2cb1770d974418d169a79c3187bd962cb752b9fd617848ca/aniso8601-10.0.0-py2.py3-none-any.whl", hash = "sha256:3c943422efaa0229ebd2b0d7d223effb5e7c89e24d2267ebe76c61a2d8e290cb", size = 52767 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@ -107,21 +98,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-restful"
|
||||
version = "0.3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aniso8601" },
|
||||
{ name = "flask" },
|
||||
{ name = "pytz" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/ce/a0a133db616ea47f78a41e15c4c68b9f08cab3df31eb960f61899200a119/Flask-RESTful-0.3.10.tar.gz", hash = "sha256:fe4af2ef0027df8f9b4f797aba20c5566801b6ade995ac63b588abf1a59cec37", size = 110453 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7b/f0b45f0df7d2978e5ae51804bb5939b7897b2ace24306009da0cc34d8d1f/Flask_RESTful-0.3.10-py2.py3-none-any.whl", hash = "sha256:1cf93c535172f112e080b0d4503a8d15f93a48c88bdd36dd87269bdaf405051b", size = 26217 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-sqlalchemy"
|
||||
version = "3.1.1"
|
||||
@ -350,15 +326,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.9.2"
|
||||
@ -384,15 +351,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/4e/33df635528292bd2d18404e4daabcd74ca8a9853b2e1df85ed3d32d24362/ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6", size = 10001738 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.37"
|
||||
@ -431,7 +389,6 @@ dependencies = [
|
||||
{ name = "alembic" },
|
||||
{ name = "click" },
|
||||
{ name = "flask" },
|
||||
{ name = "flask-restful" },
|
||||
{ name = "flask-sqlalchemy" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "psycopg" },
|
||||
@ -453,7 +410,6 @@ requires-dist = [
|
||||
{ name = "alembic", specifier = ">=1.13.1,<2.0.0" },
|
||||
{ name = "click", specifier = ">=8.1.7,<9.0.0" },
|
||||
{ name = "flask", specifier = ">=3.0.3,<4.0.0" },
|
||||
{ name = "flask-restful", specifier = ">=0.3.10,<1.0.0" },
|
||||
{ name = "flask-sqlalchemy", specifier = ">=3.1.1,<4.0.0" },
|
||||
{ name = "gunicorn", specifier = ">=22.0.0,<23.0.0" },
|
||||
{ name = "psycopg", specifier = ">=3.1.19,<4.0.0" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user