🏠Home • 🎇Features • 🔨Install • 🚜Usage • 💻CLI • 💡Examples
🤖Jinja2 API • ✅Requirements • 🐳Docker • 🚸Gotchas
What it does: ComfyUI Fulcrum lets you manage exclusive access to multiple resources (ComfyUI API URLs).
- Channels.
- Priority: Clients can specify priority for a lease request.
- Expiry: Clients can specify how long they need the lease; if they fail to "touch" the lease before expiry, it is released.
- cli: CLI that gives access to fastapi_{server, client}, backed by db, from the command line.
- fastapi_client: Client, library. Install this if you want to use the API from python.
- fastapi_server: Server, library. Install this if you want to serve the API from python.
- fastapi_mgmt_ui: Management UI, library. Install this if you want to serve the management UI from python.
- db: DB implementation, library. Can be combined with fastapi_server as a serving solution, from python.
- base: Interfaces library. Install this if you want to implement your own implementations.
# Install from pypi (https://pypi.org/project/comfy-fulcrum/)
pip install comfy-fulcrum[cli,db,fastapi_server,fastapi_mgmt_ui,fastapi_client]
# Install from git (https://github.com/realazthat/comfy-fulcrum)
pip install git+https://github.com/realazthat/comfy-fulcrum.git@v0.0.1[cli,db,fastapi_server,fastapi_mgmt_ui,fastapi_client]
Server via CLI:
python -m comfy_fulcrum.cli server \
--dsn "${DSN}" \
--host 0.0.0.0 --port "${FULCRUM_PORT}"
Client via CLI:
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
RESOURCE_A_ID=a
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'","channels": ["main"], "data": "{\"comfy_api_url\": \"url\"}"}' \
register
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
list
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
TICKET=$(python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"client_name": "me","channels": ["main"], "priority": "1"}' \
get)
# Example TICKET:
# {"id": "4fe26e1fe5974e68b788f82cc9460b0b", "client_name": "me", "lease_timeout": 180.0, "ends": "2024-07-21T01:18:16.317032"}
echo "TICKET: ${TICKET}"
TICKET_ID=$(echo "${TICKET}" | jq -r '.id')
echo "TICKET_ID: ${TICKET_ID}"
RESOURCE_ID=
while [[ -z "${RESOURCE_ID}" ]]; do
echo -e "${BLUE}Checking if resource is ready${NC}"
TICKET=$(python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'"}' \
touch)
echo "TICKET: ${TICKET}"
RESOURCE_ID=$(echo "${TICKET}" | jq -r '.resource_id')
echo "RESOURCE_ID: ${RESOURCE_ID}"
sleep 2
done
RESOURCE_DATA=$(echo "${TICKET}" | jq -r '.data')
COMFY_API_URL=$(echo "${RESOURCE_DATA}" | jq -r '.comfy_api_url')
echo -e "${BLUE}COMFY_API_URL: ${COMFY_API_URL}${NC}"
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
sleep 2
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'", "report": "success", "report_extra": {}}' \
release
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'"}' \
remove
python -m comfy_fulcrum.cli client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
Server via Python:
app = fastapi.FastAPI()
db_engine: AsyncEngine = create_async_engine(dsn,
echo=False,
pool_size=10,
max_overflow=20,
pool_timeout=5,
pool_recycle=1800)
fulcrum = DBFulcrum(engine=db_engine,
lease_timeout=60. * 5,
service_sleep_interval=1.0)
await fulcrum.Initialize()
routes = FulcrumServerRoutes(fulcrum=fulcrum)
app.include_router(routes.Router())
hypercorn_config = hypercorn.Config()
hypercorn_config.bind = [f'0.0.0.0:{port}']
hypercorn_config.use_reloader = True
hypercorn_config.loglevel = 'debug'
hypercorn_config.accesslog = '-'
hypercorn_config.errorlog = '-'
hypercorn_config.debug = True
await hypercorn.asyncio.serve(app, hypercorn_config) # type: ignore
Client (resource provider) via Python:
client = FulcrumClient(fulcrum_api_url=fulcrum_api_url)
print(await client.Stats(), file=sys.stderr)
print(await client.RegisterResource(channels=[ChannelID('main')],
resource_id=ResourceID(uuid.uuid4().hex),
data=json.dumps(
{'comfy_api_url': comfy_api_url})),
file=sys.stderr)
print(await client.ListResources(), file=sys.stderr)
Client (resource consumer) via Python:
client = FulcrumClient(fulcrum_api_url=fulcrum_api_url)
print(await client.Stats(), file=sys.stderr)
lease: Union[Lease, Ticket] = await client.Get(client_name=ClientName('test'),
channels=[ChannelID('main')],
priority=1)
print(lease, file=sys.stderr)
async def WaitForResource(id: LeaseID) -> Lease:
while True:
lease_or = await client.TouchTicket(id=id)
if isinstance(lease_or, Lease):
return lease_or
elif isinstance(lease_or, Ticket):
await asyncio.sleep(1)
elif lease_or is None:
print('Lease has expired', file=sys.stderr)
exit(1)
await asyncio.sleep(1)
lease = await WaitForResource(id=lease.id)
print(lease, file=sys.stderr)
print(await client.Stats(), file=sys.stderr)
# Now that we have the lease, let's get the data, which we happen to know is
# json with the comfy_api_url as {'comfy_api_url': <url>}.
resource_data = json.loads(lease.data)
comfy_api_url = resource_data['comfy_api_url']
# Do something with the comfy_api_url here:
print(comfy_api_url, file=sys.stderr)
await asyncio.sleep(1)
# Don't forget to touch the lease occasionally, otherwise it will expire.
await client.TouchLease(id=lease.id)
await asyncio.sleep(1)
# Once done with the lease, release it.
await client.Release(id=lease.id, report='success', report_extra=None)
print(await client.Stats(), file=sys.stderr)
Docker images are published to ghcr.io/realazthat/comfy-fulcrum at each tag.
Gotcha: --tty
will output extra hidden characters.
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 server \
--dsn "${DSN}" \
--host 0.0.0.0 --port "${FULCRUM_PORT}"
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
RESOURCE_A_ID=a
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'","channels": ["main"], "data": "{\"comfy_api_url\": \"url\"}"}' \
register
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
list
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
TICKET=$(docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"client_name": "me","channels": ["main"], "priority": "1"}' \
get)
# Example TICKET:
# {"id": "4fe26e1fe5974e68b788f82cc9460b0b", "client_name": "me", "lease_timeout": 180.0, "ends": "2024-07-21T01:18:16.317032"}
echo "TICKET: ${TICKET}"
TICKET_ID=$(echo "${TICKET}" | jq -r '.id')
echo "TICKET_ID: ${TICKET_ID}"
RESOURCE_ID=
while [[ -z "${RESOURCE_ID}" ]]; do
echo -e "${BLUE}Checking if resource is ready${NC}"
TICKET=$(docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'"}' \
touch)
echo "TICKET: ${TICKET}"
RESOURCE_ID=$(echo "${TICKET}" | jq -r '.resource_id')
echo "RESOURCE_ID: ${RESOURCE_ID}"
sleep 2
done
RESOURCE_DATA=$(echo "${TICKET}" | jq -r '.data')
COMFY_API_URL=$(echo "${RESOURCE_DATA}" | jq -r '.comfy_api_url')
echo -e "${BLUE}COMFY_API_URL: ${COMFY_API_URL}${NC}"
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
sleep 2
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'", "report": "success", "report_extra": {}}' \
release
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'"}' \
remove
docker run --rm --network="host" \
-v "${PWD}:/data" \
ghcr.io/realazthat/comfy_fulcrum:v0.0.1 client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
If you want to build the image yourself, you can use the Dockerfile in the repository.
Build:
docker build -t my-comfy_fulcrum-image .
Server:
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image server \
--dsn "${DSN}" \
--host 0.0.0.0 --port "${FULCRUM_PORT}"
Client:
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
RESOURCE_A_ID=a
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'","channels": ["main"], "data": "{\"comfy_api_url\": \"url\"}"}' \
register
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
list
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
TICKET=$(docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"client_name": "me","channels": ["main"], "priority": "1"}' \
get)
# Example TICKET:
# {"id": "4fe26e1fe5974e68b788f82cc9460b0b", "client_name": "me", "lease_timeout": 180.0, "ends": "2024-07-21T01:18:16.317032"}
echo "TICKET: ${TICKET}"
TICKET_ID=$(echo "${TICKET}" | jq -r '.id')
echo "TICKET_ID: ${TICKET_ID}"
RESOURCE_ID=
while [[ -z "${RESOURCE_ID}" ]]; do
echo -e "${BLUE}Checking if resource is ready${NC}"
TICKET=$(docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'"}' \
touch)
echo "TICKET: ${TICKET}"
RESOURCE_ID=$(echo "${TICKET}" | jq -r '.resource_id')
echo "RESOURCE_ID: ${RESOURCE_ID}"
sleep 2
done
RESOURCE_DATA=$(echo "${TICKET}" | jq -r '.data')
COMFY_API_URL=$(echo "${RESOURCE_DATA}" | jq -r '.comfy_api_url')
echo -e "${BLUE}COMFY_API_URL: ${COMFY_API_URL}${NC}"
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
sleep 2
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"id": "'"${TICKET_ID}"'", "report": "success", "report_extra": {}}' \
release
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '{"resource_id": "'"${RESOURCE_A_ID}"'"}' \
remove
docker run --rm --network="host" \
-v "${PWD}:/data" \
my-comfy_fulcrum-image client \
--fulcrum_api_url "${FULCRUM_API_URL}" \
--data '' \
stats
We use SemVer for versioning. For the versions available, see the tags on this repository.
This project is licensed under the MIT License - see the ./LICENSE.md file for details.